diff --git a/Cargo.lock b/Cargo.lock index aaedf370..6dc40bf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,19 +14,22 @@ dependencies = [ ] [[package]] -name = "aho-corasick" -version = "1.1.2" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "autocfg" @@ -36,9 +39,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base16ct" -version = "0.2.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" @@ -52,12 +55,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" - [[package]] name = "base64ct" version = "1.6.0" @@ -65,16 +62,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] -name = "bitflags" -version = "1.3.2" +name = "bech32" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" [[package]] name = "bitflags" -version = "2.4.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" @@ -95,26 +92,77 @@ dependencies = [ ] [[package]] -name = "bnum" -version = "0.8.0" +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive", + "hashbrown 0.12.3", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2 1.0.70", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2 1.0.70", + "quote 1.0.33", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128a44527fc0d6abf05f9eda748b9027536e12dff93f5acc8449f51583309350" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2 1.0.70", + "quote 1.0.33", + "syn 1.0.109", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" dependencies = [ "serde", ] +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" @@ -123,33 +171,34 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "num-traits", ] [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/icon-project/IBC-Integration.git?branch=feat/cw-wasm-light-client#d4fe981afb549a2e9d232f2aed92c33eec7dce5d" +source = "git+https://github.com/icon-project/IBC-Integration.git?branch=main#9a9095926b9141217473745d21ad06c277313015" dependencies = [ "bytes", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0 (git+https://github.com/icon-project/cw-storage-plus.git?branch=fix-raw)", + "cw-storage-plus 1.1.0", "debug_print", "derive_more", "displaydoc", "dyn-clone", "hex", - "hex-literal 0.3.4", + "hex-literal", "ibc-proto", "ics23", "pbjson", "pbjson-types", - "prost", + "prost 0.11.9", "prost-types", "rlp-derive", "rustc-hex", @@ -157,7 +206,7 @@ dependencies = [ "serde", "serde-json-wasm 0.5.1", "serde_json", - "sha2 0.10.8", + "sha2 0.10.6", "sha3", "subtle-encoding", "tendermint", @@ -166,9 +215,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "cosmwasm" @@ -185,9 +234,9 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.4.1" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fb22494cf7d23d0c348740e06e5c742070b2991fd41db77bba0bcfbae1a723" +checksum = "41c0e41be7e6c7d7ab3c61cdc32fcfaa14f948491a401cbc1c74bb33b6f4b851" dependencies = [ "digest 0.10.7", "ed25519-zebra", @@ -198,21 +247,21 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.4.1" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e199424486ea97d6b211db6387fd72e26b4a439d40cc23140b2d8305728055b" +checksum = "3a7ee2798c92c00dd17bebb4210f81d5f647e5e92d847959b7977e0fd29a3500" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.4.1" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef683a9c1c4eabd6d31515719d0d2cc66952c4c87f7eb192bfc90384517dc34" +checksum = "407aca6f1671a08b60db8167f03bb7cb6b2378f0ddd9a030367b66ba33c2fd41" dependencies = [ "cosmwasm-schema-derive", - "schemars 0.8.15", + "schemars 0.8.12", "serde", "serde_json", "thiserror", @@ -220,49 +269,65 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.4.1" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9567025acbb4c0c008178393eb53b3ac3c2e492c25949d3bf415b9cbe80772d8" +checksum = "e6d1e00b8fd27ff923c10303023626358e23a6f9079f8ebec23a8b4b0bfcd4b3" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", ] [[package]] name = "cosmwasm-std" -version = "1.4.1" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d89d680fb60439b7c5947b15f9c84b961b88d1f8a3b20c4bd178a3f87db8bae" +checksum = "92d5fdfd112b070055f068fad079d490117c8e905a588b92a5a7c9276d029930" dependencies = [ - "base64 0.21.4", - "bnum", + "base64 0.13.1", "cosmwasm-crypto", "cosmwasm-derive", "derivative", "forward_ref", "hex", - "schemars 0.8.15", + "schemars 0.8.12", "serde", "serde-json-wasm 0.5.1", - "sha2 0.10.8", + "sha2 0.10.6", "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9e21c4f58986fd20184d7685e1c43c5732c9309337b09307d5952fd34dba6e" +dependencies = [ + "cosmwasm-std", + "serde", ] [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-bigint" -version = "0.5.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -293,6 +358,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + [[package]] name = "cw-centralized-connection" version = "0.1.0" @@ -301,17 +379,65 @@ dependencies = [ "cosmwasm", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0 (git+https://github.com/icon-project/cw-storage-plus.git?branch=fix-raw)", - "cw-xcall-lib", - "cw2", + "cw-storage-plus 1.1.0", + "cw-xcall-lib 0.1.0", + "cw2 1.0.1", "getrandom", "hex", - "schemars 0.8.15", + "schemars 0.8.12", "serde", "serde-json-wasm 0.5.1", "thiserror", ] +[[package]] +name = "cw-common" +version = "0.1.1" +source = "git+https://github.com/icon-project/IBC-Integration.git?branch=main#9a9095926b9141217473745d21ad06c277313015" +dependencies = [ + "bech32", + "bytes", + "common", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "cw-xcall-lib 0.1.0 (git+https://github.com/icon-project/xCall.git?tag=v0.1.0-alpha.5)", + "debug_print", + "hex", + "hex-buffer-serde", + "ibc-proto", + "prost 0.11.9", + "schemars 0.8.12", + "serde", + "serde-json-wasm 0.5.1", + "serde_json", +] + +[[package]] +name = "cw-integration" +version = "0.2.0" +dependencies = [ + "anyhow", + "common", + "cosmwasm-schema", + "cosmwasm-std", + "cw-common", + "cw-mock-dapp", + "cw-mock-dapp-multi", + "cw-mock-ibc-core", + "cw-multi-test 0.15.1", + "cw-storage-plus 1.1.0", + "cw-xcall 0.2.0", + "cw-xcall-ibc-connection", + "cw-xcall-lib 0.1.0", + "cw-xcall-lib 0.1.0 (git+https://github.com/icon-project/xCall.git?tag=v0.1.0-alpha.5)", + "hex", + "ibc", + "prost 0.11.9", + "strum", + "strum_macros", +] + [[package]] name = "cw-mock-dapp" version = "0.1.0" @@ -320,11 +446,13 @@ dependencies = [ "cosmwasm", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0 (git+https://github.com/icon-project/cw-storage-plus.git?branch=fix-raw)", - "cw-xcall-lib", - "cw2", + "cosmwasm-storage", + "cw-multi-test 0.16.4", + "cw-storage-plus 1.1.0", + "cw-xcall-lib 0.1.0", + "cw2 1.0.1", "getrandom", - "schemars 0.8.15", + "schemars 0.8.12", "serde", "serde-json-wasm 0.5.1", "thiserror", @@ -338,24 +466,94 @@ dependencies = [ "cosmwasm", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0 (git+https://github.com/icon-project/cw-storage-plus.git?branch=fix-raw)", - "cw-xcall-lib", - "cw2", + "cosmwasm-storage", + "cw-multi-test 0.16.4", + "cw-storage-plus 1.1.0", + "cw-xcall-lib 0.1.0", + "cw2 1.0.1", "getrandom", - "schemars 0.8.15", + "schemars 0.8.12", "serde", "serde-json-wasm 0.5.1", "thiserror", ] +[[package]] +name = "cw-mock-ibc-core" +version = "0.1.1" +source = "git+https://github.com/icon-project/IBC-Integration.git?branch=main#9a9095926b9141217473745d21ad06c277313015" +dependencies = [ + "common", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-common", + "cw-storage-plus 1.1.0", + "cw-xcall 0.2.0 (git+https://github.com/icon-project/xCall.git?branch=main)", + "cw2 1.0.1", + "hex", + "schemars 0.8.12", + "serde", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e81b4a7821d5eeba0d23f737c16027b39a600742ca8c32eb980895ffd270f4" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "derivative", + "itertools", + "prost 0.9.0", + "schemars 0.8.12", + "serde", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a18afd2e201221c6d72a57f0886ef2a22151bbc9e6db7af276fde8a91081042" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "cw-utils 1.0.1", + "derivative", + "itertools", + "k256", + "prost 0.9.0", + "schemars 0.8.12", + "serde", + "thiserror", +] + [[package]] name = "cw-storage-plus" -version = "1.1.0" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6cf70ef7686e2da9ad7b067c5942cd3e88dd9453f7af42f54557f8af300fb0" +dependencies = [ + "cosmwasm-std", + "schemars 0.8.12", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" +checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa" dependencies = [ "cosmwasm-std", - "schemars 0.8.15", + "schemars 0.8.12", "serde", ] @@ -365,8 +563,56 @@ version = "1.1.0" source = "git+https://github.com/icon-project/cw-storage-plus.git?branch=fix-raw#238b520c3e193226143eedb717a3ed76187d11de" dependencies = [ "cosmwasm-std", - "schemars 0.8.15", + "schemars 0.8.12", + "serde", +] + +[[package]] +name = "cw-utils" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae0b69fa7679de78825b4edeeec045066aa2b2c4b6e063d80042e565bb4da5c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.15.1", + "schemars 0.8.12", + "semver", "serde", + "thiserror", +] + +[[package]] +name = "cw-utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.0.1", + "schemars 0.8.12", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw-xcall" +version = "0.1.0" +source = "git+https://github.com/icon-project/xCall.git?tag=v0.1.0-alpha.5#7f250bc9c6560591ccc16289cb8159dd100bbe76" +dependencies = [ + "common", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 1.1.0", + "cw-xcall-lib 0.1.0 (git+https://github.com/icon-project/xCall.git?tag=v0.1.0-alpha.5)", + "cw2 1.0.1", + "debug_print", + "schemars 0.8.12", + "serde", + "thiserror", ] [[package]] @@ -378,49 +624,135 @@ dependencies = [ "cosmwasm", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0 (git+https://github.com/icon-project/cw-storage-plus.git?branch=fix-raw)", - "cw-xcall-lib", - "cw2", + "cosmwasm-storage", + "cw-storage-plus 1.1.0", + "cw-xcall-lib 0.1.0", + "cw2 1.0.1", "debug_print", "getrandom", "hex", - "schemars 0.8.15", + "schemars 0.8.12", "serde", "test-utils", "thiserror", ] +[[package]] +name = "cw-xcall" +version = "0.2.0" +source = "git+https://github.com/icon-project/xCall.git?branch=main#1b64d8d3b932842bdc68d7fabc3842a7695860ed" +dependencies = [ + "common", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 1.1.0", + "cw-xcall-lib 0.1.0 (git+https://github.com/icon-project/xCall.git?branch=main)", + "cw2 1.0.1", + "debug_print", + "schemars 0.8.12", + "serde", + "thiserror", +] + +[[package]] +name = "cw-xcall-ibc-connection" +version = "0.1.1" +source = "git+https://github.com/icon-project/IBC-Integration.git?branch=main#9a9095926b9141217473745d21ad06c277313015" +dependencies = [ + "common", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-common", + "cw-storage-plus 1.1.0", + "cw-xcall 0.1.0", + "cw-xcall-lib 0.1.0 (git+https://github.com/icon-project/xCall.git?tag=v0.1.0-alpha.5)", + "cw2 1.0.1", + "debug_print", + "hex", + "schemars 0.8.12", + "serde", + "thiserror", +] + [[package]] name = "cw-xcall-lib" version = "0.1.0" dependencies = [ "anyhow", + "common", "cosmwasm", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0 (git+https://github.com/icon-project/cw-storage-plus.git?branch=fix-raw)", - "cw2", + "cosmwasm-storage", + "cw-storage-plus 1.1.0", + "cw2 1.0.1", "debug_print", "getrandom", "hex", - "schemars 0.8.15", + "schemars 0.8.12", "serde", "test-utils", "thiserror", ] +[[package]] +name = "cw-xcall-lib" +version = "0.1.0" +source = "git+https://github.com/icon-project/xCall.git?tag=v0.1.0-alpha.5#7f250bc9c6560591ccc16289cb8159dd100bbe76" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 1.1.0", + "cw2 1.0.1", + "debug_print", + "schemars 0.8.12", + "serde", + "thiserror", +] + +[[package]] +name = "cw-xcall-lib" +version = "0.1.0" +source = "git+https://github.com/icon-project/xCall.git?branch=main#1b64d8d3b932842bdc68d7fabc3842a7695860ed" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 1.1.0", + "cw2 1.0.1", + "debug_print", + "schemars 0.8.12", + "serde", + "thiserror", +] + [[package]] name = "cw2" -version = "1.1.1" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9431d14f64f49e41c6ef5561ed11a5391c417d0cb16455dea8cdcb9037a8d197" +checksum = "5abb8ecea72e09afff830252963cb60faf945ce6cef2c20a43814516082653da" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "schemars 0.8.15", + "cw-storage-plus 0.15.1", + "schemars 0.8.12", + "serde", +] + +[[package]] +name = "cw2" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "schemars 0.8.12", "serde", - "thiserror", ] [[package]] @@ -431,9 +763,9 @@ checksum = "8f215f9b7224f49fb73256115331f677d868b34d18b65dbe4db392e6021eea90" [[package]] name = "der" -version = "0.7.8" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", "zeroize", @@ -445,7 +777,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", ] @@ -456,7 +788,7 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", ] @@ -477,7 +809,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", "crypto-common", "subtle", ] @@ -488,9 +819,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", - "syn 2.0.38", + "syn 2.0.42", ] [[package]] @@ -501,22 +832,20 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dyn-clone" -version = "1.0.14" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der", - "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature 2.1.0", - "spki", + "signature", ] [[package]] @@ -525,7 +854,20 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature 1.6.4", + "signature", +] + +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", ] [[package]] @@ -545,18 +887,19 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" -version = "0.13.6" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97ca172ae9dc9f9b779a6e3a65d308f2af74e5b8c921299075bdb4a0370e914" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", + "der", "digest 0.10.7", "ff", "generic-array", @@ -574,32 +917,64 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "erased-serde" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +dependencies = [ + "serde", +] + [[package]] name = "errno" -version = "0.3.5" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", "libc", - "windows-sys", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] [[package]] name = "ff" -version = "0.13.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -690,14 +1065,13 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", @@ -706,9 +1080,9 @@ dependencies = [ [[package]] name = "group" -version = "0.13.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff", "rand_core 0.6.4", @@ -726,9 +1100,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -736,6 +1110,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -743,16 +1123,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "hex-literal" -version = "0.3.4" +name = "hex-buffer-serde" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +checksum = "c7e84645a601cf4a58f40673d51c111d1b5f847b711559c076ebcb779606a6d0" +dependencies = [ + "hex", + "serde", +] [[package]] name = "hex-literal" -version = "0.4.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] name = "hmac" @@ -764,12 +1148,36 @@ dependencies = [ ] [[package]] -name = "home" -version = "0.5.5" +name = "ibc" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "030b54b02c3f35ea7265da2beedf0d7d22492208919a6582f980ff3eae82c2d0" dependencies = [ - "windows-sys", + "bytes", + "cfg-if", + "derive_more", + "displaydoc", + "dyn-clone", + "erased-serde", + "ibc-proto", + "ics23", + "num-traits", + "parity-scale-codec", + "primitive-types", + "prost 0.11.9", + "safe-regex", + "scale-info", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.6", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "time", + "tracing", + "uint", ] [[package]] @@ -778,38 +1186,91 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9303a1308c886aea769ef0667c5caa422a78b01e9f8177fea8b91b08a4ff50c" dependencies = [ - "base64 0.13.1", - "bytes", - "flex-error", - "prost", - "serde", - "subtle-encoding", - "tendermint-proto", + "base64 0.13.1", + "borsh", + "bytes", + "flex-error", + "parity-scale-codec", + "prost 0.11.9", + "scale-info", + "serde", + "subtle-encoding", + "tendermint-proto", +] + +[[package]] +name = "ics23" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca44b684ce1859cff746ff46f5765ab72e12e3c06f76a8356db8f9a2ecf43f17" +dependencies = [ + "anyhow", + "bytes", + "hex", + "prost 0.11.9", + "ripemd", + "sha2 0.10.6", + "sha3", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2 1.0.70", + "quote 1.0.33", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", ] [[package]] -name = "ics23" -version = "0.9.0" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca44b684ce1859cff746ff46f5765ab72e12e3c06f76a8356db8f9a2ecf43f17" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "anyhow", - "bytes", - "hex", - "prost", - "ripemd", - "sha2 0.10.8", - "sha3", + "cfg-if", ] [[package]] -name = "indexmap" -version = "2.0.2" +name = "io-lifetimes" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "equivalent", - "hashbrown 0.14.1", + "hermit-abi", + "libc", + "windows-sys 0.48.0", ] [[package]] @@ -823,22 +1284,20 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "k256" -version = "0.13.1" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", - "sha2 0.10.8", - "signature 2.1.0", + "sha2 0.10.6", ] [[package]] @@ -858,21 +1317,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "log" -version = "0.4.20" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "memchr" @@ -892,16 +1351,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] @@ -918,11 +1377,35 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "parity-scale-codec" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +dependencies = [ + "arrayvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +dependencies = [ + "proc-macro-crate 2.0.1", + "proc-macro2 1.0.70", + "quote 1.0.33", + "syn 1.0.109", +] + [[package]] name = "paste" -version = "1.0.14" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pbjson" @@ -942,7 +1425,7 @@ checksum = "bdbb7b706f2afc610f3853550cdbbf6372fd324824a087806bd4480ea4996e24" dependencies = [ "heck", "itertools", - "prost", + "prost 0.11.9", "prost-types", ] @@ -956,26 +1439,26 @@ dependencies = [ "chrono", "pbjson", "pbjson-build", - "prost", + "prost 0.11.9", "prost-build", "serde", ] [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.3", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -985,9 +1468,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.10.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ "der", "spki", @@ -999,28 +1482,78 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "syn 1.0.109", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", +] + [[package]] name = "proc-macro2" version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive 0.9.0", +] + [[package]] name = "prost" version = "0.11.9" @@ -1028,7 +1561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.9", ] [[package]] @@ -1045,7 +1578,7 @@ dependencies = [ "multimap", "petgraph", "prettyplease", - "prost", + "prost 0.11.9", "prost-types", "regex", "syn 1.0.109", @@ -1053,6 +1586,19 @@ dependencies = [ "which", ] +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2 1.0.70", + "quote 1.0.33", + "syn 1.0.109", +] + [[package]] name = "prost-derive" version = "0.11.9" @@ -1061,7 +1607,7 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", ] @@ -1072,7 +1618,7 @@ version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "prost", + "prost 0.11.9", ] [[package]] @@ -1090,7 +1636,7 @@ version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", ] [[package]] @@ -1114,46 +1660,33 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] name = "regex" -version = "1.10.0" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" -dependencies = [ - "aho-corasick", - "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.8.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "rfc6979" -version = "0.4.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ + "crypto-bigint", "hmac", - "subtle", + "zeroize", ] [[package]] @@ -1171,7 +1704,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", ] @@ -1184,30 +1717,37 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustix" -version = "0.38.18" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ - "bitflags 2.4.0", + "bitflags", "errno", + "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safe-proc-macro2" -version = "1.0.67" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd85be67db87168aa3c13fd0da99f48f2ab005dccad5af5626138dc1df20eb6" +checksum = "814c536dcd27acf03296c618dab7ad62d28e70abd7ba41d3f34a2ce707a2c666" dependencies = [ - "unicode-ident", + "unicode-xid 0.2.4", ] [[package]] @@ -1248,6 +1788,30 @@ dependencies = [ "safe-regex-compiler", ] +[[package]] +name = "scale-info" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +dependencies = [ + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2 1.0.70", + "quote 1.0.33", + "syn 1.0.109", +] + [[package]] name = "schemars" version = "0.5.1" @@ -1261,12 +1825,12 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" dependencies = [ "dyn-clone", - "schemars_derive 0.8.15", + "schemars_derive 0.8.12", "serde", "serde_json", ] @@ -1277,7 +1841,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ab7f0b9b64633747a68dce4ed6318bbf30e3befe67df7624ad83abb7295c09" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "serde_derive_internals 0.25.0", "syn 1.0.109", @@ -1285,11 +1849,11 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "serde_derive_internals 0.26.0", "syn 1.0.109", @@ -1297,9 +1861,9 @@ dependencies = [ [[package]] name = "sec1" -version = "0.7.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", "der", @@ -1309,11 +1873,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -1338,22 +1908,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", - "syn 2.0.38", + "syn 2.0.42", ] [[package]] @@ -1362,7 +1932,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", ] @@ -1373,16 +1943,16 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1391,13 +1961,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", - "syn 2.0.38", + "syn 2.0.42", ] [[package]] @@ -1415,9 +1985,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", @@ -1439,12 +2009,6 @@ name = "signature" version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - -[[package]] -name = "signature" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -1473,14 +2037,39 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2 1.0.70", + "quote 1.0.33", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.5.0" @@ -1496,6 +2085,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "syn" version = "0.15.44" @@ -1504,7 +2099,7 @@ checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "unicode-xid", + "unicode-xid 0.1.0", ] [[package]] @@ -1513,33 +2108,33 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.38" +version = "2.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", "unicode-ident", ] [[package]] name = "tempfile" -version = "3.8.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1551,17 +2146,19 @@ dependencies = [ "bytes", "digest 0.10.7", "ed25519", + "ed25519-consensus", "flex-error", "futures", "num-traits", "once_cell", - "prost", + "prost 0.11.9", "prost-types", "serde", "serde_bytes", "serde_json", "serde_repr", - "signature 1.6.4", + "sha2 0.10.6", + "signature", "subtle", "subtle-encoding", "tendermint-proto", @@ -1569,6 +2166,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tendermint-light-client-verifier" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c3dc3c75f7a5708ac0bf98374b2b1a2cf17b3a45ddfd5faab3c111aff7fc0e" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint", + "time", +] + [[package]] name = "tendermint-proto" version = "0.29.1" @@ -1579,7 +2189,7 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost", + "prost 0.11.9", "prost-types", "serde", "serde_bytes", @@ -1590,36 +2200,37 @@ dependencies = [ [[package]] name = "test-utils" version = "0.1.0" -source = "git+https://github.com/icon-project/IBC-Integration.git?branch=feat/cw-wasm-light-client#d4fe981afb549a2e9d232f2aed92c33eec7dce5d" +source = "git+https://github.com/icon-project/IBC-Integration.git?branch=main#9a9095926b9141217473745d21ad06c277313015" dependencies = [ "common", "cosmwasm-std", + "cw-multi-test 0.16.4", "hex", - "hex-literal 0.4.1", + "hex-literal", "ibc-proto", - "prost", + "prost 0.11.9", "serde", "serde_json", ] [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", - "syn 2.0.38", + "syn 2.0.42", ] [[package]] @@ -1647,17 +2258,82 @@ dependencies = [ "time-core", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" + [[package]] name = "typenum" -version = "1.17.0" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-xid" @@ -1665,6 +2341,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "version_check" version = "0.9.4" @@ -1679,14 +2361,22 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "which" -version = "4.4.2" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", - "home", + "libc", "once_cell", - "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", ] [[package]] @@ -1695,65 +2385,131 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" +dependencies = [ + "memchr", +] [[package]] name = "zeroize" @@ -1770,7 +2526,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2 1.0.70", "quote 1.0.33", - "syn 2.0.38", + "syn 2.0.42", ] diff --git a/Cargo.toml b/Cargo.toml index 7d1e2d06..de108d13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,12 @@ repository = "https://github.com/icon-project/ibc-integration.git" version="0.2.0" [workspace.dependencies] -cosmwasm-std = {version="1.2.2",default-features = false} +cosmwasm-std = {version="1.2.2",default-features = false,features = ["iterator", "staking"]} cw-storage-plus = {git="https://github.com/icon-project/cw-storage-plus.git", branch="fix-raw"} -cosmwasm-schema = {version="1.2.2",default-features = false} -common = { git = "https://github.com/icon-project/IBC-Integration.git",branch="feat/cw-wasm-light-client" ,default-features = false} -schemars = {version="0.8.12",default-features = false} -cw2 = {version="1.0.1",default-features = false} +cosmwasm-storage = "1.2.2" +cosmwasm-schema = "1.2.2" +schemars = "0.8.12" +cw2 = "1.0.1" prost = { version = "0.11.8", default-features = false,features=["prost-derive"]} @@ -27,8 +27,15 @@ bytes = { version = "1.4.0", default-features = false } thiserror = { version = "1.0.39" } hex ={ version = "0.4.3", default-features = false } debug_print = "1.0.0" - - +cw-xcall-lib = { path="contracts/cosmwasm-vm/cw-xcall-lib" } +cw-xcall={ path="contracts/cosmwasm-vm/cw-xcall" } +cw-xcall-ibc-connection = { git="https://github.com/icon-project/IBC-Integration.git", branch = "main" } +cw-mock-ibc-core = { git="https://github.com/icon-project/IBC-Integration.git", branch = "main"} +common = { git="https://github.com/icon-project/IBC-Integration.git", branch = "main" } +cw-common={ git="https://github.com/icon-project/IBC-Integration.git", branch = "main" } + +cw-mock-dapp = {path="contracts/cosmwasm-vm/cw-mock-dapp"} +cw-mock-dapp-multi = { path="contracts/cosmwasm-vm/cw-mock-dapp-multi"} [profile.release] opt-level = 'z' diff --git a/Makefile b/Makefile index 6aabc138..5a4c9755 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ build-java-docker: docker-compose -f ./scripts/docker-compose.yml up java build-wasm-docker: - @echo "Build java contracts" + @echo "Build wasm contracts" docker-compose -f ./scripts/docker-compose.yml up wasm gobuild: diff --git a/contracts/cosmwasm-vm/cw-integration/Cargo.toml b/contracts/cosmwasm-vm/cw-integration/Cargo.toml new file mode 100644 index 00000000..fcc9572d --- /dev/null +++ b/contracts/cosmwasm-vm/cw-integration/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "cw-integration" +version.workspace = true +authors.workspace = true +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-schema = {workspace=true} +cosmwasm-std = {workspace=true} +cw-storage-plus = {workspace=true} +cw-common={workspace=true} +common = { workspace=true, default-features = false } +ibc = { version = "0.32.0", default-features = false, features = ["parity-scale-codec", "mocks-no-std", "serde"]} +prost = { workspace=true} +strum="*" +strum_macros = "0.24" +cw-xcall-lib={package = "cw-xcall-lib",workspace=true} + +[dev-dependencies] +hex = "0.4.3" +cw-multi-test = "0.15.1" +cw-xcall-ibc-connection = { workspace=true } +cw-mock-ibc-core = { workspace=true } +cw-mock-dapp = { workspace=true} +cw-mock-dapp-multi = { workspace=true } +## xcall connection uses the alpha tag need to update +xcall-lib={package="cw-xcall-lib", git="https://github.com/icon-project/xCall.git", tag="v0.1.0-alpha.5"} + +cw-xcall = {workspace=true} +anyhow="*" + + diff --git a/contracts/cosmwasm-vm/cw-integration/tests/setup.rs b/contracts/cosmwasm-vm/cw-integration/tests/setup.rs new file mode 100644 index 00000000..a326167f --- /dev/null +++ b/contracts/cosmwasm-vm/cw-integration/tests/setup.rs @@ -0,0 +1,277 @@ +use std::collections::HashMap; + +use cosmwasm_std::{ + coins, + testing::{mock_dependencies, mock_info, MockApi, MockQuerier, MockStorage}, + Addr, Attribute, Empty, Event, IbcEndpoint, MessageInfo, OwnedDeps, +}; +use cw_common::ibc_types::IbcEventType; +use cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor}; +use cw_xcall_ibc_connection::state::IbcConfig; + +pub const PORT: &str = "xcall"; +pub const COUNTERPARTY_NID: &str = "0x3.icon"; + +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum TestApps { + Dapp, + XcallApp, + IbcCore, + XcallIbcConnection, +} + +pub struct TestContext { + pub app: App, + pub contracts: HashMap, + pub sender: Addr, + pub admin: Option, + pub caller: Option, +} + +impl TestContext { + pub fn get_dapp(&self) -> Addr { + return self.contracts.get(&TestApps::Dapp).unwrap().clone(); + } + pub fn get_ibc_core(&self) -> Addr { + return self.contracts.get(&TestApps::IbcCore).unwrap().clone(); + } + + pub fn get_xcall_app(&self) -> Addr { + return self.contracts.get(&TestApps::XcallApp).unwrap().clone(); + } + pub fn get_xcall_ibc_connection(&self) -> Addr { + return self + .contracts + .get(&TestApps::XcallIbcConnection) + .unwrap() + .clone(); + } + + pub fn set_dapp(&mut self, addr: Addr) -> Option { + self.contracts.insert(TestApps::Dapp, addr) + } + pub fn set_ibc_core(&mut self, addr: Addr) -> Option { + self.contracts.insert(TestApps::IbcCore, addr) + } + + pub fn set_xcall_app(&mut self, addr: Addr) -> Option { + self.contracts.insert(TestApps::XcallApp, addr) + } + pub fn set_xcall_ibc_connection(&mut self, addr: Addr) -> Option { + self.contracts.insert(TestApps::XcallIbcConnection, addr) + } + + pub fn list_contracts(&self) { + println!("IbcHost {}", self.get_ibc_core()); + println!("IbcConnection {}", self.get_xcall_ibc_connection()); + println!("Xcall {}", self.get_xcall_app()); + println!("Dapp {}", self.get_dapp()) + } +} + +pub fn create_mock_info(creator: &str, denom: &str, amount: u128) -> MessageInfo { + let funds = coins(amount, denom); + mock_info(creator, &funds) +} + +pub fn deps() -> OwnedDeps { + mock_dependencies() +} + +pub fn mock_ibc_config() -> IbcConfig { + let src = IbcEndpoint { + port_id: "xcall".to_string(), + channel_id: "our-channel-id".to_string(), + }; + + let dst = IbcEndpoint { + port_id: "xcall".to_string(), + channel_id: "their-channel-id".to_string(), + }; + + IbcConfig::new(src, dst) +} + +pub fn mock_dapp_contract() -> Box> { + let contract = ContractWrapper::new( + cw_mock_dapp::execute, + cw_mock_dapp::instantiate, + cw_mock_dapp::query, + ); + Box::new(contract) +} + +pub fn mock_dapp_multi_contract() -> Box> { + let contract = ContractWrapper::new( + cw_mock_dapp_multi::execute, + cw_mock_dapp_multi::instantiate, + cw_mock_dapp_multi::query, + ); + Box::new(contract) +} + +pub fn init_mock_dapp_multi_contract(mut ctx: TestContext) -> TestContext { + let code_id = ctx.app.store_code(mock_dapp_multi_contract()); + let contract_addr = ctx + .app + .instantiate_contract( + code_id, + ctx.sender.clone(), + &cw_mock_dapp_multi::types::InstantiateMsg { + address: ctx.get_xcall_app().to_string(), + }, + &[], + "MockApp", + Some(ctx.sender.clone().to_string()), + ) + .unwrap(); + ctx.set_dapp(contract_addr); + + ctx +} + +pub fn init_mock_dapp_contract(mut ctx: TestContext) -> TestContext { + let code_id = ctx.app.store_code(mock_dapp_contract()); + let contract_addr = ctx + .app + .instantiate_contract( + code_id, + ctx.sender.clone(), + &cw_mock_dapp::types::InstantiateMsg { + address: "someaddr".to_string(), + }, + &[], + "MockApp", + Some(ctx.sender.clone().to_string()), + ) + .unwrap(); + ctx.set_dapp(contract_addr); + + ctx +} + +pub fn init_mock_ibc_core_contract(mut ctx: TestContext) -> TestContext { + let ibc_core_code_id = ctx.app.store_code(mock_ibc_core_contract()); + let ibc_core_addr = ctx + .app + .instantiate_contract( + ibc_core_code_id, + ctx.sender.clone(), + &cw_common::core_msg::InstantiateMsg {}, + &[], + "IBCCore", + Some(ctx.sender.clone().to_string()), + ) + .unwrap(); + + ctx.set_ibc_core(ibc_core_addr); + ctx +} + +pub fn ibc_connection_contract() -> Box> { + let contract = ContractWrapper::new( + cw_xcall_ibc_connection::execute, + cw_xcall_ibc_connection::instantiate, + cw_xcall_ibc_connection::query, + ) + .with_reply(cw_xcall_ibc_connection::reply); + Box::new(contract) +} + +pub fn mock_ibc_core_contract() -> Box> { + let contract = ContractWrapper::new( + cw_mock_ibc_core::contract::execute, + cw_mock_ibc_core::contract::instantiate, + cw_mock_ibc_core::contract::query, + ) + .with_reply(cw_mock_ibc_core::contract::reply); + Box::new(contract) +} + +pub fn xcall_app_contract() -> Box> { + let contract = ContractWrapper::new(cw_xcall::execute, cw_xcall::instantiate, cw_xcall::query) + .with_reply(cw_xcall::reply); + Box::new(contract) +} + +pub fn init_xcall_app_contract(mut ctx: TestContext) -> TestContext { + let xcall_app_contractcode_id = ctx.app.store_code(xcall_app_contract()); + let xcall_app_contract_addr = ctx + .app + .instantiate_contract( + xcall_app_contractcode_id, + ctx.sender.clone(), + &cw_xcall::msg::InstantiateMsg { + network_id: "nid".to_string(), + denom: "uarch".to_string(), + }, + &[], + "XCallApp", + Some(ctx.sender.clone().to_string()), + ) + .unwrap(); + + ctx.set_xcall_app(xcall_app_contract_addr); + ctx +} + +pub fn init_xcall_ibc_connection_contract(mut ctx: TestContext) -> TestContext { + let ibc_connection_contract_code_id = ctx.app.store_code(ibc_connection_contract()); + let ibc_connection_contract_addr = ctx + .app + .instantiate_contract( + ibc_connection_contract_code_id, + ctx.sender.clone(), + &cw_xcall_ibc_connection::msg::InstantiateMsg { + ibc_host: ctx.get_ibc_core(), + denom: "uarch".to_string(), + port_id: PORT.to_string(), + xcall_address: ctx.get_xcall_app(), + }, + &[], + "IBCConnection", + Some(ctx.sender.clone().to_string()), + ) + .unwrap(); + + ctx.set_xcall_ibc_connection(ibc_connection_contract_addr); + ctx +} + +pub fn setup_context() -> TestContext { + let router = App::default(); + let sender = Addr::unchecked("sender"); + + TestContext { + app: router, + contracts: HashMap::new(), + sender, + admin: None, + caller: None, + } +} + +pub fn get_event(res: &AppResponse, event: &str) -> Option> { + let event = res + .events + .iter() + .filter(|e| e.ty == event) + .collect::>(); + if !event.is_empty() { + let map = to_attribute_map(&event[0].attributes); + return Some(map); + } + None +} + +pub fn get_event_name(event_type: IbcEventType) -> String { + format!("wasm-{}", event_type.as_str()) +} + +pub fn to_attribute_map(attrs: &Vec) -> HashMap { + let mut map = HashMap::new(); + for attr in attrs { + map.insert(attr.key.clone(), attr.value.clone()); + } + map +} diff --git a/contracts/cosmwasm-vm/cw-integration/tests/test_xcall.rs b/contracts/cosmwasm-vm/cw-integration/tests/test_xcall.rs new file mode 100644 index 00000000..bef858ea --- /dev/null +++ b/contracts/cosmwasm-vm/cw-integration/tests/test_xcall.rs @@ -0,0 +1,360 @@ +mod setup; +use std::str::FromStr; + +use anyhow::Error as AppError; + +use common::rlp::Nullable; + +use cosmwasm_std::Addr; +use cosmwasm_std::IbcChannel; + +use cw_common::raw_types::channel::RawPacket; + +use cw_multi_test::AppResponse; + +use cw_multi_test::Executor; + +use cw_xcall::types::message::CSMessage; +use cw_xcall::types::request::CSMessageRequest; +use cw_xcall::types::result::CSMessageResult; +use cw_xcall::types::result::CallServiceResponseType; +use cw_xcall_ibc_connection::types::message::Message; +use cw_xcall_lib::message::call_message_rollback::CallMessageWithRollback; +use cw_xcall_lib::message::envelope::Envelope; +use cw_xcall_lib::message::AnyMessage; + +use cw_common::ProstMessage; +use cw_xcall_lib::message::msg_type::MessageType; +use cw_xcall_lib::network_address::NetworkAddress; +use setup::init_mock_dapp_multi_contract; +use setup::{ + init_mock_ibc_core_contract, init_xcall_app_contract, init_xcall_ibc_connection_contract, + TestContext, +}; +use xcall_lib::network_address::NetId; + +use crate::setup::get_event; +use crate::setup::mock_ibc_config; +use crate::setup::setup_context; +const MOCK_CONTRACT_TO_ADDR: &str = "cosmoscontract"; + +fn setup_contracts(mut ctx: TestContext) -> TestContext { + ctx = init_mock_ibc_core_contract(ctx); + // ctx.set_ibc_core(ctx.sender.clone()); + ctx = init_xcall_app_contract(ctx); + ctx = init_xcall_ibc_connection_contract(ctx); + ctx = init_mock_dapp_multi_contract(ctx); + ctx +} + +fn setup_test() -> TestContext { + let mut context = setup_context(); + context = setup_contracts(context); + context +} + +pub fn call_send_call_message( + ctx: &mut TestContext, + to: &str, + sources: Vec, + destinations: Vec, + data: Vec, + rollback: Option>, +) -> Result { + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_xcall_app(), + &xcall_lib::xcall_msg::ExecuteMsg::SendCallMessage { + to: xcall_lib::network_address::NetworkAddress::from_str(to).unwrap(), + data, + rollback, + sources: Some(sources), + destinations: Some(destinations), + }, + &[], + ) +} + +pub fn call_execute_call_message( + ctx: &mut TestContext, + request_id: u128, + data: Vec, +) -> Result { + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_xcall_app(), + &xcall_lib::xcall_msg::ExecuteMsg::ExecuteCall { request_id, data }, + &[], + ) +} + +pub fn call_dapp_send_call( + ctx: &mut TestContext, + to: String, + envelope: Envelope, +) -> Result { + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_dapp(), + &cw_mock_dapp_multi::msg::ExecuteMsg::SendMessageAny { + to: cw_xcall_lib::network_address::NetworkAddress::from_str(&to).unwrap(), + envelope, + }, + &[], + ) +} + +pub fn call_dapp_add_connection( + ctx: &mut TestContext, + src_endpoint: String, + dest_endpoint: String, + network_id: String, +) -> Result { + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_dapp(), + &cw_mock_dapp_multi::msg::ExecuteMsg::AddConnection { + src_endpoint, + dest_endpoint, + network_id, + }, + &[], + ) +} + +pub fn call_set_xcall_host(ctx: &mut TestContext) -> Result { + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_xcall_ibc_connection(), + &cw_common::xcall_connection_msg::ExecuteMsg::SetXCallHost { + address: ctx.get_xcall_app().to_string(), + }, + &[], + ) +} + +pub fn call_set_default_connection( + ctx: &mut TestContext, + nid: String, +) -> Result { + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_xcall_app(), + &xcall_lib::xcall_msg::ExecuteMsg::SetDefaultConnection { + nid: NetId::from(nid), + address: ctx.get_xcall_ibc_connection(), + }, + &[], + ) +} + +pub fn call_configure_connection( + ctx: &mut TestContext, + connection_id: String, + nid: String, + client_id: String, +) -> Result { + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_xcall_ibc_connection(), + &cw_common::xcall_connection_msg::ExecuteMsg::ConfigureConnection { + connection_id, + counterparty_port_id: "xcall".to_string(), + counterparty_nid: NetId::from_str(&nid).unwrap(), + client_id, + timeout_height: 10, + }, + &[], + ) +} + +pub fn call_ibc_channel_connect(ctx: &mut TestContext) -> Result { + let ibc_config = mock_ibc_config(); + let channel = IbcChannel::new( + ibc_config.src_endpoint().clone(), + ibc_config.dst_endpoint().clone(), + cosmwasm_std::IbcOrder::Unordered, + "ics-20", + "connection-1", + ); + + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_ibc_core(), + &cw_mock_ibc_core::msg::ExecuteMsg::IbcConfig { + msg: cosmwasm_std::IbcChannelConnectMsg::OpenConfirm { channel }, + }, + &[], + ) +} + +pub fn call_ibc_receive_packet( + ctx: &mut TestContext, + msg: Vec, +) -> Result { + let ibc_config = mock_ibc_config(); + let packet = RawPacket { + sequence: 1, + source_port: ibc_config.dst_endpoint().port_id.to_string(), + source_channel: ibc_config.dst_endpoint().channel_id.to_string(), + destination_port: ibc_config.src_endpoint().port_id.to_string(), + destination_channel: ibc_config.src_endpoint().channel_id.to_string(), + data: msg, + timeout_height: Some(cw_common::raw_types::RawHeight { + revision_number: 0, + revision_height: 12345, + }), + timeout_timestamp: 17747483838282, + }; + let packet_bytes = hex::encode(packet.encode_to_vec()); + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_ibc_core(), + &cw_mock_ibc_core::msg::ExecuteMsg::ReceivePacket { + message: packet_bytes, + }, + &[], + ) +} + +pub fn call_register_connection(ctx: &mut TestContext) -> Result { + ctx.app.execute_contract( + ctx.sender.clone(), + ctx.get_ibc_core(), + &cw_mock_ibc_core::msg::ExecuteMsg::RegisterXcall { + address: ctx.get_xcall_ibc_connection(), + }, + &[], + ) +} + +#[test] +fn test_xcall_send_call_message() { + let mut ctx = setup_test(); + call_set_xcall_host(&mut ctx).unwrap(); + call_register_connection(&mut ctx).unwrap(); + let src = ctx.get_xcall_ibc_connection().to_string(); + + let nid = "0x3.icon"; + call_configure_connection( + &mut ctx, + "connection-1".to_string(), + nid.to_string(), + "client-1".to_string(), + ) + .unwrap(); + call_ibc_channel_connect(&mut ctx).unwrap(); + let result = call_send_call_message( + &mut ctx, + &format!("{nid}/{MOCK_CONTRACT_TO_ADDR}"), + vec![src], + vec!["somedestination".to_string()], + vec![1, 2, 3], + None, + ); + println!("{result:?}"); + assert!(result.is_ok()); + let result = result.unwrap(); + let event = get_event(&result, "wasm-CallMessageSent").unwrap(); + println!("{event:?}"); + assert_eq!( + &format!("{nid}/{MOCK_CONTRACT_TO_ADDR}"), + event.get("to").unwrap() + ); +} + +#[test] +fn test_xcall_send_call() { + let mut ctx = setup_test(); + call_set_xcall_host(&mut ctx).unwrap(); + call_register_connection(&mut ctx).unwrap(); + let src = ctx.get_xcall_ibc_connection().to_string(); + let dapp = ctx.get_dapp().to_string(); + + let nid = "0x3.icon"; + call_configure_connection( + &mut ctx, + "connection-1".to_string(), + nid.to_string(), + "client-1".to_string(), + ) + .unwrap(); + call_ibc_channel_connect(&mut ctx).unwrap(); + let message = AnyMessage::CallMessageWithRollback(CallMessageWithRollback { + data: vec![1, 2, 3], + rollback: "rollback-reply".as_bytes().to_vec(), + }); + let envelope = Envelope::new(message, vec![src], vec!["somedestination".to_string()]); + let result = call_dapp_send_call(&mut ctx, format!("{nid}/{dapp}"), envelope); + println!("{result:?}"); + assert!(result.is_ok()); + let result = result.unwrap(); + let event = get_event(&result, "wasm-CallMessageSent").unwrap(); + println!("{event:?}"); + assert_eq!(&format!("{nid}/{dapp}"), event.get("to").unwrap()); +} + +#[test] +fn test_rollback_reply() { + let mut ctx = setup_test(); + call_set_xcall_host(&mut ctx).unwrap(); + call_register_connection(&mut ctx).unwrap(); + let src = ctx.get_xcall_ibc_connection().to_string(); + let _dapp = ctx.get_dapp().to_string(); + + let nid = "0x3.icon"; + call_configure_connection( + &mut ctx, + "connection-1".to_string(), + nid.to_string(), + "client-1".to_string(), + ) + .unwrap(); + call_ibc_channel_connect(&mut ctx).unwrap(); + call_dapp_add_connection(&mut ctx, src, "somedest".to_string(), nid.to_string()).unwrap(); + let data = "reply-response".as_bytes().to_vec(); + let msg = CSMessageRequest::new( + NetworkAddress::from_str(&format!("{nid}/{MOCK_CONTRACT_TO_ADDR}")).unwrap(), + ctx.get_dapp(), + 1, + MessageType::CallMessageWithRollback, + data.clone(), + vec![ctx.get_xcall_ibc_connection().to_string()], + ); + let request = CSMessage { + message_type: cw_xcall::types::message::CSMessageType::CSMessageRequest, + payload: msg.as_bytes(), + }; + + let msg = Message { + sn: Nullable::new(Some(1_i64)), + fee: 0_u128, + data: request.as_bytes(), + }; + let bytes: Vec = common::rlp::encode(&msg).to_vec(); + + call_ibc_receive_packet(&mut ctx, bytes).unwrap(); + let expected_reply = CSMessageRequest::new( + NetworkAddress::from_str("nid/contract3").unwrap(), + Addr::unchecked(MOCK_CONTRACT_TO_ADDR), + 1, + MessageType::CallMessage, + vec![1, 2, 3], + vec!["somedest".to_string()], + ); + let reply_message = CSMessageResult::new( + expected_reply.sequence_no(), + CallServiceResponseType::CallServiceResponseSuccess, + Some(expected_reply.as_bytes()), + ); + let message: CSMessage = reply_message.into(); + let expected_hex = hex::encode(message.as_bytes()); + + let result = call_execute_call_message(&mut ctx, 1, data); + println!("{result:?}"); + assert!(result.is_ok()); + let result = result.unwrap(); + let event = get_event(&result, "wasm-write_acknowledgement").unwrap(); + println!("{event:?}"); + assert_eq!(&expected_hex, event.get("data").unwrap()); +} diff --git a/contracts/cosmwasm-vm/cw-mock-dapp-multi/Cargo.toml b/contracts/cosmwasm-vm/cw-mock-dapp-multi/Cargo.toml index e26c80c2..624e8888 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp-multi/Cargo.toml +++ b/contracts/cosmwasm-vm/cw-mock-dapp-multi/Cargo.toml @@ -28,18 +28,19 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-schema = {workspace=true} -cosmwasm-std = { workspace=true} -#cosmwasm-storage = {workspace=true} +cosmwasm-std = { workspace=true, features = ["iterator"] } +cosmwasm-storage = {workspace=true} cw-storage-plus = {workspace=true} cw2 = {workspace=true} schemars = {workspace=true} serde = { workspace=true} thiserror = { workspace=true} -common ={ workspace=true } -cw-xcall-lib = { path="../cw-xcall-lib" } +common ={ git = "https://github.com/icon-project/IBC-Integration.git",branch="main" } +cw-xcall-lib = { workspace=true} serde-json-wasm = {workspace=true} [dev-dependencies] cosmwasm = "0.7.2" +cw-multi-test = "0.16.2" getrandom = {version = "0.2", default-features = false, features = ["custom"]} diff --git a/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/contract.rs b/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/contract.rs index 72741c04..5c02dbf2 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/contract.rs +++ b/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/contract.rs @@ -1,5 +1,12 @@ use std::str::from_utf8; +use cosmwasm_std::SubMsg; +use cw_xcall_lib::message::call_message_persisted::CallMessagePersisted; +use cw_xcall_lib::message::msg_type::MessageType; +use cw_xcall_lib::message::AnyMessage; +use cw_xcall_lib::message::{ + call_message::CallMessage, call_message_rollback::CallMessageWithRollback, envelope::Envelope, +}; use cw_xcall_lib::{network_address::NetworkAddress, xcall_msg::ExecuteMsg}; use super::*; @@ -31,7 +38,7 @@ impl<'a> CwMockService<'a> { to: NetworkAddress, data: Vec, rollback: Option>, - ) -> Result { + ) -> Result { let _sequence = self.increment_sequence(deps.storage)?; let address = self .xcall_address() @@ -60,6 +67,96 @@ impl<'a> CwMockService<'a> { msg: to_binary(&msg).unwrap(), funds: info.funds, }); + let submessage = SubMsg { + id: 1, + msg: message, + gas_limit: None, + reply_on: cosmwasm_std::ReplyOn::Never, + }; + + println!("{:?}", submessage); + + Ok(submessage) + } + + pub fn send_new_call_message( + &self, + deps: DepsMut, + info: MessageInfo, + to: NetworkAddress, + data: Vec, + message_type: u64, + rollback: Option>, + ) -> Result { + let _sequence = self.increment_sequence(deps.storage)?; + let address = self + .xcall_address() + .load(deps.storage) + .map_err(|_e| ContractError::ModuleAddressNotFound)?; + + let network_id = to.nid().to_string(); + let connections = self.get_connections(deps.storage, network_id)?; + let (sources, destinations) = + connections + .into_iter() + .fold((Vec::new(), Vec::new()), |mut acc, x| { + acc.0.push(x.src_endpoint); + acc.1.push(x.dest_endpoint); + acc + }); + + let msg = if message_type == MessageType::CallMessagePersisted as u64 { + AnyMessage::CallMessagePersisted(CallMessagePersisted { data }) + } else if message_type == MessageType::CallMessageWithRollback as u64 { + if let Some(rollback) = rollback { + AnyMessage::CallMessageWithRollback(CallMessageWithRollback { data, rollback }) + } else { + return Err(ContractError::InvalidRollbackMessage); + } + } else if message_type == MessageType::CallMessage as u64 { + AnyMessage::CallMessage(CallMessage { data }) + } else { + return Err(ContractError::InvalidMessageType); + }; + let envelope = Envelope::new(msg, sources, destinations); + + let msg = ExecuteMsg::SendCall { envelope, to }; + let message: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: address, + msg: to_binary(&msg).unwrap(), + funds: info.funds, + }); + + let submessage = SubMsg { + id: 1, + msg: message, + gas_limit: None, + reply_on: cosmwasm_std::ReplyOn::Never, + }; + + Ok(Response::new() + .add_attribute("Action", "SendNewMessage") + .add_submessage(submessage)) + } + + pub fn send_call( + &self, + deps: DepsMut, + info: MessageInfo, + to: NetworkAddress, + envelope: Envelope, + ) -> Result { + let address = self + .xcall_address() + .load(deps.storage) + .map_err(|_e| ContractError::ModuleAddressNotFound)?; + + let msg = ExecuteMsg::SendCall { to, envelope }; + let message: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: address, + msg: to_binary(&msg).unwrap(), + funds: info.funds, + }); println!("{:?}", message); @@ -70,6 +167,7 @@ impl<'a> CwMockService<'a> { pub fn handle_call_message( &self, + deps: DepsMut, info: MessageInfo, from: NetworkAddress, data: Vec, @@ -80,13 +178,20 @@ impl<'a> CwMockService<'a> { .add_attribute("action", "RollbackDataReceived") .add_attribute("from", from.to_string())) } else { + let mut res = Response::new(); let msg_data = from_utf8(&data).map_err(|e| ContractError::DecodeError { error: e.to_string(), })?; if "rollback" == msg_data { return Err(ContractError::RevertFromDAPP); } - Ok(Response::new() + if "reply-response" == msg_data { + let submsg = self + .send_call_message(deps, info, from.clone(), vec![1, 2, 3], None) + .unwrap(); + res = res.add_submessage(submsg) + } + Ok(res .add_attribute("from", from.to_string()) .add_attribute("data", msg_data)) } diff --git a/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/errors.rs b/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/errors.rs index c6e4eb97..a22aa68a 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/errors.rs +++ b/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/errors.rs @@ -20,4 +20,8 @@ pub enum ContractError { ConnectionNotFound { network_id: String }, #[error("Invalid Address {address}")] InvalidAddress { address: String }, + #[error("Invalid Rollback Message")] + InvalidRollbackMessage, + #[error("Invalid Message Type")] + InvalidMessageType, } diff --git a/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/lib.rs b/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/lib.rs index 49b3fbb8..bd7d5983 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/lib.rs +++ b/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/lib.rs @@ -44,13 +44,25 @@ pub fn execute( let call_service = CwMockService::default(); match msg { ExecuteMsg::SendCallMessage { to, data, rollback } => { - call_service.send_call_message(deps, info, to, data, rollback) + let submsg = call_service.send_call_message(deps, info, to, data, rollback)?; + Ok(Response::new() + .add_submessage(submsg) + .add_attribute("Action", "SendMessage")) + } + ExecuteMsg::SendNewCallMessage { + to, + data, + message_type, + rollback, + } => call_service.send_new_call_message(deps, info, to, data, message_type, rollback), + ExecuteMsg::SendMessageAny { to, envelope } => { + call_service.send_call(deps, info, to, envelope) } ExecuteMsg::HandleCallMessage { from, data, protocols, - } => call_service.handle_call_message(info, from, data, protocols), + } => call_service.handle_call_message(deps, info, from, data, protocols), ExecuteMsg::AddConnection { src_endpoint, dest_endpoint, diff --git a/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/msg.rs b/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/msg.rs index 17558db7..2a8d94f6 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/msg.rs +++ b/contracts/cosmwasm-vm/cw-mock-dapp-multi/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cw_xcall_lib::network_address::NetworkAddress; +use cw_xcall_lib::{message::envelope::Envelope, network_address::NetworkAddress}; #[cw_serde] pub enum ExecuteMsg { @@ -8,6 +8,16 @@ pub enum ExecuteMsg { data: Vec, rollback: Option>, }, + SendMessageAny { + to: NetworkAddress, + envelope: Envelope, + }, + SendNewCallMessage { + to: NetworkAddress, + data: Vec, + message_type: u64, + rollback: Option>, + }, HandleCallMessage { from: NetworkAddress, data: Vec, diff --git a/contracts/cosmwasm-vm/cw-mock-dapp-multi/tests/mock_test.rs b/contracts/cosmwasm-vm/cw-mock-dapp-multi/tests/mock_test.rs index 5d423d09..6933dde4 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp-multi/tests/mock_test.rs +++ b/contracts/cosmwasm-vm/cw-mock-dapp-multi/tests/mock_test.rs @@ -71,7 +71,7 @@ fn test_send_message() { ); assert!(res.is_ok()); - assert_eq!(res.unwrap().messages[0].id, 0) + assert_eq!(res.unwrap().id, 1) } #[test] @@ -107,6 +107,7 @@ fn test_handle_message() { ctx.instantiate(deps.as_mut(), env, info.clone(), msg) .unwrap(); let res = ctx.handle_call_message( + deps.as_mut(), info, NetworkAddress::from_str("netid/xcall").unwrap(), "helloError".as_bytes().to_vec(), @@ -131,6 +132,7 @@ fn test_handle_message_fail_revert() { ctx.instantiate(deps.as_mut(), env, info.clone(), msg) .unwrap(); ctx.handle_call_message( + deps.as_mut(), info, NetworkAddress::from_str("netid/xcall").unwrap(), "rollback".as_bytes().to_vec(), @@ -159,6 +161,7 @@ fn test_handle_message_pass_true() { rollback: vec![1, 2, 3], }; let res = ctx.handle_call_message( + deps.as_mut(), info, NetworkAddress::from_str("netid/hugobyte").unwrap(), to_vec(&rollback_data).unwrap(), diff --git a/contracts/cosmwasm-vm/cw-mock-dapp/Cargo.toml b/contracts/cosmwasm-vm/cw-mock-dapp/Cargo.toml index b3fd3137..e80fcc03 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp/Cargo.toml +++ b/contracts/cosmwasm-vm/cw-mock-dapp/Cargo.toml @@ -28,17 +28,19 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-schema = {workspace=true} -cosmwasm-std = { workspace=true} +cosmwasm-std = { workspace=true, features = ["iterator"] } +cosmwasm-storage = {workspace=true} cw-storage-plus = {workspace=true} cw2 = {workspace=true} schemars = {workspace=true} serde = { workspace=true} thiserror = { workspace=true} -common ={ workspace=true} -cw-xcall-lib = { path="../cw-xcall-lib" } +common ={ git = "https://github.com/icon-project/IBC-Integration.git",branch="main" } +cw-xcall-lib = { workspace=true } serde-json-wasm = {workspace=true} [dev-dependencies] cosmwasm = "0.7.2" +cw-multi-test = "0.16.2" getrandom = {version = "0.2", default-features = false, features = ["custom"]} diff --git a/contracts/cosmwasm-vm/cw-mock-dapp/src/contract.rs b/contracts/cosmwasm-vm/cw-mock-dapp/src/contract.rs index 2a902544..6be19f67 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp/src/contract.rs +++ b/contracts/cosmwasm-vm/cw-mock-dapp/src/contract.rs @@ -1,8 +1,12 @@ use std::str::from_utf8; -use cw_xcall_lib::{network_address::NetworkAddress, xcall_msg::ExecuteMsg}; - use super::*; +use cw_xcall_lib::message::call_message_persisted::CallMessagePersisted; +use cw_xcall_lib::message::AnyMessage; +use cw_xcall_lib::message::{ + call_message::CallMessage, call_message_rollback::CallMessageWithRollback, envelope::Envelope, +}; +use cw_xcall_lib::{network_address::NetworkAddress, xcall_msg::ExecuteMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw-mock-dapp"; @@ -58,6 +62,44 @@ impl<'a> CwMockService<'a> { .add_message(message)) } + pub fn send_new_call_message( + &self, + deps: DepsMut, + info: MessageInfo, + to: NetworkAddress, + data: Vec, + rollback: Option>, + is_persistent: bool, + ) -> Result { + let _sequence = self.increment_sequence(deps.storage)?; + let address = self + .xcall_address() + .load(deps.storage) + .map_err(|_e| ContractError::ModuleAddressNotFound)?; + + let msg = if is_persistent { + AnyMessage::CallMessagePersisted(CallMessagePersisted { data }) + } else if let Some(rollback) = rollback { + AnyMessage::CallMessageWithRollback(CallMessageWithRollback { data, rollback }) + } else { + AnyMessage::CallMessage(CallMessage { data }) + }; + let envelope = Envelope::new(msg, vec![], vec![]); + + let msg = ExecuteMsg::SendCall { envelope, to }; + let message: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: address, + msg: to_binary(&msg).unwrap(), + funds: info.funds, + }); + + println!("{:?}", message); + + Ok(Response::new() + .add_attribute("Action", "SendNewMessage") + .add_message(message)) + } + pub fn handle_call_message( &self, deps: DepsMut, diff --git a/contracts/cosmwasm-vm/cw-mock-dapp/src/lib.rs b/contracts/cosmwasm-vm/cw-mock-dapp/src/lib.rs index 636bf851..ede6cb50 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp/src/lib.rs +++ b/contracts/cosmwasm-vm/cw-mock-dapp/src/lib.rs @@ -46,6 +46,12 @@ pub fn execute( ExecuteMsg::SendCallMessage { to, data, rollback } => { call_service.send_call_message(deps, info, to, data, rollback) } + ExecuteMsg::SendNewCallMessage { + to, + data, + rollback, + is_persistent, + } => call_service.send_new_call_message(deps, info, to, data, rollback, is_persistent), ExecuteMsg::HandleCallMessage { from, data } => { call_service.handle_call_message(deps, info, from, data) } diff --git a/contracts/cosmwasm-vm/cw-mock-dapp/src/msg.rs b/contracts/cosmwasm-vm/cw-mock-dapp/src/msg.rs index 1e804231..8662db3f 100644 --- a/contracts/cosmwasm-vm/cw-mock-dapp/src/msg.rs +++ b/contracts/cosmwasm-vm/cw-mock-dapp/src/msg.rs @@ -8,6 +8,12 @@ pub enum ExecuteMsg { data: Vec, rollback: Option>, }, + SendNewCallMessage { + to: NetworkAddress, + data: Vec, + rollback: Option>, + is_persistent: bool, + }, HandleCallMessage { from: NetworkAddress, data: Vec, diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/Cargo.toml b/contracts/cosmwasm-vm/cw-xcall-lib/Cargo.toml index 270152e8..63829f72 100644 --- a/contracts/cosmwasm-vm/cw-xcall-lib/Cargo.toml +++ b/contracts/cosmwasm-vm/cw-xcall-lib/Cargo.toml @@ -29,13 +29,14 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-schema = {workspace=true} cosmwasm-std = {workspace=true} +cosmwasm-storage = {workspace=true} cw-storage-plus = {workspace=true} cw2 = {workspace=true} schemars = {workspace=true} serde = { workspace=true} thiserror = { workspace=true} debug_print={workspace=true} - +common = { workspace=true } [dev-dependencies] @@ -43,4 +44,4 @@ cosmwasm = "0.7.2" getrandom = {version = "0.2", default-features = false, features = ["custom"]} hex = "0.4.3" anyhow="*" -test-utils={ git = "https://github.com/icon-project/IBC-Integration.git",branch="feat/cw-wasm-light-client",default-features = false } \ No newline at end of file +test-utils={ git = "https://github.com/icon-project/IBC-Integration.git",branch="main" } \ No newline at end of file diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/lib.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/lib.rs index ae13fdb2..d32be794 100644 --- a/contracts/cosmwasm-vm/cw-xcall-lib/src/lib.rs +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/lib.rs @@ -1,5 +1,6 @@ pub mod dapp_msg; pub mod dapp_multi_msg; +pub mod message; pub mod network_address; pub mod xcall_connection_msg; pub mod xcall_msg; diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message.rs new file mode 100644 index 00000000..b4d79f9c --- /dev/null +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message.rs @@ -0,0 +1,37 @@ +use common::rlp::{self, Decodable, DecoderError, Encodable, RlpStream}; +use cosmwasm_schema::cw_serde; + +use super::msg_trait::IMessage; + +#[cw_serde] +pub struct CallMessage { + pub data: Vec, +} + +impl Encodable for CallMessage { + fn rlp_append(&self, stream: &mut RlpStream) { + stream.begin_list(1).append(&self.data); + } +} + +impl Decodable for CallMessage { + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Self { + data: rlp.val_at(0)?, + }) + } +} + +impl IMessage for CallMessage { + fn rollback(&self) -> Option> { + None + } + + fn data(&self) -> Vec { + self.data.clone() + } + + fn to_bytes(&self) -> Result, DecoderError> { + Ok(rlp::encode(self).to_vec()) + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_persisted.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_persisted.rs new file mode 100644 index 00000000..8bdbfca6 --- /dev/null +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_persisted.rs @@ -0,0 +1,36 @@ +use super::msg_trait::IMessage; +use common::rlp::{self, Decodable, DecoderError, Encodable, RlpStream}; +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct CallMessagePersisted { + pub data: Vec, +} + +impl Encodable for CallMessagePersisted { + fn rlp_append(&self, stream: &mut RlpStream) { + stream.begin_list(1).append(&self.data); + } +} + +impl Decodable for CallMessagePersisted { + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Self { + data: rlp.val_at(0)?, + }) + } +} + +impl IMessage for CallMessagePersisted { + fn rollback(&self) -> Option> { + None + } + + fn data(&self) -> Vec { + self.data.clone() + } + + fn to_bytes(&self) -> Result, DecoderError> { + Ok(rlp::encode(self).to_vec()) + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_rollback.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_rollback.rs new file mode 100644 index 00000000..26764b47 --- /dev/null +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/call_message_rollback.rs @@ -0,0 +1,42 @@ +use common::rlp::{self, Decodable, DecoderError, Encodable, RlpStream}; +use cosmwasm_schema::cw_serde; + +use super::msg_trait::IMessage; + +#[cw_serde] +pub struct CallMessageWithRollback { + pub data: Vec, + pub rollback: Vec, +} + +impl Encodable for CallMessageWithRollback { + fn rlp_append(&self, stream: &mut RlpStream) { + stream + .begin_list(2) + .append(&self.data) + .append(&self.rollback); + } +} + +impl Decodable for CallMessageWithRollback { + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Self { + data: rlp.val_at(0)?, + rollback: rlp.val_at(1)?, + }) + } +} + +impl IMessage for CallMessageWithRollback { + fn rollback(&self) -> Option> { + Some(self.rollback.clone()) + } + + fn data(&self) -> Vec { + self.data.clone() + } + + fn to_bytes(&self) -> Result, DecoderError> { + Ok(rlp::encode(self).to_vec()) + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/envelope.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/envelope.rs new file mode 100644 index 00000000..f533714a --- /dev/null +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/envelope.rs @@ -0,0 +1,123 @@ +use common::rlp::{self, Decodable, DecoderError, Encodable}; +use cosmwasm_schema::cw_serde; + +use super::{ + call_message::CallMessage, call_message_persisted::CallMessagePersisted, + call_message_rollback::CallMessageWithRollback, msg_trait::IMessage, msg_type::MessageType, + AnyMessage, +}; +#[cw_serde] +pub struct Envelope { + pub message: AnyMessage, + pub sources: Vec, + pub destinations: Vec, +} + +impl Envelope { + pub fn new(msg: AnyMessage, sources: Vec, destinations: Vec) -> Self { + Self { + message: msg, + sources, + destinations, + } + } +} + +impl Encodable for Envelope { + fn rlp_append(&self, stream: &mut common::rlp::RlpStream) { + stream.begin_list(4); + stream.append(&Into::::into(self.message.msg_type().clone())); + stream.append(&self.message.to_bytes().unwrap()); + stream.begin_list(self.sources.len()); + for source in self.sources.iter() { + stream.append(source); + } + stream.begin_list(self.destinations.len()); + for dest in self.destinations.iter() { + stream.append(dest); + } + } +} + +impl Decodable for Envelope { + fn decode(rlp: &rlp::Rlp) -> Result { + let msg_int: u8 = rlp.val_at(0)?; + let msg_type = MessageType::from(msg_int); + let message_bytes: Vec = rlp.val_at(1)?; + let message = decode_message(msg_type, message_bytes)?; + + let sources = rlp.at(2)?; + let sources: Vec = sources.as_list()?; + let destinations = rlp.at(3)?; + let destinations: Vec = destinations.as_list()?; + + Ok(Envelope { + message, + sources, + destinations, + }) + } +} + +pub fn decode_message(msg_type: MessageType, bytes: Vec) -> Result { + match msg_type { + MessageType::CallMessage => { + let msg: CallMessage = rlp::decode(&bytes)?; + Ok(AnyMessage::CallMessage(msg)) + } + MessageType::CallMessageWithRollback => { + let msg: CallMessageWithRollback = rlp::decode(&bytes)?; + Ok(AnyMessage::CallMessageWithRollback(msg)) + } + MessageType::CallMessagePersisted => { + let msg: CallMessagePersisted = rlp::decode(&bytes)?; + Ok(AnyMessage::CallMessagePersisted(msg)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encoding_decoding_envelope_call_message() { + // Create a sample Envelope + let message = AnyMessage::CallMessage(CallMessage { + data: vec![1, 2, 3], + }); + let sources = vec!["source1".to_string(), "source2".to_string()]; + let destinations = vec!["dest1".to_string(), "dest2".to_string()]; + let envelope = Envelope::new(message, sources, destinations); + let encoded_data = rlp::encode(&envelope).to_vec(); + + assert_eq!( + "e50085c483010203d087736f757263653187736f7572636532cc856465737431856465737432", + hex::encode(&encoded_data) + ); + let decoded: Envelope = rlp::decode(&encoded_data).unwrap(); + + assert_eq!(envelope, decoded); + } + + #[test] + fn test_encoding_decoding_envelope_call_message_rollback() { + // Create a sample Envelope + let message = AnyMessage::CallMessageWithRollback(CallMessageWithRollback { + data: vec![1, 2, 3], + rollback: vec![1, 2, 3], + }); + let sources = vec!["source1".to_string(), "source2".to_string()]; + let destinations = vec!["dest1".to_string(), "dest2".to_string()]; + let envelope = Envelope::new(message, sources, destinations); + let encoded_data = rlp::encode(&envelope).to_vec(); + + assert_eq!( + "e90189c88301020383010203d087736f757263653187736f7572636532cc856465737431856465737432", + hex::encode(&encoded_data) + ); + let decoded: Envelope = rlp::decode(&encoded_data).unwrap(); + + assert_eq!(envelope, decoded); + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/mod.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/mod.rs new file mode 100644 index 00000000..e74bbfe0 --- /dev/null +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/mod.rs @@ -0,0 +1,56 @@ +use common::rlp::DecoderError; +use cosmwasm_schema::cw_serde; + +use self::{ + call_message::CallMessage, call_message_persisted::CallMessagePersisted, + call_message_rollback::CallMessageWithRollback, msg_trait::IMessage, msg_type::MessageType, +}; + +pub mod call_message; +pub mod call_message_persisted; +pub mod call_message_rollback; +pub mod envelope; +pub mod msg_trait; +pub mod msg_type; +#[cw_serde] +pub enum AnyMessage { + CallMessage(CallMessage), + CallMessageWithRollback(CallMessageWithRollback), + CallMessagePersisted(CallMessagePersisted), +} + +impl IMessage for AnyMessage { + fn rollback(&self) -> Option> { + match self { + AnyMessage::CallMessage(m) => m.rollback(), + AnyMessage::CallMessageWithRollback(m) => m.rollback(), + AnyMessage::CallMessagePersisted(m) => m.rollback(), + } + } + + fn data(&self) -> Vec { + match self { + AnyMessage::CallMessage(m) => m.data(), + AnyMessage::CallMessageWithRollback(m) => m.data(), + AnyMessage::CallMessagePersisted(m) => m.data(), + } + } + + fn to_bytes(&self) -> Result, DecoderError> { + match self { + AnyMessage::CallMessage(m) => m.to_bytes(), + AnyMessage::CallMessageWithRollback(m) => m.to_bytes(), + AnyMessage::CallMessagePersisted(m) => m.to_bytes(), + } + } +} + +impl AnyMessage { + pub fn msg_type(&self) -> &MessageType { + match self { + AnyMessage::CallMessage(_m) => &MessageType::CallMessage, + AnyMessage::CallMessageWithRollback(_m) => &MessageType::CallMessageWithRollback, + AnyMessage::CallMessagePersisted(_m) => &MessageType::CallMessagePersisted, + } + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_trait.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_trait.rs new file mode 100644 index 00000000..5f89edc0 --- /dev/null +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_trait.rs @@ -0,0 +1,8 @@ +use common::rlp::DecoderError; + +pub trait IMessage: Clone { + fn rollback(&self) -> Option>; + fn data(&self) -> Vec; + + fn to_bytes(&self) -> Result, DecoderError>; +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_type.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_type.rs new file mode 100644 index 00000000..2e9b8ac4 --- /dev/null +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message/msg_type.rs @@ -0,0 +1,38 @@ +use serde::Serialize; + +#[derive(Clone, Debug, Serialize, serde::Deserialize, PartialEq, Eq)] +pub enum MessageType { + CallMessage = 0, + CallMessageWithRollback = 1, + CallMessagePersisted = 2, +} + +impl From for u8 { + fn from(val: MessageType) -> Self { + match val { + MessageType::CallMessage => 0, + MessageType::CallMessageWithRollback => 1, + MessageType::CallMessagePersisted => 2, + } + } +} + +impl From for MessageType { + fn from(value: u8) -> Self { + match value { + 0 => MessageType::CallMessage, + 1 => MessageType::CallMessageWithRollback, + 2 => MessageType::CallMessagePersisted, + _ => panic!("unsupported message type"), + } + } +} + +impl MessageType { + pub fn as_int(&self) -> u8 { + self.clone().into() + } + pub fn from_int(val: u8) -> Self { + MessageType::from(val) + } +} diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/message_types.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/message_types.rs new file mode 100644 index 00000000..d91a78d8 --- /dev/null +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/message_types.rs @@ -0,0 +1,17 @@ +use common::rlp::Encodable; +use common::rlp::Decodable; + + +pub struct Message { + msg_type:MessageType, + data:Vec, +} + + +pub struct MessageWithRollback{ + msg_type:MessageType, + data:Vec, + rollback:Vec, +} + + diff --git a/contracts/cosmwasm-vm/cw-xcall-lib/src/xcall_msg.rs b/contracts/cosmwasm-vm/cw-xcall-lib/src/xcall_msg.rs index 6a7162f4..b8a80665 100644 --- a/contracts/cosmwasm-vm/cw-xcall-lib/src/xcall_msg.rs +++ b/contracts/cosmwasm-vm/cw-xcall-lib/src/xcall_msg.rs @@ -1,7 +1,10 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; -use crate::network_address::{NetId, NetworkAddress}; +use crate::{ + message::envelope::Envelope, + network_address::{NetId, NetworkAddress}, +}; #[cw_serde] pub enum ExecuteMsg { @@ -22,6 +25,10 @@ pub enum ExecuteMsg { sources: Option>, destinations: Option>, }, + SendCall { + envelope: Envelope, + to: NetworkAddress, + }, HandleMessage { from_nid: NetId, msg: Vec, diff --git a/contracts/cosmwasm-vm/cw-xcall/Cargo.toml b/contracts/cosmwasm-vm/cw-xcall/Cargo.toml index f146360f..4c234ac9 100644 --- a/contracts/cosmwasm-vm/cw-xcall/Cargo.toml +++ b/contracts/cosmwasm-vm/cw-xcall/Cargo.toml @@ -30,14 +30,15 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-schema = {workspace=true} cosmwasm-std = {workspace=true} +cosmwasm-storage = {workspace=true} cw-storage-plus = {workspace=true} cw2 = {workspace=true} schemars = {workspace=true} serde = { workspace=true} thiserror = { workspace=true} -common = {workspace=true} +common = { git = "https://github.com/icon-project/IBC-Integration.git",branch="main" } -cw-xcall-lib = { path="../cw-xcall-lib" } +cw-xcall-lib = { workspace=true } debug_print={workspace=true} @@ -47,4 +48,4 @@ cosmwasm = "0.7.2" getrandom = {version = "0.2", default-features = false, features = ["custom"]} hex = "0.4.3" anyhow="*" -test-utils={ git = "https://github.com/icon-project/IBC-Integration.git",branch="feat/cw-wasm-light-client",default-features = false } \ No newline at end of file +test-utils={ git = "https://github.com/icon-project/IBC-Integration.git",branch="main" } \ No newline at end of file diff --git a/contracts/cosmwasm-vm/cw-xcall/src/assertion.rs b/contracts/cosmwasm-vm/cw-xcall/src/assertion.rs index 2e608cde..bdeaadf2 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/assertion.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/assertion.rs @@ -3,7 +3,7 @@ use super::*; use crate::{ error::ContractError, state::{CwCallService, MAX_DATA_SIZE, MAX_ROLLBACK_SIZE}, - types::{call_request::CallRequest, request::CSMessageRequest}, + types::{request::CSMessageRequest, rollback::Rollback}, }; impl<'a> CwCallService<'a> { @@ -128,7 +128,7 @@ impl<'a> CwCallService<'a> { pub fn ensure_call_request_not_null( &self, sequence_no: u128, - message: &CallRequest, + message: &Rollback, ) -> Result<(), ContractError> { let data = to_binary(message).unwrap(); ensure!( @@ -200,6 +200,6 @@ impl<'a> CwCallService<'a> { /// `querier` object to get information about the contract at the given `address`. If the query is /// successful, it returns `true`, indicating that the address is a valid contract. If the query fails, /// it returns false ` -fn is_contract(querier: QuerierWrapper, address: &Addr) -> bool { +pub fn is_contract(querier: QuerierWrapper, address: &Addr) -> bool { querier.query_wasm_contract_info(address).is_ok() } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/contract.rs b/contracts/cosmwasm-vm/cw-xcall/src/contract.rs index 9213e116..b41239bb 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/contract.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/contract.rs @@ -94,6 +94,7 @@ impl<'a> CwCallService<'a> { let dests = destinations.unwrap_or(vec![]); self.send_call_message(deps, info, env, to, data, rollback, sources, dests) } + ExecuteMsg::SendCall { envelope, to } => self.send_call(deps, info, to, envelope), ExecuteMsg::HandleMessage { msg, from_nid } => { self.handle_message(deps, info, from_nid, msg) } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/error.rs b/contracts/cosmwasm-vm/cw-xcall/src/error.rs index 5f7f8b7d..ec96689a 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/error.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/error.rs @@ -42,4 +42,10 @@ pub enum ContractError { DataMismatch, #[error("CallAlreadyInProgress")] CallAlreadyInProgress, + #[error("MessageTypeNotAllowed")] + MessageTypeNotAllowed, + #[error("InvalidReplyReceived")] + InvalidReplyReceived, + #[error("CallRequest Not Found For {sn}")] + CallRequestNotFound { sn: u128 }, } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/execute_call.rs b/contracts/cosmwasm-vm/cw-xcall/src/execute_call.rs index 41536e64..1d5ead63 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/execute_call.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/execute_call.rs @@ -1,4 +1,4 @@ -use common::utils::keccak256; +use common::{rlp, utils::keccak256}; use cosmwasm_std::{DepsMut, Env, MessageInfo, Reply, Response, SubMsg}; use crate::{ @@ -7,7 +7,7 @@ use crate::{ state::{CwCallService, EXECUTE_CALL_ID}, types::{ message::CSMessage, - response::{CSMessageResponse, CallServiceResponseType}, + result::{CSMessageResult, CallServiceResponseType}, }, }; @@ -76,30 +76,41 @@ impl<'a> CwCallService<'a> { self.remove_execute_request_id(deps.storage); let request = self.get_proxy_request(deps.storage, req_id)?; - self.remove_proxy_request(deps.storage, req_id); let (response, event) = match msg.result { cosmwasm_std::SubMsgResult::Ok(_res) => { let code = CallServiceResponseType::CallServiceResponseSuccess.into(); - - let message_response = CSMessageResponse::new( + let reply = self + .pop_call_reply(deps.storage) + .map(|msg| rlp::encode(&msg).to_vec()); + let message_response = CSMessageResult::new( request.sequence_no(), CallServiceResponseType::CallServiceResponseSuccess, + reply, ); + let event = event_call_executed(req_id, code, "success"); + self.remove_proxy_request(deps.storage, req_id); (message_response, event) } cosmwasm_std::SubMsgResult::Err(err) => { let code = CallServiceResponseType::CallServiceResponseFailure; let error_message = format!("CallService Reverted : {err}"); - let message_response = CSMessageResponse::new(request.sequence_no(), code.clone()); + let message_response = + CSMessageResult::new(request.sequence_no(), code.clone(), None); let event = event_call_executed(req_id, code.into(), &error_message); + if request.allow_retry() { + return Err(ContractError::ReplyError { + code: msg.id, + msg: err, + }); + } (message_response, event) } }; let mut submsgs: Vec = vec![]; let sn: i64 = -(request.sequence_no() as i64); - if request.rollback() { + if request.need_response() { let message: CSMessage = response.into(); let mut reply_address = request.protocols().clone(); let from = request.from().clone(); diff --git a/contracts/cosmwasm-vm/cw-xcall/src/fees.rs b/contracts/cosmwasm-vm/cw-xcall/src/fees.rs index 29dadd55..bdad658e 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/fees.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/fees.rs @@ -45,6 +45,10 @@ impl<'a> CwCallService<'a> { rollback: bool, sources: Vec, ) -> Result { + if !rollback && self.is_reply(deps, nid.clone(), &sources) { + return Ok(0_u128); + } + let protocol_fee = self.get_protocol_fee(deps.storage); let mut sources = sources; if sources.is_empty() { diff --git a/contracts/cosmwasm-vm/cw-xcall/src/handle_call_message.rs b/contracts/cosmwasm-vm/cw-xcall/src/handle_call_message.rs index ade3e6f3..80cb2a8e 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/handle_call_message.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/handle_call_message.rs @@ -18,15 +18,47 @@ impl<'a> CwCallService<'a> { let call_service_message: CSMessage = CSMessage::try_from(message)?; match call_service_message.message_type() { - CallServiceMessageType::CallServiceRequest => { + CSMessageType::CSMessageRequest => { self.handle_request(deps, info, from_nid, call_service_message.payload()) } - CallServiceMessageType::CallServiceResponse => { - self.handle_response(deps, info, call_service_message.payload()) + CSMessageType::CSMessageResult => { + self.handle_result(deps, info, call_service_message.payload()) } } } + pub fn handle_reply( + &self, + deps: DepsMut, + rollback: Rollback, + request: CSMessageRequest, + ) -> Result { + // reply can be targeted to source nid but any contract + if !(rollback.to().nid() == request.from().nid()) { + return Err(ContractError::InvalidReplyReceived); + } + let request_id = self.increment_last_request_id(deps.storage)?; + + let req = CSMessageRequest::new( + request.from().clone(), + request.to().clone(), + request.sequence_no(), + request.msg_type(), + keccak256(request.data().unwrap()).to_vec(), + rollback.protocols().clone(), + ); + self.store_proxy_request(deps.storage, request_id, &req)?; + + let event = event_call_message( + request.from().to_string(), + request.to().to_string(), + request.sequence_no(), + request_id, + request.data().unwrap().to_vec(), + ); + Ok(event) + } + pub fn handle_request( &self, deps: DepsMut, @@ -68,7 +100,7 @@ impl<'a> CwCallService<'a> { request.from().clone(), request.to().clone(), request.sequence_no(), - request.rollback(), + request.msg_type(), keccak256(request.data().unwrap()).to_vec(), request.protocols().clone(), ); @@ -88,21 +120,21 @@ impl<'a> CwCallService<'a> { .add_event(event)) } - pub fn handle_response( + pub fn handle_result( &self, deps: DepsMut, info: MessageInfo, data: &[u8], ) -> Result { - let message: CSMessageResponse = rlp::decode(data).unwrap(); - - let response_sequence_no = message.sequence_no(); + let result: CSMessageResult = rlp::decode(data).unwrap(); - let mut call_request = self.get_call_request(deps.storage, response_sequence_no)?; + let response_sequence_no = result.sequence_no(); - if call_request.is_null() { - return Ok(Response::new()); - } + let mut call_request = self + .get_call_request(deps.storage, response_sequence_no) + .map_err(|_e| ContractError::CallRequestNotFound { + sn: response_sequence_no, + })?; let source = info.sender.to_string(); let source_valid = self.is_valid_source( @@ -130,17 +162,24 @@ impl<'a> CwCallService<'a> { } let response_event = event_response_message( response_sequence_no, - (message.response_code().clone()).into(), + (result.response_code().clone()).into(), ); - match message.response_code() { + match result.response_code() { CallServiceResponseType::CallServiceResponseSuccess => { self.cleanup_request(deps.storage, response_sequence_no); self.set_successful_response(deps.storage, response_sequence_no)?; - Ok(Response::new() + let mut res = Response::new() .add_attribute("action", "call_service") .add_attribute("method", "handle_response") - .add_event(response_event)) + .add_event(response_event); + if result.get_message().is_some() { + let reply = result.get_message().unwrap(); + let event = self.handle_reply(deps, call_request, reply)?; + res = res.add_event(event); + } + + Ok(res) } _ => { self.ensure_rollback_length(call_request.rollback()) @@ -194,7 +233,11 @@ impl<'a> CwCallService<'a> { info: MessageInfo, sn: u128, ) -> Result { - let msg = CSMessageResponse::new(sn, CallServiceResponseType::CallServiceResponseFailure); - self.handle_response(deps, info, &rlp::encode(&msg)) + let msg = CSMessageResult::new( + sn, + CallServiceResponseType::CallServiceResponseFailure, + None, + ); + self.handle_result(deps, info, &rlp::encode(&msg)) } } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/lib.rs b/contracts/cosmwasm-vm/cw-xcall/src/lib.rs index 457df968..7a7deb44 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/lib.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/lib.rs @@ -25,10 +25,10 @@ use crate::{ msg::{InstantiateMsg, QueryMsg}, state::{CwCallService, EXECUTE_CALL_ID}, types::{ - call_request::CallRequest, - message::{CSMessage, CallServiceMessageType}, + message::{CSMessage, CSMessageType}, request::CSMessageRequest, - response::{CSMessageResponse, CallServiceResponseType}, + result::{CSMessageResult, CallServiceResponseType}, + rollback::Rollback, storage_keys::StorageKey, }, }; diff --git a/contracts/cosmwasm-vm/cw-xcall/src/send_call_message.rs b/contracts/cosmwasm-vm/cw-xcall/src/send_call_message.rs index 9f888003..cbcb6ad7 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/send_call_message.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/send_call_message.rs @@ -1,7 +1,12 @@ use cosmwasm_std::{coins, BankMsg}; -use cw_xcall_lib::network_address::NetworkAddress; +use cw_xcall_lib::message::call_message::CallMessage; +use cw_xcall_lib::message::msg_trait::IMessage; -use crate::types::LOG_PREFIX; +use cw_xcall_lib::message::AnyMessage; +use cw_xcall_lib::message::{call_message_rollback::CallMessageWithRollback, envelope::Envelope}; +use cw_xcall_lib::network_address::{NetId, NetworkAddress}; + +use crate::{assertion::is_contract, types::LOG_PREFIX}; use super::*; @@ -17,47 +22,90 @@ impl<'a> CwCallService<'a> { sources: Vec, destinations: Vec, ) -> Result { - let caller = info.sender; - let config = self.get_config(deps.as_ref().storage)?; - let nid = config.network_id; - - self.ensure_caller_is_contract_and_rollback_is_null(deps.as_ref(), &caller, &rollback)?; - - let need_response = rollback.is_some(); - - let rollback_data = match rollback { - Some(data) => data, - None => vec![], + let msg = if rollback.is_some() { + AnyMessage::CallMessageWithRollback(CallMessageWithRollback { + data, + rollback: rollback.unwrap(), + }) + } else { + AnyMessage::CallMessage(CallMessage { data }) }; + let envelope = Envelope::new(msg, sources, destinations); + self.send_call(deps, info, to, envelope) + } + + pub fn validate_payload( + &self, + deps: Deps, + caller: &Addr, + envelope: &Envelope, + ) -> Result<(), ContractError> { + match &envelope.message { + AnyMessage::CallMessage(_m) => Ok(()), + AnyMessage::CallMessageWithRollback(m) => { + if !is_contract(deps.querier, caller) { + return Err(ContractError::RollbackNotPossible); + } + self.ensure_rollback_length(&m.rollback().unwrap())?; + Ok(()) + } + AnyMessage::CallMessagePersisted(_) => Ok(()), + } + } - self.ensure_rollback_length(&rollback_data)?; - println!("{LOG_PREFIX} Packet Validated"); + pub fn send_call( + &self, + deps: DepsMut, + info: MessageInfo, + to: NetworkAddress, + envelope: Envelope, + ) -> Result { + let caller = info.sender.clone(); + let config = self.get_config(deps.as_ref().storage)?; + let nid = config.network_id; + self.validate_payload(deps.as_ref(), &caller, &envelope)?; let sequence_no = self.get_next_sn(deps.storage)?; - let mut confirmed_sources = sources.clone(); - let from = NetworkAddress::new(&nid, caller.as_ref()); - if confirmed_sources.is_empty() { - let default = self.get_default_connection(deps.as_ref().storage, to.nid())?; - confirmed_sources = vec![default.to_string()] - } + let from = NetworkAddress::new(&nid, caller.as_ref()); - if need_response { - let request = - CallRequest::new(caller.clone(), to.clone(), sources, rollback_data, false); + if envelope.message.rollback().is_some() { + let rollback_data = envelope.message.rollback().unwrap(); + let request = Rollback::new( + caller.clone(), + to.clone(), + envelope.sources.clone(), + rollback_data, + false, + ); self.store_call_request(deps.storage, sequence_no, &request)?; } - let call_request = CSMessageRequest::new( from, to.account(), sequence_no, - need_response, - data.to_vec(), - destinations, + envelope.message.msg_type().clone(), + envelope.message.data(), + envelope.destinations, ); + let need_response = call_request.need_response(); + let event = event_xcall_message_sent(caller.to_string(), to.to_string(), sequence_no); + // if contract is in reply state + if envelope.message.rollback().is_none() + && self.is_reply(deps.as_ref(), to.nid(), &envelope.sources) + { + self.save_call_reply(deps.storage, &call_request)?; + let res = self.send_call_response(event, sequence_no); + return Ok(res); + } + + let mut confirmed_sources = envelope.sources; + if confirmed_sources.is_empty() { + let default = self.get_default_connection(deps.as_ref().storage, to.nid())?; + confirmed_sources = vec![default.to_string()] + } let message: CSMessage = call_request.into(); let sn: i64 = if need_response { sequence_no as i64 } else { 0 }; let mut total_spent = 0_u128; @@ -91,14 +139,11 @@ impl<'a> CwCallService<'a> { } let remaining = total_paid - total_spent; - let event = event_xcall_message_sent(caller.to_string(), to.to_string(), sequence_no); println!("{LOG_PREFIX} Sent Bank Message"); - let mut res = Response::new() - .add_submessages(submessages) - .add_attribute("action", "xcall-service") - .add_attribute("method", "send_packet") - .add_attribute("sequence_no", sequence_no.to_string()) - .add_event(event); + let mut res = self + .send_call_response(event, sequence_no) + .add_submessages(submessages); + if remaining > 0 { let msg = BankMsg::Send { to_address: fee_handler, @@ -109,4 +154,39 @@ impl<'a> CwCallService<'a> { Ok(res) } + + fn send_call_response(&self, event: Event, sequence_no: u128) -> Response { + Response::new() + .add_attribute("action", "xcall-service") + .add_attribute("method", "send_packet") + .add_attribute("sequence_no", sequence_no.to_string()) + .add_event(event) + } + + pub fn is_reply(&self, deps: Deps, to: NetId, sources: &Vec) -> bool { + if self.get_call_reply(deps.storage).is_some() { + return false; + } + + if let Ok(reqid) = self.get_execute_request_id(deps.storage) { + let request = self.get_proxy_request(deps.storage, reqid).unwrap(); + if request.from().nid() != to { + return false; + } + return self.are_equal(request.protocols(), sources); + } + false + } + + fn are_equal(&self, protocols: &Vec, sources: &Vec) -> bool { + if protocols.len() != sources.len() { + return false; + } + for protocol in protocols.iter() { + if !sources.contains(protocol) { + return false; + } + } + true + } } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/state.rs b/contracts/cosmwasm-vm/cw-xcall/src/state.rs index 06d8f933..4ce9efa0 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/state.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/state.rs @@ -20,7 +20,7 @@ pub struct CwCallService<'a> { last_request_id: Item<'a, u128>, admin: Item<'a, Addr>, proxy_request: Map<'a, u128, CSMessageRequest>, - call_requests: Map<'a, u128, CallRequest>, + call_requests: Map<'a, u128, Rollback>, fee_handler: Item<'a, String>, protocol_fee: Item<'a, u128>, default_connections: Map<'a, NetId, Addr>, @@ -28,6 +28,7 @@ pub struct CwCallService<'a> { pending_responses: Map<'a, (Vec, String), bool>, successful_responses: Map<'a, u128, bool>, callback_data: Map<'a, u64, Vec>, + call_reply: Item<'a, CSMessageRequest>, } impl<'a> Default for CwCallService<'a> { @@ -52,6 +53,7 @@ impl<'a> CwCallService<'a> { successful_responses: Map::new(StorageKey::SuccessfulResponses.as_str()), config: Item::new(StorageKey::Config.as_str()), callback_data: Map::new(StorageKey::Callbackdata.as_str()), + call_reply: Item::new(StorageKey::CallReply.as_str()), } } @@ -145,7 +147,7 @@ impl<'a> CwCallService<'a> { &self, store: &dyn Storage, id: u128, - ) -> Result { + ) -> Result { self.call_requests .load(store, id) .map_err(ContractError::Std) @@ -159,7 +161,7 @@ impl<'a> CwCallService<'a> { &self, store: &mut dyn Storage, id: u128, - request: &CallRequest, + request: &Rollback, ) -> Result<(), ContractError> { self.call_requests .save(store, id, request) @@ -371,4 +373,22 @@ impl<'a> CwCallService<'a> { let data = from_slice::(&bytes).map_err(ContractError::Std)?; Ok(data) } + + pub fn get_call_reply(&self, store: &dyn Storage) -> Option { + self.call_reply.load(store).ok() + } + + pub fn save_call_reply( + &self, + store: &mut dyn Storage, + msg: &CSMessageRequest, + ) -> Result<(), ContractError> { + self.call_reply.save(store, msg).map_err(ContractError::Std) + } + + pub fn pop_call_reply(&self, store: &mut dyn Storage) -> Option { + let reply = self.get_call_reply(store); + self.call_reply.remove(store); + reply + } } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/types/message.rs b/contracts/cosmwasm-vm/cw-xcall/src/types/message.rs index 1ab718bf..112e6137 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/types/message.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/types/message.rs @@ -1,38 +1,42 @@ use super::*; #[cw_serde] -pub enum CallServiceMessageType { - CallServiceRequest = 1, - CallServiceResponse, +pub enum CSMessageType { + CSMessageRequest = 1, + CSMessageResult, } #[cw_serde] pub struct CSMessage { - pub message_type: CallServiceMessageType, + pub message_type: CSMessageType, pub payload: Vec, } impl CSMessage { - pub fn new(message_type: CallServiceMessageType, payload: Vec) -> Self { + pub fn new(message_type: CSMessageType, payload: Vec) -> Self { Self { message_type, payload: payload.to_vec(), } } - pub fn message_type(&self) -> &CallServiceMessageType { + pub fn message_type(&self) -> &CSMessageType { &self.message_type } pub fn payload(&self) -> &[u8] { &self.payload } + + pub fn as_bytes(&self) -> Vec { + rlp::encode(&self.clone()).to_vec() + } } impl Encodable for CSMessage { fn rlp_append(&self, stream: &mut rlp::RlpStream) { let msg_type: u8 = match self.message_type { - CallServiceMessageType::CallServiceRequest => 1, - CallServiceMessageType::CallServiceResponse => 2, + CSMessageType::CSMessageRequest => 1, + CSMessageType::CSMessageResult => 2, }; stream.begin_list(2).append(&msg_type).append(&self.payload); } @@ -44,8 +48,8 @@ impl Decodable for CSMessage { Ok(Self { message_type: match msg_type { - 1 => Ok(CallServiceMessageType::CallServiceRequest), - 2 => Ok(CallServiceMessageType::CallServiceResponse), + 1 => Ok(CSMessageType::CSMessageRequest), + 2 => Ok(CSMessageType::CSMessageResult), _ => Err(rlp::DecoderError::Custom("Invalid type")), }?, payload: rlp.val_at(1)?, @@ -56,16 +60,16 @@ impl Decodable for CSMessage { impl From for CSMessage { fn from(value: CSMessageRequest) -> Self { Self { - message_type: CallServiceMessageType::CallServiceRequest, + message_type: CSMessageType::CSMessageRequest, payload: rlp::encode(&value).to_vec(), } } } -impl From for CSMessage { - fn from(value: CSMessageResponse) -> Self { +impl From for CSMessage { + fn from(value: CSMessageResult) -> Self { Self { - message_type: CallServiceMessageType::CallServiceResponse, + message_type: CSMessageType::CSMessageResult, payload: rlp::encode(&value).to_vec(), } } @@ -119,18 +123,12 @@ mod tests { #[test] fn test_csmessage_encoding() { let data = hex::decode("7465737431").unwrap(); - let message = CSMessage::new( - super::CallServiceMessageType::CallServiceRequest, - data.clone(), - ); + let message = CSMessage::new(super::CSMessageType::CSMessageRequest, data.clone()); let encoded = rlp::encode(&message); assert_eq!("c701857465737431", hex::encode(encoded)); - let message = CSMessage::new( - crate::types::message::CallServiceMessageType::CallServiceResponse, - data, - ); + let message = CSMessage::new(crate::types::message::CSMessageType::CSMessageResult, data); let encoded = rlp::encode(&message); assert_eq!("c702857465737431", hex::encode(encoded)); } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/types/mod.rs b/contracts/cosmwasm-vm/cw-xcall/src/types/mod.rs index 4f2d8210..f8fdb3d4 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/types/mod.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/types/mod.rs @@ -1,8 +1,8 @@ -pub mod call_request; pub mod config; pub mod message; pub mod request; -pub mod response; +pub mod result; +pub mod rollback; pub mod storage_keys; pub const LOG_PREFIX: &str = "[xcall_app]:"; @@ -13,4 +13,4 @@ use common::rlp::{Decodable, Encodable}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Binary}; use request::CSMessageRequest; -use response::CSMessageResponse; +use result::CSMessageResult; diff --git a/contracts/cosmwasm-vm/cw-xcall/src/types/request.rs b/contracts/cosmwasm-vm/cw-xcall/src/types/request.rs index 6cdf06ab..b5597c2a 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/types/request.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/types/request.rs @@ -1,7 +1,7 @@ use super::*; use common::rlp::Nullable; use cosmwasm_std::Addr; -use cw_xcall_lib::network_address::NetworkAddress; +use cw_xcall_lib::{message::msg_type::MessageType, network_address::NetworkAddress}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -11,7 +11,7 @@ pub struct CSMessageRequest { to: Addr, sequence_no: u128, protocols: Vec, - rollback: bool, + msg_type: MessageType, data: Nullable>, } @@ -20,7 +20,7 @@ impl CSMessageRequest { from: NetworkAddress, to: Addr, sequence_no: u128, - rollback: bool, + msg_type: MessageType, data: Vec, protocols: Vec, ) -> Self { @@ -32,7 +32,7 @@ impl CSMessageRequest { from, to, sequence_no, - rollback, + msg_type, data: Nullable::new(data_bytes), protocols, } @@ -50,8 +50,16 @@ impl CSMessageRequest { self.sequence_no } - pub fn rollback(&self) -> bool { - self.rollback + pub fn msg_type(&self) -> MessageType { + self.msg_type.clone() + } + + pub fn need_response(&self) -> bool { + self.msg_type == MessageType::CallMessageWithRollback + } + + pub fn allow_retry(&self) -> bool { + self.msg_type == MessageType::CallMessagePersisted } pub fn data(&self) -> Result<&[u8], ContractError> { @@ -66,6 +74,10 @@ impl CSMessageRequest { pub fn protocols(&self) -> &Vec { &self.protocols } + + pub fn as_bytes(&self) -> Vec { + rlp::encode(self.clone()).to_vec() + } } impl Encodable for CSMessageRequest { @@ -74,7 +86,7 @@ impl Encodable for CSMessageRequest { stream.append(&self.from.to_string()); stream.append(&self.to.to_string()); stream.append(&self.sequence_no); - stream.append(&self.rollback); + stream.append(&self.msg_type.as_int()); stream.append(&self.data); stream.begin_list(self.protocols.len()); for protocol in self.protocols.iter() { @@ -89,12 +101,13 @@ impl Decodable for CSMessageRequest { let list: Vec = rlp_protocols.as_list()?; let str_from: String = rlp.val_at(0)?; let to_str: String = rlp.val_at(1)?; + let msg_type_int: u8 = rlp.val_at(3)?; Ok(Self { from: NetworkAddress::from_str(&str_from) .map_err(|_e| rlp::DecoderError::RlpInvalidLength)?, to: Addr::unchecked(to_str), sequence_no: rlp.val_at(2)?, - rollback: rlp.val_at(3)?, + msg_type: MessageType::from_int(msg_type_int), data: rlp.val_at(4)?, protocols: list, }) @@ -129,28 +142,28 @@ mod tests { from: 0x1.ETH/0xa to: cx0000000000000000000000000000000000000102 sn: 21 - rollback: false + messageType: 1 data: 74657374 protocol: [] - RLP: F83F8B3078312E4554482F307861AA63783030303030303030303030303030303030303030303030303030303030303030303030303031303215008474657374C0 + RLP: f83f8b3078312e4554482f307861aa63783030303030303030303030303030303030303030303030303030303030303030303030303031303215018474657374c0 CSMessageRequest from: 0x1.ETH/0xa to: cx0000000000000000000000000000000000000102 sn: 21 - rollback: false + messageType: 1 data: 74657374 protocol: [abc, cde, efg] - RLP: F84B8B3078312E4554482F307861AA63783030303030303030303030303030303030303030303030303030303030303030303030303031303215008474657374CC836162638363646583656667 + RLP: f84b8b3078312e4554482f307861aa63783030303030303030303030303030303030303030303030303030303030303030303030303031303215018474657374cc836162638363646583656667 CSMessageRequest from: 0x1.ETH/0xa to: cx0000000000000000000000000000000000000102 sn: 21 - rollback: true + messageType: 2 data: 74657374 protocol: [abc, cde, efg] - RLP: F84B8B3078312E4554482F307861AA63783030303030303030303030303030303030303030303030303030303030303030303030303031303215018474657374CC836162638363646583656667 + RLP: f84b8b3078312e4554482f307861aa63783030303030303030303030303030303030303030303030303030303030303030303030303031303215028474657374cc836162638363646583656667 */ @@ -162,6 +175,7 @@ mod tests { use cw_xcall_lib::network_address::NetworkAddress; use super::CSMessageRequest; + use cw_xcall_lib::message::msg_type::MessageType; #[test] fn test_csmessage_request_encoding() { @@ -170,36 +184,39 @@ mod tests { NetworkAddress::from_str("0x1.ETH/0xa").unwrap(), Addr::unchecked("cx0000000000000000000000000000000000000102"), 21, - false, + MessageType::CallMessage, data.clone(), vec![], ); let encoded = rlp::encode(&msg); + assert_eq!("f83f8b3078312e4554482f307861aa63783030303030303030303030303030303030303030303030303030303030303030303030303031303215008474657374c0",hex::encode(encoded)); let msg = CSMessageRequest::new( NetworkAddress::from_str("0x1.ETH/0xa").unwrap(), Addr::unchecked("cx0000000000000000000000000000000000000102"), 21, - false, + MessageType::CallMessage, data.clone(), vec!["abc".to_string(), "cde".to_string(), "efg".to_string()], ); let encoded = rlp::encode(&msg); + assert_eq!("f84b8b3078312e4554482f307861aa63783030303030303030303030303030303030303030303030303030303030303030303030303031303215008474657374cc836162638363646583656667",hex::encode(encoded)); let msg = CSMessageRequest::new( NetworkAddress::from_str("0x1.ETH/0xa").unwrap(), Addr::unchecked("cx0000000000000000000000000000000000000102"), 21, - true, + MessageType::CallMessageWithRollback, data, vec!["abc".to_string(), "cde".to_string(), "efg".to_string()], ); let encoded = rlp::encode(&msg); + assert_eq!("f84b8b3078312e4554482f307861aa63783030303030303030303030303030303030303030303030303030303030303030303030303031303215018474657374cc836162638363646583656667",hex::encode(encoded)); } } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/types/response.rs b/contracts/cosmwasm-vm/cw-xcall/src/types/result.rs similarity index 69% rename from contracts/cosmwasm-vm/cw-xcall/src/types/response.rs rename to contracts/cosmwasm-vm/cw-xcall/src/types/result.rs index e52addab..cdb26b38 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/types/response.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/types/result.rs @@ -25,16 +25,22 @@ impl TryFrom for CallServiceResponseType { } #[cw_serde] -pub struct CSMessageResponse { +pub struct CSMessageResult { sequence_no: u128, response_code: CallServiceResponseType, + message: Vec, } -impl CSMessageResponse { - pub fn new(sequence_no: u128, response_code: CallServiceResponseType) -> Self { +impl CSMessageResult { + pub fn new( + sequence_no: u128, + response_code: CallServiceResponseType, + reply: Option>, + ) -> Self { Self { sequence_no, response_code, + message: reply.unwrap_or(vec![]), } } @@ -50,31 +56,44 @@ impl CSMessageResponse { self.sequence_no.clone_from(&sequence_no); self.response_code = response_code; } + + pub fn get_message(&self) -> Option { + if self.message.is_empty() { + return None; + } + rlp::decode(&self.message).ok() + } + + pub fn as_bytes(&self) -> Vec { + rlp::encode(&self.clone()).to_vec() + } } -impl Encodable for CSMessageResponse { +impl Encodable for CSMessageResult { fn rlp_append(&self, stream: &mut rlp::RlpStream) { let code: u8 = self.response_code.clone().into(); stream - .begin_list(2) + .begin_list(3) .append(&self.sequence_no()) - .append(&code); + .append(&code) + .append(&self.message); } } -impl Decodable for CSMessageResponse { +impl Decodable for CSMessageResult { fn decode(rlp: &rlp::Rlp) -> Result { let code: u8 = rlp.val_at(1)?; Ok(Self { sequence_no: rlp.val_at(0)?, response_code: CallServiceResponseType::try_from(code)?, + message: rlp.val_at(2).unwrap_or(vec![]), }) } } -impl TryFrom<&Vec> for CSMessageResponse { +impl TryFrom<&Vec> for CSMessageResult { type Error = ContractError; fn try_from(value: &Vec) -> Result { let rlp = rlp::Rlp::new(value as &[u8]); @@ -84,7 +103,7 @@ impl TryFrom<&Vec> for CSMessageResponse { } } -impl TryFrom<&[u8]> for CSMessageResponse { +impl TryFrom<&[u8]> for CSMessageResult { type Error = ContractError; fn try_from(value: &[u8]) -> Result { let rlp = rlp::Rlp::new(value); @@ -112,20 +131,20 @@ mod tests { use common::rlp; - use super::{CSMessageResponse, CallServiceResponseType}; + use super::{CSMessageResult, CallServiceResponseType}; #[test] fn test_cs_message_response_encoding() { let cs_response = - CSMessageResponse::new(1, CallServiceResponseType::CallServiceResponseSuccess); + CSMessageResult::new(1, CallServiceResponseType::CallServiceResponseSuccess, None); let encoded = rlp::encode(&cs_response); - assert_eq!("c20101", hex::encode(encoded)); + assert_eq!("c3010180", hex::encode(encoded)); let cs_response = - CSMessageResponse::new(2, CallServiceResponseType::CallServiceResponseFailure); + CSMessageResult::new(2, CallServiceResponseType::CallServiceResponseFailure, None); let encoded = rlp::encode(&cs_response); - assert_eq!("c20200", hex::encode(encoded)); + assert_eq!("c3020080", hex::encode(encoded)); } } diff --git a/contracts/cosmwasm-vm/cw-xcall/src/types/call_request.rs b/contracts/cosmwasm-vm/cw-xcall/src/types/rollback.rs similarity index 95% rename from contracts/cosmwasm-vm/cw-xcall/src/types/call_request.rs rename to contracts/cosmwasm-vm/cw-xcall/src/types/rollback.rs index 8230bde8..4d16d73b 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/types/call_request.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/types/rollback.rs @@ -5,7 +5,7 @@ use super::*; #[cw_serde] -pub struct CallRequest { +pub struct Rollback { from: Addr, to: NetworkAddress, protocols: Vec, @@ -13,7 +13,7 @@ pub struct CallRequest { enabled: bool, } -impl CallRequest { +impl Rollback { pub fn new( from: Addr, to: NetworkAddress, diff --git a/contracts/cosmwasm-vm/cw-xcall/src/types/storage_keys.rs b/contracts/cosmwasm-vm/cw-xcall/src/types/storage_keys.rs index 0237a7fb..ce4fe994 100644 --- a/contracts/cosmwasm-vm/cw-xcall/src/types/storage_keys.rs +++ b/contracts/cosmwasm-vm/cw-xcall/src/types/storage_keys.rs @@ -17,6 +17,7 @@ pub enum StorageKey { SuccessfulResponses, Config, Callbackdata, + CallReply, } impl StorageKey { @@ -37,6 +38,7 @@ impl StorageKey { StorageKey::SuccessfulResponses => "successful_responses", StorageKey::Config => "config", StorageKey::Callbackdata => "callback_data", + StorageKey::CallReply => "call_reply", } } } diff --git a/contracts/cosmwasm-vm/cw-xcall/tests/test_call_message.rs b/contracts/cosmwasm-vm/cw-xcall/tests/test_call_message.rs index e22454b1..07023047 100644 --- a/contracts/cosmwasm-vm/cw-xcall/tests/test_call_message.rs +++ b/contracts/cosmwasm-vm/cw-xcall/tests/test_call_message.rs @@ -1,6 +1,6 @@ mod account; mod setup; -use std::{collections::HashMap, vec}; +use std::{collections::HashMap, str::FromStr, vec}; use crate::account::*; use cosmwasm_std::{ @@ -34,6 +34,20 @@ fn send_packet_by_non_contract_and_rollback_data_is_not_null() { }, ) .unwrap(); + contract + .set_admin( + mock_deps.as_mut().storage, + Addr::unchecked(alice().to_string()), + ) + .unwrap(); + contract + .set_default_connection( + mock_deps.as_mut(), + mock_info.clone(), + NetId::from_str("nid").unwrap(), + Addr::unchecked("defaultconn".to_string()), + ) + .unwrap(); contract .send_call_message( diff --git a/contracts/cosmwasm-vm/cw-xcall/tests/test_handle_call_message.rs b/contracts/cosmwasm-vm/cw-xcall/tests/test_handle_call_message.rs index 298b114a..5c501573 100644 --- a/contracts/cosmwasm-vm/cw-xcall/tests/test_handle_call_message.rs +++ b/contracts/cosmwasm-vm/cw-xcall/tests/test_handle_call_message.rs @@ -2,14 +2,14 @@ use common::utils::keccak256; use cosmwasm_std::{ from_binary, testing::{mock_dependencies, mock_env, mock_info}, - Addr, Coin, CosmosMsg, Reply, SubMsgResponse, SubMsgResult, WasmMsg, + to_binary, Addr, Coin, CosmosMsg, Reply, SubMsgResponse, SubMsgResult, WasmMsg, }; use cw_xcall::{ state::{CwCallService, EXECUTE_CALL_ID}, - types::{call_request::CallRequest, request::CSMessageRequest}, + types::{request::CSMessageRequest, rollback::Rollback}, }; -use cw_xcall_lib::network_address::NetworkAddress; +use cw_xcall_lib::{message::msg_type::MessageType, network_address::NetworkAddress}; mod account; mod setup; use crate::account::alice; @@ -42,7 +42,7 @@ fn test_execute_call_with_wrong_data() { NetworkAddress::new("nid", "mockaddress"), Addr::unchecked("88bd05442686be0a5df7da33b6f1089ebfea3769b19dbb2477fe0cd6e0f123t7"), 123, - false, + MessageType::CallMessage, keccak256(&[104, 106, 108, 108, 111]).to_vec(), vec![], ); @@ -67,7 +67,7 @@ fn test_execute_call_having_request_id_without_rollback() { NetworkAddress::new("nid", "mockaddress"), Addr::unchecked("88bd05442686be0a5df7da33b6f1089ebfea3769b19dbb2477fe0cd6e0f123t7"), 123, - false, + MessageType::CallMessage, keccak256(&data).to_vec(), vec![], ); @@ -119,7 +119,7 @@ fn test_successful_reply_message() { NetworkAddress::new("nid", "mockaddress"), Addr::unchecked("88bd05442686be0a5df7da33b6f1089ebfea3769b19dbb2477fe0cd6e0f123t7"), 123, - false, + MessageType::CallMessage, vec![], vec![], ); @@ -154,7 +154,7 @@ fn test_failed_reply_message() { NetworkAddress::new("nid", "mockaddress"), Addr::unchecked("88bd05442686be0a5df7da33b6f1089ebfea3769b19dbb2477fe0cd6e0f123t7"), 123, - false, + MessageType::CallMessage, vec![], vec![], ); @@ -204,7 +204,7 @@ fn execute_rollback_success() { let seq_id = 123456; - let request = CallRequest::new( + let request = Rollback::new( Addr::unchecked("88bd05442686be0a5df7da33b6f1089ebfea3769b19dbb2477fe0cd6e0f126e4"), NetworkAddress::new("nid", "mockaddress"), vec![], @@ -248,7 +248,7 @@ fn execute_rollback_failure() { let seq_id = 123456; - let request = CallRequest::new( + let request = Rollback::new( Addr::unchecked("88bd05442686be0a5df7da33b6f1089ebfea3769b19dbb2477fe0cd6e0f126e4"), NetworkAddress::new("nid", "mockaddress"), vec![], @@ -277,3 +277,84 @@ fn execute_rollback_failure() { _ => todo!(), } } + +#[test] +fn test_persisted_message_not_removed_on_error() { + let mut mock_deps = deps(); + + let env = mock_env(); + + let msg = Reply { + id: EXECUTE_CALL_ID, + result: SubMsgResult::Err("error message".into()), + }; + + let contract = CwCallService::default(); + + let request_id = 123456; + let proxy_requests = CSMessageRequest::new( + NetworkAddress::new("nid", "mockaddress"), + Addr::unchecked("88bd05442686be0a5df7da33b6f1089ebfea3769b19dbb2477fe0cd6e0f123t7"), + 123, + MessageType::CallMessagePersisted, + vec![], + vec![], + ); + contract + .store_proxy_request(mock_deps.as_mut().storage, request_id, &proxy_requests) + .unwrap(); + + contract + .store_execute_request_id(mock_deps.as_mut().storage, request_id) + .unwrap(); + + let _response = contract.reply(mock_deps.as_mut(), env, msg); + + assert_eq!(_response.is_err(), true); + + let req = contract + .get_proxy_request(mock_deps.as_ref().storage, request_id) + .unwrap(); + assert_eq!(req, proxy_requests); +} + +#[test] +fn test_persisted_message_removed_on_success() { + let mut mock_deps = deps(); + + let env = mock_env(); + + let msg = Reply { + id: EXECUTE_CALL_ID, + result: SubMsgResult::Ok(SubMsgResponse { + events: vec![], + data: to_binary(&1).ok(), + }), + }; + + let contract = CwCallService::default(); + + let request_id = 123456; + let proxy_requests = CSMessageRequest::new( + NetworkAddress::new("nid", "mockaddress"), + Addr::unchecked("88bd05442686be0a5df7da33b6f1089ebfea3769b19dbb2477fe0cd6e0f123t7"), + 123, + MessageType::CallMessagePersisted, + vec![], + vec![], + ); + contract + .store_proxy_request(mock_deps.as_mut().storage, request_id, &proxy_requests) + .unwrap(); + + contract + .store_execute_request_id(mock_deps.as_mut().storage, request_id) + .unwrap(); + + let _response = contract.reply(mock_deps.as_mut(), env, msg).unwrap(); + + let req = contract + .get_proxy_request(mock_deps.as_ref().storage, request_id) + .ok(); + assert_eq!(req, None); +} diff --git a/contracts/evm/contracts/adapters/CentralizedConnection.sol b/contracts/evm/contracts/adapters/CentralizedConnection.sol index 2443258e..58a90be7 100644 --- a/contracts/evm/contracts/adapters/CentralizedConnection.sol +++ b/contracts/evm/contracts/adapters/CentralizedConnection.sol @@ -5,7 +5,7 @@ pragma abicoder v2; import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; import "@xcall/utils/Types.sol"; import "@xcall/contracts/xcall/interfaces/IConnection.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallService.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; contract CentralizedConnection is Initializable, IConnection { mapping(string => uint256) private messageFees; diff --git a/contracts/evm/contracts/adapters/LayerZeroAdapter.sol b/contracts/evm/contracts/adapters/LayerZeroAdapter.sol index bc47100e..686d932c 100644 --- a/contracts/evm/contracts/adapters/LayerZeroAdapter.sol +++ b/contracts/evm/contracts/adapters/LayerZeroAdapter.sol @@ -4,7 +4,7 @@ pragma abicoder v2; import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; import "@xcall/utils/Types.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallService.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; import "@lz-contracts/interfaces/ILayerZeroReceiver.sol"; import "@lz-contracts/interfaces/ILayerZeroEndpoint.sol"; import "./interfaces/ILayerZeroAdapter.sol"; diff --git a/contracts/evm/contracts/adapters/WormholeAdapter.sol b/contracts/evm/contracts/adapters/WormholeAdapter.sol index 7b4f3a45..d230a6a8 100644 --- a/contracts/evm/contracts/adapters/WormholeAdapter.sol +++ b/contracts/evm/contracts/adapters/WormholeAdapter.sol @@ -11,7 +11,7 @@ import "./interfaces/IWormholeAdapter.sol"; import "@xcall/utils/Types.sol"; import "@xcall/contracts/xcall/interfaces/IConnection.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallService.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; /** * @title WormholeAdapter diff --git a/contracts/evm/contracts/mocks/dapp/DAppProxySample.sol b/contracts/evm/contracts/mocks/dapp/DAppProxySample.sol index 010be3f7..8ce763d8 100644 --- a/contracts/evm/contracts/mocks/dapp/DAppProxySample.sol +++ b/contracts/evm/contracts/mocks/dapp/DAppProxySample.sol @@ -2,8 +2,8 @@ pragma solidity >=0.8.0; pragma abicoder v2; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallService.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/IDefaultCallServiceReceiver.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/IDefaultCallServiceReceiver.sol"; import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; diff --git a/contracts/evm/contracts/mocks/multi-protocol-dapp/MultiProtocolSampleDapp.sol b/contracts/evm/contracts/mocks/multi-protocol-dapp/MultiProtocolSampleDapp.sol index daba7af6..9504cbd8 100644 --- a/contracts/evm/contracts/mocks/multi-protocol-dapp/MultiProtocolSampleDapp.sol +++ b/contracts/evm/contracts/mocks/multi-protocol-dapp/MultiProtocolSampleDapp.sol @@ -1,12 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.13; -import "@iconfoundation/btp2-solidity-library/utils/NetworkAddress.sol"; -import "@iconfoundation/btp2-solidity-library/utils/Integers.sol"; -import "@iconfoundation/btp2-solidity-library/utils/ParseAddress.sol"; -import "@iconfoundation/btp2-solidity-library/utils/Strings.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallService.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallServiceReceiver.sol"; +import "@iconfoundation/xcall-solidity-library/utils/NetworkAddress.sol"; +import "@iconfoundation/xcall-solidity-library/utils/Integers.sol"; +import "@iconfoundation/xcall-solidity-library/utils/ParseAddress.sol"; +import "@iconfoundation/xcall-solidity-library/utils/Strings.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallServiceReceiver.sol"; +import "@xcall/utils/Types.sol"; import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; @@ -49,6 +50,30 @@ contract MultiProtocolSampleDapp is Initializable, ICallServiceReceiver { _sendCallMessage(msg.value, to, data, rollback); } + function sendNewMessage(string memory to, bytes memory data, int256 messageType, bytes memory rollback) external payable { + + bytes memory message; + (string memory net,) = to.parseNetworkAddress(); + string[] memory _sources = getSources(net); + string[] memory _destinations = getDestinations(net); + + if (messageType == Types.PERSISTENT_MESSAGE_TYPE) { + message = Types.createPersistentMessage(data, _sources, _destinations); + } else if(messageType == Types.CALL_MESSAGE_TYPE) { + message = Types.createCallMessage(data, _sources, _destinations); + } else if(messageType == Types.CALL_MESSAGE_ROLLBACK_TYPE) { + require(rollback.length > 0, "InvalidRollback"); + message = Types.createCallMessageWithRollback(data, rollback, _sources, _destinations); + } else { + revert("InvalidMessageType"); + } + _sendCall(msg.value, to, message); + } + + function sendMessageAny(string memory to, bytes memory data) external payable { + _sendCall(msg.value, to, data); + } + function _sendCallMessage( uint256 value, string memory to, @@ -59,6 +84,14 @@ contract MultiProtocolSampleDapp is Initializable, ICallServiceReceiver { ICallService(callSvc).sendCallMessage{value: value}(to, data, rollback, getSources(net), getDestinations(net)); } + function _sendCall( + uint256 value, + string memory to, + bytes memory message + ) private { + ICallService(callSvc).sendCall{value: value}(to, message); + } + function handleCallMessage(string memory from, bytes memory data, string[] memory protocols) external onlyCallService { (string memory netFrom,) = from.parseNetworkAddress(); @@ -69,11 +102,14 @@ contract MultiProtocolSampleDapp is Initializable, ICallServiceReceiver { } else { require(protocolsEqual(protocols, getSources(netFrom)), "invalid protocols"); require(keccak256(data) != keccak256(abi.encodePacked("rollback")), "rollback"); + + if(keccak256(data) == keccak256(abi.encodePacked("reply-reponse"))) { + _sendCallMessage(0, from, '010203', bytes("")); + } emit MessageReceived(from, data); } } - function protocolsEqual(string[] memory a, string[] memory b) private pure returns (bool) { if (a.length != b.length) { return false; diff --git a/contracts/evm/contracts/xcall/CallService.sol b/contracts/evm/contracts/xcall/CallService.sol index 78a0d75c..5b3ee9ab 100644 --- a/contracts/evm/contracts/xcall/CallService.sol +++ b/contracts/evm/contracts/xcall/CallService.sol @@ -8,17 +8,17 @@ import "@xcall/utils/RLPDecodeStruct.sol"; import "@xcall/utils/RLPEncodeStruct.sol"; import "@xcall/utils/Types.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/IBSH.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallService.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallServiceReceiver.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/IDefaultCallServiceReceiver.sol"; -import "@iconfoundation/btp2-solidity-library/utils/NetworkAddress.sol"; -import "@iconfoundation/btp2-solidity-library/utils/Integers.sol"; -import "@iconfoundation/btp2-solidity-library/utils/ParseAddress.sol"; -import "@iconfoundation/btp2-solidity-library/utils/Strings.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/IBSH.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallServiceReceiver.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/IDefaultCallServiceReceiver.sol"; +import "@iconfoundation/xcall-solidity-library/utils/NetworkAddress.sol"; +import "@iconfoundation/xcall-solidity-library/utils/Integers.sol"; +import "@iconfoundation/xcall-solidity-library/utils/ParseAddress.sol"; +import "@iconfoundation/xcall-solidity-library/utils/Strings.sol"; import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; - +/// @custom:oz-upgrades-from contracts/xcall/CallServiceV1.sol:CallServiceV1 contract CallService is IBSH, ICallService, IFeeManage, Initializable { using Strings for string; using Integers for uint; @@ -26,8 +26,10 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { using ParseAddress for string; using NetworkAddress for string; using RLPEncodeStruct for Types.CSMessage; - using RLPEncodeStruct for Types.CSMessageRequest; - using RLPEncodeStruct for Types.CSMessageResponse; + using RLPEncodeStruct for Types.CSMessageRequestV2; + using RLPEncodeStruct for Types.CSMessageResult; + using RLPEncodeStruct for Types.CallMessageWithRollback; + using RLPEncodeStruct for Types.XCallEnvelope; using RLPDecodeStruct for bytes; uint256 private constant MAX_DATA_SIZE = 2048; @@ -38,7 +40,14 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { uint256 private lastReqId; uint256 private protocolFee; + /** + * Legacy Code, replaced by rollbacks in V2 + */ mapping(uint256 => Types.CallRequest) private requests; + + /** + * Legacy Code, replaced by proxyReqsV2 in V2 + */ mapping(uint256 => Types.ProxyRequest) private proxyReqs; mapping(uint256 => bool) private successfulResponses; @@ -52,6 +61,12 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { address private adminAddress; address payable private feeHandler; + mapping(uint256 => Types.RollbackData) private rollbacks; + mapping(uint256 => Types.ProxyRequestV2) private proxyReqsV2; + + bytes private callReply; + Types.ProxyRequestV2 private replyState; + modifier onlyOwner() { require(msg.sender == owner, "OnlyOwner"); _; @@ -62,9 +77,7 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { _; } - function initialize( - string memory _nid - ) public initializer { + function initialize(string memory _nid) public initializer { owner = msg.sender; adminAddress = msg.sender; nid = _nid; @@ -72,42 +85,35 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { } /* Implementation-specific external */ - function getNetworkAddress( - ) external view override returns ( - string memory - ) { + function getNetworkAddress() + external + view + override + returns (string memory) + { return networkAddress; } - function getNetworkId( - ) external view override returns ( - string memory - ) { + function getNetworkId() external view override returns (string memory) { return nid; } - function checkService( - string calldata _svc - ) internal pure { + function checkService(string calldata _svc) internal pure { require(Types.NAME.compareTo(_svc), "InvalidServiceName"); } - function getNextSn( - ) internal returns (uint256) { + function getNextSn() internal returns (uint256) { lastSn = lastSn + 1; return lastSn; } - function getNextReqId( - ) internal returns (uint256) { + function getNextReqId() internal returns (uint256) { lastReqId = lastReqId + 1; return lastReqId; } - function cleanupCallRequest( - uint256 sn - ) internal { - delete requests[sn]; + function cleanupCallRequest(uint256 sn) internal { + delete rollbacks[sn]; } function sendCallMessage( @@ -116,155 +122,249 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { bytes memory _rollback, string[] memory sources, string[] memory destinations - ) external payable override returns ( - uint256 - ) { + ) external payable override returns (uint256) { return _sendCallMessage(_to, _data, _rollback, sources, destinations); } - function sendCallMessage( string memory _to, bytes memory _data, bytes memory _rollback - ) external payable override returns ( - uint256 - ) { + ) external payable override returns (uint256) { string[] memory src; string[] memory dst; return _sendCallMessage(_to, _data, _rollback, src, dst); } - function _sendCallMessage( + function sendCall( string memory _to, - bytes memory _data, - bytes memory _rollback, - string[] memory sources, - string[] memory destinations - ) internal returns ( - uint256 - ) { - // check if caller is a contract or rollback data is null in case of EOA - require(msg.sender.code.length > 0 || _rollback.length == 0, "RollbackNotPossible"); - - // check size of payloads to avoid abusing - require(_rollback.length <= MAX_ROLLBACK_SIZE, "MaxRollbackSizeExceeded"); - - bool needResponse = _rollback.length > 0; - (string memory netTo, string memory dstAccount) = _to.parseNetworkAddress(); - string memory from = nid.networkAddress(msg.sender.toString()); + bytes memory _data + ) public payable returns (uint256) { + address caller = msg.sender; + Types.XCallEnvelope memory envelope = _data.decodeXCallEnvelope(); uint256 sn = getNextSn(); - int256 msgSn = 0; - if (needResponse) { - requests[sn] = Types.CallRequest(msg.sender, netTo, sources, _rollback, false); - msgSn = int256(sn); - } - Types.CSMessageRequest memory reqMsg = Types.CSMessageRequest( - from, dstAccount, sn, needResponse, _data, destinations); - bytes memory _msg = reqMsg.encodeCSMessageRequest(); + Types.ProcessResult memory result = preProcessMessage( + sn, + _to, + envelope + ); + + string memory from = nid.networkAddress(caller.toString()); + + (string memory netTo, string memory dstAccount) = _to + .parseNetworkAddress(); + + Types.CSMessageRequestV2 memory req = Types.CSMessageRequestV2( + from, + dstAccount, + sn, + envelope.messageType, + result.data, + envelope.destinations + ); + + bytes memory _msg = req.encodeCSMessageRequestV2(); require(_msg.length <= MAX_DATA_SIZE, "MaxDataSizeExceeded"); + if (isReply(netTo, envelope.sources) && !result.needResponse) { + delete replyState; + callReply = _msg; + } else { + uint256 sendSn = result.needResponse ? sn : 0; + + sendMessage( + envelope.sources, + netTo, + Types.CS_REQUEST, + int(sendSn), + _msg + ); + claimProtocolFee(); + } + emit CallMessageSent(caller, _to, sn); + return sn; + } + + function sendMessage( + string[] memory sources, + string memory netTo, + int msgType, + int256 sn, + bytes memory data + ) private { if (sources.length == 0) { address conn = defaultConnections[netTo]; require(conn != address(0), "NoDefaultConnection"); - uint256 requiredFee = IConnection(conn).getFee(netTo, needResponse); - sendBTPMessage(conn, requiredFee, netTo, Types.CS_REQUEST, msgSn, _msg); + uint256 requiredFee = _getFee(conn, netTo, sn); + sendToConnection(conn, requiredFee, netTo, msgType, sn, data); } else { for (uint i = 0; i < sources.length; i++) { address conn = sources[i].parseAddress("IllegalArgument"); - uint256 requiredFee = IConnection(conn).getFee(netTo, needResponse); - sendBTPMessage(conn, requiredFee, netTo, Types.CS_REQUEST, msgSn, _msg); + uint256 requiredFee = _getFee(conn, netTo, sn); + sendToConnection(conn, requiredFee, netTo, msgType, sn, data); } } + } - // handle protocol fee - if (feeHandler != address(0) && address(this).balance > 0) { - // we trust fee handler, it should just accept the protocol fee and return - // assume that no reentrant cases occur here - feeHandler.transfer(address(this).balance); + function preProcessMessage( + uint256 sn, + string memory to, + Types.XCallEnvelope memory envelope + ) internal returns (Types.ProcessResult memory) { + int envelopeType = envelope.messageType; + if ( + envelopeType == Types.CALL_MESSAGE_TYPE || + envelopeType == Types.PERSISTENT_MESSAGE_TYPE + ) { + return Types.ProcessResult(false, envelope.message); + } else if (envelopeType == Types.CALL_MESSAGE_ROLLBACK_TYPE) { + address caller = msg.sender; + Types.CallMessageWithRollback memory _msg = envelope + .message + .decodeCallMessageWithRollback(); + require(msg.sender.code.length > 0, "RollbackNotPossible"); + Types.RollbackData memory req = Types.RollbackData( + caller, + to.nid(), + envelope.sources, + _msg.rollback, + false + ); + rollbacks[sn] = req; + return Types.ProcessResult(true, _msg.data); } + revert("Message type is not supported"); + } + + function claimProtocolFee() internal { + uint256 balance = address(this).balance; + require(balance >= protocolFee, "InsufficientBalance"); + feeHandler.transfer(balance); + } - emit CallMessageSent(msg.sender, _to, sn); + function _sendCallMessage( + string memory _to, + bytes memory _data, + bytes memory _rollback, + string[] memory sources, + string[] memory destinations + ) internal returns (uint256) { + int msgType; + + Types.XCallEnvelope memory envelope; + + if (_rollback.length == 0) { + Types.CallMessage memory _msg = Types.CallMessage(_data); + envelope = Types.XCallEnvelope( + Types.CALL_MESSAGE_TYPE, + _msg.data, + sources, + destinations + ); + } else { + Types.CallMessageWithRollback memory _msg = Types + .CallMessageWithRollback(_data, _rollback); + + envelope = Types.XCallEnvelope( + Types.CALL_MESSAGE_ROLLBACK_TYPE, + _msg.encodeCallMessageWithRollback(), + sources, + destinations + ); + } - return sn; + return sendCall(_to, envelope.encodeXCallEnvelope()); } - function executeCall( - uint256 _reqId, - bytes memory _data - ) external override { - Types.ProxyRequest memory msgReq = proxyReqs[_reqId]; - require(bytes(msgReq.from).length > 0, "InvalidRequestId"); - require(msgReq.hash == keccak256(_data), "DataHashMismatch"); + function executeCall(uint256 _reqId, bytes memory _data) external override { + Types.ProxyRequestV2 memory req = proxyReqsV2[_reqId]; + require(bytes(req.from).length > 0, "InvalidRequestId"); + require(req.hash == keccak256(_data), "DataHashMismatch"); // cleanup - delete proxyReqs[_reqId]; - - string memory netFrom = msgReq.from.nid(); - Types.CSMessageResponse memory msgRes; - string memory errorMessage = ""; - try this.tryHandleCallMessage( - address(0), - msgReq.to, - msgReq.from, - _data, - msgReq.protocols - ) { - msgRes = Types.CSMessageResponse(msgReq.sn, Types.CS_RESP_SUCCESS); - } catch Error(string memory reason) { - msgRes = Types.CSMessageResponse(msgReq.sn, Types.CS_RESP_FAILURE); - errorMessage = reason; - } catch (bytes memory) { - msgRes = Types.CSMessageResponse(msgReq.sn, Types.CS_RESP_FAILURE); - errorMessage = "unknownError"; - } - emit CallExecuted(_reqId, msgRes.code, errorMessage); - - // send response only when there was a rollback - if (msgReq.rollback) { - if (msgReq.protocols.length == 0) { - address conn = defaultConnections[netFrom]; - sendBTPMessage(conn, 0, netFrom, Types.CS_RESPONSE, int256(msgReq.sn) * - 1, msgRes.encodeCSMessageResponse()); - - } else { - for (uint i = 0; i < msgReq.protocols.length; i++) { - address conn = msgReq.protocols[i].parseAddress("IllegalArgument"); - sendBTPMessage(conn, 0, netFrom, Types.CS_RESPONSE, int256(msgReq.sn) * - 1, msgRes.encodeCSMessageResponse()); - } + delete proxyReqsV2[_reqId]; + + string[] memory protocols = req.protocols; + address dapp = req.to.parseAddress("IllegalArgument"); + if (req.messageType == Types.CALL_MESSAGE_TYPE) { + tryExecuteCall(_reqId, dapp, req.from, _data, protocols); + } else if (req.messageType == Types.PERSISTENT_MESSAGE_TYPE) { + this.executeMessage(dapp, req.from, _data, protocols); + } else if (req.messageType == Types.CALL_MESSAGE_ROLLBACK_TYPE) { + replyState = req; + int256 code = tryExecuteCall( + _reqId, + dapp, + req.from, + _data, + protocols + ); + delete replyState; + + bytes memory message; + if (callReply.length > 0 && code == Types.CS_RESP_SUCCESS) { + message = callReply; + delete callReply; } + Types.CSMessageResult memory response = Types.CSMessageResult( + req.sn, + code, + message + ); + + sendMessage( + protocols, + req.from.nid(), + Types.CS_RESULT, + int256(req.sn) * -1, + response.encodeCSMessageResult() + ); + } else { + revert("Message type is not yet supported"); + } + } + function tryExecuteCall( + uint256 id, + address dapp, + string memory from, + bytes memory data, + string[] memory protocols + ) private returns (int256) { + try this.executeMessage(dapp, from, data, protocols) { + emit CallExecuted(id, Types.CS_RESP_SUCCESS, ""); + return Types.CS_RESP_SUCCESS; + } catch Error(string memory errorMessage) { + emit CallExecuted(id, Types.CS_RESP_FAILURE, errorMessage); + return Types.CS_RESP_FAILURE; + } catch (bytes memory) { + emit CallExecuted(id, Types.CS_RESP_FAILURE, "unknownError"); + return Types.CS_RESP_FAILURE; } } // @dev To catch error - function tryHandleCallMessage( - address toAddr, - string memory to, + function executeMessage( + address to, string memory from, bytes memory data, string[] memory protocols ) external { require(msg.sender == address(this), "OnlyInternal"); - if (toAddr == address(0)) { - toAddr = to.parseAddress("IllegalArgument"); - } if (protocols.length == 0) { - IDefaultCallServiceReceiver(toAddr).handleCallMessage(from, data); + IDefaultCallServiceReceiver(to).handleCallMessage(from, data); } else { - ICallServiceReceiver(toAddr).handleCallMessage(from, data, protocols); + ICallServiceReceiver(to).handleCallMessage(from, data, protocols); } } - function executeRollback( - uint256 _sn - ) external override { - Types.CallRequest memory req = requests[_sn]; + function executeRollback(uint256 _sn) external override { + Types.RollbackData memory req = rollbacks[_sn]; require(req.from != address(0), "InvalidSerialNum"); require(req.enabled, "RollbackNotEnabled"); cleanupCallRequest(_sn); - this.tryHandleCallMessage( + this.executeMessage( req.from, - "", networkAddress, req.rollback, req.sources @@ -294,6 +394,7 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { checkService(_svc); handleError(_sn); } + /* ========================================= */ function handleMessage( @@ -304,8 +405,8 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { Types.CSMessage memory csMsg = _msg.decodeCSMessage(); if (csMsg.msgType == Types.CS_REQUEST) { handleRequest(_from, csMsg.payload); - } else if (csMsg.msgType == Types.CS_RESPONSE) { - handleResponse(csMsg.payload.decodeCSMessageResponse()); + } else if (csMsg.msgType == Types.CS_RESULT) { + handleResult(csMsg.payload.decodeCSMessageResult()); } else { string memory errMsg = string("UnknownMsgType(") .concat(uint(csMsg.msgType).toString()) @@ -314,16 +415,13 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { } } - function handleError( - uint256 _sn - ) public override { - handleResponse(Types.CSMessageResponse( - _sn, - Types.CS_RESP_FAILURE - )); + function handleError(uint256 _sn) public override { + handleResult( + Types.CSMessageResult(_sn, Types.CS_RESP_FAILURE, bytes("")) + ); } - function sendBTPMessage( + function sendToConnection( address connection, uint256 value, string memory netTo, @@ -335,10 +433,7 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { netTo, Types.NAME, sn, - Types.CSMessage( - msgType, - msgPayload - ).encodeCSMessage() + Types.CSMessage(msgType, msgPayload).encodeCSMessage() ); } @@ -346,9 +441,10 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { string memory netFrom, bytes memory msgPayload ) internal { - Types.CSMessageRequest memory req = msgPayload.decodeCSMessageRequest(); + Types.CSMessageRequestV2 memory req = msgPayload + .decodeCSMessageRequestV2(); string memory fromNID = req.from.nid(); - require(netFrom.compareTo(fromNID),"Invalid NID"); + require(netFrom.compareTo(fromNID), "Invalid NID"); bytes32 dataHash = keccak256(req.data); if (req.protocols.length > 1) { @@ -358,22 +454,24 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { return; } } - for (uint i = 0; i < req.protocols.length; i++) { delete pendingReqs[dataHash][req.protocols[i]]; } } else if (req.protocols.length == 1) { - require(msg.sender == req.protocols[0].parseAddress("IllegalArgument"), "NotAuthorized"); + require( + msg.sender == req.protocols[0].parseAddress("IllegalArgument"), + "NotAuthorized" + ); } else { require(msg.sender == defaultConnections[fromNID], "NotAuthorized"); } - uint256 reqId = getNextReqId(); - proxyReqs[reqId] = Types.ProxyRequest( + + proxyReqsV2[reqId] = Types.ProxyRequestV2( req.from, req.to, req.sn, - req.rollback, + req.messageType, dataHash, req.protocols ); @@ -381,48 +479,71 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { emit CallMessage(req.from, req.to, req.sn, reqId, req.data); } - function handleResponse( - Types.CSMessageResponse memory res + function handleReply( + Types.RollbackData memory rollback, + Types.CSMessageRequestV2 memory reply ) internal { - Types.CallRequest memory req = requests[res.sn]; - if (req.from == address(0)) { - return; - } + require(rollback.to.compareTo(reply.from.nid()), "Invalid Reply"); + uint256 reqId = getNextReqId(); + + emit CallMessage(reply.from, reply.to, reply.sn, reqId, reply.data); + + proxyReqsV2[reqId] = Types.ProxyRequestV2( + reply.from, + reply.to, + reply.sn, + reply.messageType, + keccak256(reply.data), + rollback.sources + ); + } - if (req.sources.length > 1) { + function handleResult(Types.CSMessageResult memory res) internal { + Types.RollbackData memory rollback = rollbacks[res.sn]; + require(rollback.from != address(0), "CallRequestNotFound"); + + if (rollback.sources.length > 1) { pendingResponses[res.sn][msg.sender.toString()] = true; - for (uint i = 0; i < req.sources.length; i++) { - if (!pendingResponses[res.sn][req.sources[i]]) { + for (uint i = 0; i < rollback.sources.length; i++) { + if (!pendingResponses[res.sn][rollback.sources[i]]) { return; } } - for (uint i = 0; i < req.sources.length; i++) { - delete pendingResponses[res.sn][req.sources[i]]; + for (uint i = 0; i < rollback.sources.length; i++) { + delete pendingResponses[res.sn][rollback.sources[i]]; } - } else if (req.sources.length == 1) { - require(msg.sender == req.sources[0].parseAddress("IllegalArgument"), "NotAuthorized"); + } else if (rollback.sources.length == 1) { + require( + msg.sender == + rollback.sources[0].parseAddress("IllegalArgument"), + "NotAuthorized" + ); } else { - require(msg.sender == defaultConnections[req.to], "NotAuthorized"); + require( + msg.sender == defaultConnections[rollback.to], + "NotAuthorized" + ); } emit ResponseMessage(res.sn, res.code); if (res.code == Types.CS_RESP_SUCCESS) { cleanupCallRequest(res.sn); + if (res.message.length > 0) { + handleReply(rollback, res.message.decodeCSMessageRequestV2()); + } successfulResponses[res.sn] = true; } else { //emit rollback event - require(req.rollback.length > 0, "NoRollbackData"); - req.enabled = true; - requests[res.sn] = req; + require(rollback.rollback.length > 0, "NoRollbackData"); + rollback.enabled = true; + rollbacks[res.sn] = rollback; + emit RollbackMessage(res.sn); } } - function _admin( - ) internal view returns ( - address - ) { + function _admin() internal view returns (address) { if (adminAddress == address(0)) { return owner; } @@ -433,10 +554,7 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { @notice Gets the address of admin @return (Address) the address of admin */ - function admin( - ) external view returns ( - address - ) { + function admin() external view returns (address) { return _admin(); } @@ -445,44 +563,39 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { @dev Only the owner wallet can invoke this. @param _address (Address) The address of admin */ - function setAdmin( - address _address - ) external onlyAdmin { + function setAdmin(address _address) external onlyAdmin { + require(_address != address(0), "InvalidAddress"); adminAddress = _address; } - function setProtocolFeeHandler( - address _addr - ) external override onlyAdmin { + function setProtocolFeeHandler(address _addr) external override onlyAdmin { + require(_addr != address(0), "InvalidAddress"); feeHandler = payable(_addr); } - function getProtocolFeeHandler( - ) external view override returns ( - address - ) { + function getProtocolFeeHandler() external view override returns (address) { return feeHandler; } - function setDefaultConnection(string memory _nid, address connection) external onlyAdmin { + function setDefaultConnection( + string memory _nid, + address connection + ) external onlyAdmin { defaultConnections[_nid] = connection; } - function getDefaultConnection(string memory _nid) external view returns (address) { + function getDefaultConnection( + string memory _nid + ) external view returns (address) { return defaultConnections[_nid]; } - function setProtocolFee( - uint256 _value - ) external override onlyAdmin { + function setProtocolFee(uint256 _value) external override onlyAdmin { require(_value >= 0, "ValueShouldBePositive"); protocolFee = _value; } - function getProtocolFee( - ) external view override returns ( - uint256 - ) { + function getProtocolFee() external view override returns (uint256) { return protocolFee; } @@ -490,18 +603,25 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { address connection, string memory _net, bool _rollback - ) internal view returns ( - uint256 - ) { + ) internal view returns (uint256) { return IConnection(connection).getFee(_net, _rollback); } + function _getFee( + address connection, + string memory _net, + int256 sn + ) internal view returns (uint256) { + if (sn < 0) { + return 0; + } + return IConnection(connection).getFee(_net, sn > 0); + } + function getFee( string memory _net, bool _rollback - ) external view override returns ( - uint256 - ) { + ) external view override returns (uint256) { return protocolFee + _getFee(defaultConnections[_net], _net, _rollback); } @@ -509,10 +629,11 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { string memory _net, bool _rollback, string[] memory _sources - ) external view override returns ( - uint256 - ) { + ) external view override returns (uint256) { uint256 fee = protocolFee; + if (isReply(_net, _sources) && !_rollback) { + return 0; + } for (uint i = 0; i < _sources.length; i++) { address conn = _sources[i].parseAddress("IllegalArgument"); fee = fee + _getFee(conn, _net, _rollback); @@ -521,7 +642,36 @@ contract CallService is IBSH, ICallService, IFeeManage, Initializable { return fee; } + function isReply( + string memory _net, + string[] memory _sources + ) internal view returns (bool) { + if (!replyState.from.compareTo("")) { + return + replyState.from.nid().compareTo(_net) && + areArraysEqual(replyState.protocols, _sources); + } + return false; + } + + function areArraysEqual( + string[] memory array1, + string[] memory array2 + ) internal pure returns (bool) { + if (array1.length != array2.length) { + return false; + } + + for (uint256 i = 0; i < array1.length; i++) { + if (!array1[i].compareTo(array2[i])) { + return false; + } + } + + return true; + } + function verifySuccess(uint256 _sn) external view returns (bool) { return successfulResponses[_sn]; } -} +} \ No newline at end of file diff --git a/contracts/evm/contracts/xcall/CallServiceV1.sol b/contracts/evm/contracts/xcall/CallServiceV1.sol new file mode 100644 index 00000000..1a70d989 --- /dev/null +++ b/contracts/evm/contracts/xcall/CallServiceV1.sol @@ -0,0 +1,527 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; +pragma abicoder v2; + +import "./interfaces/IFeeManage.sol"; +import "./interfaces/IConnection.sol"; +import "@xcall/utils/RLPDecodeStruct.sol"; +import "@xcall/utils/RLPEncodeStruct.sol"; +import "@xcall/utils/Types.sol"; + +import "@iconfoundation/xcall-solidity-library/interfaces/IBSH.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallServiceV1.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallServiceReceiver.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/IDefaultCallServiceReceiver.sol"; +import "@iconfoundation/xcall-solidity-library/utils/NetworkAddress.sol"; +import "@iconfoundation/xcall-solidity-library/utils/Integers.sol"; +import "@iconfoundation/xcall-solidity-library/utils/ParseAddress.sol"; +import "@iconfoundation/xcall-solidity-library/utils/Strings.sol"; +import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; + + +contract CallServiceV1 is IBSH, ICallServiceV1, IFeeManage, Initializable { + using Strings for string; + using Integers for uint; + using ParseAddress for address; + using ParseAddress for string; + using NetworkAddress for string; + using RLPEncodeStruct for Types.CSMessage; + using RLPEncodeStruct for Types.CSMessageRequest; + using RLPEncodeStruct for Types.CSMessageResponse; + using RLPDecodeStruct for bytes; + + uint256 private constant MAX_DATA_SIZE = 2048; + uint256 private constant MAX_ROLLBACK_SIZE = 1024; + string private nid; + string private networkAddress; + uint256 private lastSn; + uint256 private lastReqId; + uint256 private protocolFee; + + mapping(uint256 => Types.CallRequest) private requests; + mapping(uint256 => Types.ProxyRequest) private proxyReqs; + + mapping(uint256 => bool) private successfulResponses; + + mapping(bytes32 => mapping(string => bool)) private pendingReqs; + mapping(uint256 => mapping(string => bool)) private pendingResponses; + + mapping(string => address) private defaultConnections; + + address private owner; + address private adminAddress; + address payable private feeHandler; + + modifier onlyOwner() { + require(msg.sender == owner, "OnlyOwner"); + _; + } + + modifier onlyAdmin() { + require(msg.sender == _admin(), "OnlyAdmin"); + _; + } + + function initialize( + string memory _nid + ) public initializer { + owner = msg.sender; + adminAddress = msg.sender; + nid = _nid; + networkAddress = nid.networkAddress(address(this).toString()); + } + + /* Implementation-specific external */ + function getNetworkAddress( + ) external view override returns ( + string memory + ) { + return networkAddress; + } + + function getNetworkId( + ) external view override returns ( + string memory + ) { + return nid; + } + + function checkService( + string calldata _svc + ) internal pure { + require(Types.NAME.compareTo(_svc), "InvalidServiceName"); + } + + function getNextSn( + ) internal returns (uint256) { + lastSn = lastSn + 1; + return lastSn; + } + + function getNextReqId( + ) internal returns (uint256) { + lastReqId = lastReqId + 1; + return lastReqId; + } + + function cleanupCallRequest( + uint256 sn + ) internal { + delete requests[sn]; + } + + function sendCallMessage( + string memory _to, + bytes memory _data, + bytes memory _rollback, + string[] memory sources, + string[] memory destinations + ) external payable override returns ( + uint256 + ) { + return _sendCallMessage(_to, _data, _rollback, sources, destinations); + } + + + function sendCallMessage( + string memory _to, + bytes memory _data, + bytes memory _rollback + ) external payable override returns ( + uint256 + ) { + string[] memory src; + string[] memory dst; + return _sendCallMessage(_to, _data, _rollback, src, dst); + } + + function _sendCallMessage( + string memory _to, + bytes memory _data, + bytes memory _rollback, + string[] memory sources, + string[] memory destinations + ) internal returns ( + uint256 + ) { + // check if caller is a contract or rollback data is null in case of EOA + require(msg.sender.code.length > 0 || _rollback.length == 0, "RollbackNotPossible"); + + // check size of payloads to avoid abusing + require(_rollback.length <= MAX_ROLLBACK_SIZE, "MaxRollbackSizeExceeded"); + + bool needResponse = _rollback.length > 0; + (string memory netTo, string memory dstAccount) = _to.parseNetworkAddress(); + string memory from = nid.networkAddress(msg.sender.toString()); + uint256 sn = getNextSn(); + int256 msgSn = 0; + if (needResponse) { + requests[sn] = Types.CallRequest(msg.sender, netTo, sources, _rollback, false); + msgSn = int256(sn); + } + Types.CSMessageRequest memory reqMsg = Types.CSMessageRequest( + from, dstAccount, sn, needResponse, _data, destinations); + bytes memory _msg = reqMsg.encodeCSMessageRequest(); + require(_msg.length <= MAX_DATA_SIZE, "MaxDataSizeExceeded"); + + if (sources.length == 0) { + address conn = defaultConnections[netTo]; + require(conn != address(0), "NoDefaultConnection"); + uint256 requiredFee = IConnection(conn).getFee(netTo, needResponse); + sendBTPMessage(conn, requiredFee, netTo, Types.CS_REQUEST, msgSn, _msg); + } else { + for (uint i = 0; i < sources.length; i++) { + address conn = sources[i].parseAddress("IllegalArgument"); + uint256 requiredFee = IConnection(conn).getFee(netTo, needResponse); + sendBTPMessage(conn, requiredFee, netTo, Types.CS_REQUEST, msgSn, _msg); + } + } + + // handle protocol fee + if (feeHandler != address(0) && address(this).balance > 0) { + // we trust fee handler, it should just accept the protocol fee and return + // assume that no reentrant cases occur here + feeHandler.transfer(address(this).balance); + } + + emit CallMessageSent(msg.sender, _to, sn); + + return sn; + } + + function executeCall( + uint256 _reqId, + bytes memory _data + ) external override { + Types.ProxyRequest memory msgReq = proxyReqs[_reqId]; + require(bytes(msgReq.from).length > 0, "InvalidRequestId"); + require(msgReq.hash == keccak256(_data), "DataHashMismatch"); + // cleanup + delete proxyReqs[_reqId]; + + string memory netFrom = msgReq.from.nid(); + Types.CSMessageResponse memory msgRes; + string memory errorMessage = ""; + try this.tryHandleCallMessage( + address(0), + msgReq.to, + msgReq.from, + _data, + msgReq.protocols + ) { + msgRes = Types.CSMessageResponse(msgReq.sn, Types.CS_RESP_SUCCESS); + } catch Error(string memory reason) { + msgRes = Types.CSMessageResponse(msgReq.sn, Types.CS_RESP_FAILURE); + errorMessage = reason; + } catch (bytes memory) { + msgRes = Types.CSMessageResponse(msgReq.sn, Types.CS_RESP_FAILURE); + errorMessage = "unknownError"; + } + emit CallExecuted(_reqId, msgRes.code, errorMessage); + + // send response only when there was a rollback + if (msgReq.rollback) { + if (msgReq.protocols.length == 0) { + address conn = defaultConnections[netFrom]; + sendBTPMessage(conn, 0, netFrom, Types.CS_RESPONSE, int256(msgReq.sn) * - 1, msgRes.encodeCSMessageResponse()); + + } else { + for (uint i = 0; i < msgReq.protocols.length; i++) { + address conn = msgReq.protocols[i].parseAddress("IllegalArgument"); + sendBTPMessage(conn, 0, netFrom, Types.CS_RESPONSE, int256(msgReq.sn) * - 1, msgRes.encodeCSMessageResponse()); + } + } + + } + } + + // @dev To catch error + function tryHandleCallMessage( + address toAddr, + string memory to, + string memory from, + bytes memory data, + string[] memory protocols + ) external { + require(msg.sender == address(this), "OnlyInternal"); + if (toAddr == address(0)) { + toAddr = to.parseAddress("IllegalArgument"); + } + if (protocols.length == 0) { + IDefaultCallServiceReceiver(toAddr).handleCallMessage(from, data); + } else { + ICallServiceReceiver(toAddr).handleCallMessage(from, data, protocols); + } + } + + function executeRollback( + uint256 _sn + ) external override { + Types.CallRequest memory req = requests[_sn]; + require(req.from != address(0), "InvalidSerialNum"); + require(req.enabled, "RollbackNotEnabled"); + cleanupCallRequest(_sn); + + this.tryHandleCallMessage( + req.from, + "", + networkAddress, + req.rollback, + req.sources + ); + + emit RollbackExecuted(_sn); + } + + /* ========== Interfaces with BMC ========== */ + function handleBTPMessage( + string calldata _from, + string calldata _svc, + uint256 _sn, + bytes calldata _msg + ) external override { + checkService(_svc); + handleMessage(_from, _msg); + } + + function handleBTPError( + string calldata _src, + string calldata _svc, + uint256 _sn, + uint256 _code, + string calldata _msg + ) external override { + checkService(_svc); + handleError(_sn); + } + /* ========================================= */ + + function handleMessage( + string calldata _from, + bytes calldata _msg + ) public override { + require(!_from.compareTo(nid), "Invalid Network ID"); + Types.CSMessage memory csMsg = _msg.decodeCSMessage(); + if (csMsg.msgType == Types.CS_REQUEST) { + handleRequest(_from, csMsg.payload); + } else if (csMsg.msgType == Types.CS_RESPONSE) { + handleResponse(csMsg.payload.decodeCSMessageResponse()); + } else { + string memory errMsg = string("UnknownMsgType(") + .concat(uint(csMsg.msgType).toString()) + .concat(string(")")); + revert(errMsg); + } + } + + function handleError( + uint256 _sn + ) public override { + handleResponse(Types.CSMessageResponse( + _sn, + Types.CS_RESP_FAILURE + )); + } + + function sendBTPMessage( + address connection, + uint256 value, + string memory netTo, + int msgType, + int256 sn, + bytes memory msgPayload + ) internal { + IConnection(connection).sendMessage{value: value}( + netTo, + Types.NAME, + sn, + Types.CSMessage( + msgType, + msgPayload + ).encodeCSMessage() + ); + } + + function handleRequest( + string memory netFrom, + bytes memory msgPayload + ) internal { + Types.CSMessageRequest memory req = msgPayload.decodeCSMessageRequest(); + string memory fromNID = req.from.nid(); + require(netFrom.compareTo(fromNID),"Invalid NID"); + + bytes32 dataHash = keccak256(req.data); + if (req.protocols.length > 1) { + pendingReqs[dataHash][msg.sender.toString()] = true; + for (uint i = 0; i < req.protocols.length; i++) { + if (!pendingReqs[dataHash][req.protocols[i]]) { + return; + } + } + + for (uint i = 0; i < req.protocols.length; i++) { + delete pendingReqs[dataHash][req.protocols[i]]; + } + } else if (req.protocols.length == 1) { + require(msg.sender == req.protocols[0].parseAddress("IllegalArgument"), "NotAuthorized"); + } else { + require(msg.sender == defaultConnections[fromNID], "NotAuthorized"); + } + + uint256 reqId = getNextReqId(); + proxyReqs[reqId] = Types.ProxyRequest( + req.from, + req.to, + req.sn, + req.rollback, + dataHash, + req.protocols + ); + + emit CallMessage(req.from, req.to, req.sn, reqId, req.data); + } + + function handleResponse( + Types.CSMessageResponse memory res + ) internal { + Types.CallRequest memory req = requests[res.sn]; + if (req.from == address(0)) { + return; + } + + if (req.sources.length > 1) { + pendingResponses[res.sn][msg.sender.toString()] = true; + for (uint i = 0; i < req.sources.length; i++) { + if (!pendingResponses[res.sn][req.sources[i]]) { + return; + } + } + + for (uint i = 0; i < req.sources.length; i++) { + delete pendingResponses[res.sn][req.sources[i]]; + } + } else if (req.sources.length == 1) { + require(msg.sender == req.sources[0].parseAddress("IllegalArgument"), "NotAuthorized"); + } else { + require(msg.sender == defaultConnections[req.to], "NotAuthorized"); + } + + emit ResponseMessage(res.sn, res.code); + if (res.code == Types.CS_RESP_SUCCESS) { + cleanupCallRequest(res.sn); + successfulResponses[res.sn] = true; + } else { + //emit rollback event + require(req.rollback.length > 0, "NoRollbackData"); + req.enabled = true; + requests[res.sn] = req; + emit RollbackMessage(res.sn); + } + } + + function _admin( + ) internal view returns ( + address + ) { + if (adminAddress == address(0)) { + return owner; + } + return adminAddress; + } + + /** + @notice Gets the address of admin + @return (Address) the address of admin + */ + function admin( + ) external view returns ( + address + ) { + return _admin(); + } + + /** + @notice Sets the address of admin + @dev Only the owner wallet can invoke this. + @param _address (Address) The address of admin + */ + function setAdmin( + address _address + ) external onlyAdmin { + adminAddress = _address; + } + + function setProtocolFeeHandler( + address _addr + ) external override onlyAdmin { + feeHandler = payable(_addr); + } + + function getProtocolFeeHandler( + ) external view override returns ( + address + ) { + return feeHandler; + } + + function setDefaultConnection(string memory _nid, address connection) external onlyAdmin { + defaultConnections[_nid] = connection; + } + + function getDefaultConnection(string memory _nid) external view returns (address) { + return defaultConnections[_nid]; + } + + function setProtocolFee( + uint256 _value + ) external override onlyAdmin { + require(_value >= 0, "ValueShouldBePositive"); + protocolFee = _value; + } + + function getProtocolFee( + ) external view override returns ( + uint256 + ) { + return protocolFee; + } + + function _getFee( + address connection, + string memory _net, + bool _rollback + ) internal view returns ( + uint256 + ) { + return IConnection(connection).getFee(_net, _rollback); + } + + function getFee( + string memory _net, + bool _rollback + ) external view override returns ( + uint256 + ) { + return protocolFee + _getFee(defaultConnections[_net], _net, _rollback); + } + + function getFee( + string memory _net, + bool _rollback, + string[] memory _sources + ) external view override returns ( + uint256 + ) { + uint256 fee = protocolFee; + for (uint i = 0; i < _sources.length; i++) { + address conn = _sources[i].parseAddress("IllegalArgument"); + fee = fee + _getFee(conn, _net, _rollback); + } + + return fee; + } + + function verifySuccess(uint256 _sn) external view returns (bool) { + return successfulResponses[_sn]; + } +} diff --git a/contracts/evm/env.example b/contracts/evm/env.example new file mode 100644 index 00000000..10a5a87a --- /dev/null +++ b/contracts/evm/env.example @@ -0,0 +1,138 @@ +PRIVATE_KEY=0xe7f654b797e30f709183d1028dcec6548be88cbe5f181ddb0f51f0d3203023d8 +OWNER_ADDRESS=0xc92e83F1b446e649b3bD284DD9CD6cF8f2bd8250 +PROTOCOL_FEE=0 +ADMIN=0xc92e83F1b446e649b3bD284DD9CD6cF8f2bd8250 + +#XCALL DEPLOYMENTS +LOCAL_NID= +LOCAL_RPC_URL=http://127.0.0.1:8545 +LOCAL_XCALL= + +#MAINNET DEPLOYMENTS + +#ETHEREUM +ETHERUM_RPC_URL= +ETHEREUM_NID=* +ETHEREUM_XCALL= + +#BINANCE +BSC_RPC_URL= +BSC_NID= +BSC_XCALL= + +#AVALANCHE +AVALANCHE_RPC_URL= + + + + +BSCTEST_RPC_URL=https://data-seed-prebsc-1-s1.binance.org:8545 +SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/JxPqX6PhmfdYfoQ1avgoBFwCxNUCKzu2 +BASE_GOERLI_RPC_URL=https://goerli.base.org +OPTIMISM_GOERLI_RPC_URL=https://goerli.optimism.io +ARBITRUM_GOERLI_RPC_URL=https://goerli-rollup.arbitrum.io/rpc +FUJI_RPC_URL=https://api.avax-test.network/ext/C/rpc +CELOTEST_RPC_URL=https://alfajores-forno.celo-testnet.org +POLYGON_MUMBAI_RPC_URL=https://rpc.ankr.com/polygon_mumbai + +BSC_RPC_URL= +ETHERUM_RPC_URL= +BASE_RPC_URL= +OPTIMISM_RPC_URL= +ARBITRUM_RPC_URL= +AVALANCHE_RPC_URL= + +#DIFFERENT CHAINS NID +LOCAL_NID=local + +BSCTEST_NID=0x61.bsc +SEPOLIA_NID= +BASE_GOERLI_NID=0x14a33.base +OPTIMISM_GOERLI_NID= +ARBITRUM_GOERLI_NID= +FUJI_NID=0xa869.fuji +CELOTEST_NID=0xaef3.celo +POLYGON_MUMBAI_NID=0x13881.mumbai + +BSC_NID=* +ETHEREUM_NID=* +BASE_NID=* +OPTIMISM_NID=* +ARBITRUM_NID=* +AVALANCHE_NID=* + + +#WORMHOLE RELAYER ADDRESSES +LOCAL_WORMHOLE_RELAYER=0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB +FUJI_WORMHOLE_RELAYER=0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB +BSCTEST_WORMHOLE_RELAYER=0x80aC94316391752A193C1c47E27D382b507c93F3 +BASE_GOERLI_WORMHOLE_RELAYER=0xea8029CD7FCAEFFcD1F53686430Db0Fc8ed384E1 +CELOTEST_WORMHOLE_RELAYER=0x306B68267Deb7c5DfCDa3619E22E9Ca39C374f84 + +#LAYER ZERO RELAYER ENDPOINT +LOCAL_LAYERZERO_RELAYER=0xA3cF45939bD6260bcFe3D66bc73d60f19e49a8BB +FUJI_LAYERZERO_RELAYER=0x93f54D755A063cE7bB9e6Ac47Eccc8e33411d706 +BSCTEST_LAYERZERO_RELAYER=0x6Fcb97553D41516Cb228ac03FdC8B9a0a9df04A1 +BASE_GOERLI_LAYERZERO_RELAYER=0x6aB5Ae6822647046626e83ee6dB8187151E1d5ab +CELOTEST_LAYERZERO_RELAYER=0xae92d5aD7583AD66E49A0c67BAd18F6ba52dDDc1 + +#Wormhole Chain ID +FUJI_CHAIN_ID=6 +BSCTEST_CHAIN_ID=4 +BASE_GOERLI_CHAIN_ID=30 +CELOTEST_CHAIN_ID=14 + +#LAYERZERO CHAINID +FUJI_LAYERZERO_CHAIN_ID=10106 +BSCTEST_LAYERZERO_CHAIN_ID=10102 +BASE_GOERLI_LAYERZERO_CHAIN_ID=10160 +CELOTEST_LAYERZERO_CHAIN_ID=10125 + +#XCALL ADDRESSES +BSCTEST_XCALL=0x4bf2816025920D4c721888Cf3c23856B42DF69b0 +FUJI_XCALL=0xb8baf62BEd55366042c0aF3ed5AE114609bf77AA +BASE_GOERLI_XCALL=0x2Ca03A78001b9c2E18D56Daa27b0A6BB9761eBCD +CELOTEST_XCALL=0x2Ca03A78001b9c2E18D56Daa27b0A6BB9761eBCD +LOCAL_XCALL=0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e +POLYGON_MUMBAI_XCALL=0x2Ca03A78001b9c2E18D56Daa27b0A6BB9761eBCD +CELOTEST_XCALL= + + +#MOCK DAPP ADDRESSES +BSCTEST_MOCK_DAPP=0x3d5A17C384Ea22373aE053550f24F6c142D1A4d0 +FUJI_MOCK_DAPP=0xe3330eb0c2498e21d23a6ca2f7aa75d00a94c653 +BASE_GOERLI_MOCK_DAPP=0x103ad18d900290b6575e4af6287a215fe78085fb + +# WORMHOLE ADDRESSES +BSCTEST_WORMHOLE_ADAPTER=0xB54B7e5825b52231dA4cd52c49b91949f1002aC0 +FUJI_WORMHOLE_ADAPTER=0x965582B1365EECE38CB6862E9818A5fdf422b86A +BASE_GOERLI_WORMHOLE_ADAPTER=0x64FDC0B87019cEeA603f9DD559b9bAd31F1157b8 +# CELOTEST_ADAPTER=0x64FDC0B87019cEeA603f9DD559b9bAd31F1157b8 + +#ETHERSCAN APIS +BSCSCAN_API_KEY=MDBZR5TAPWDJAQD4DA3Q77HYBITBI1UE6P +ETHERSCAN_API_KEY=8VP9SPX7C2UXZWR37WWKJ6SPKSM69VHRX8 +BASESCAN_API_KEY=6JVRDVBKVCGAH3PZ4AHMWAVP99B8RITI9W +OPTIMISMSCAN_API_KEY=87KTYBPDE7GWS112X9H73FBRYVBBJZPGIE +ARBITRUMSCAN_API_KEY=ZP4SRGNIXX7PRT8IMT1IQEPV86EX5326Q5 +SNOWSCAN_API_KEY=ZKM5XEXV5CDHWD73QMU5IW1KF4F2X87B46 +CELOSCAN_API_KEY=83RKDAH8MR5FV5CUXK7HIEH1G3R5ZHI9IA +POLYSCAN_API_KEY=YD3616TF1CD81YRH2J2TBF1SMJE9XJH38R + +#LAYERZERO +BSCTEST_LAYERZERO_ADAPTER=0x8e83b1d0a2def6dd3c754a91f8c08cdf8a917f42 +FUJI_LAYERZERO_ADAPTER=0xec1837fa2f63a96a28ca7c6436f641fab03dcb75 +BASE_GOERLI_LAYERZERO_ADAPTER=0x4b83195D02F58a1Acf8e1A80bEfB28e6a4F54654 +CELOTEST_LAYERZERO_ADAPTER=0x885dd30aed3a009C72a8a21F4A23784bF0a41303 + +#CENTRALIZED_RELAYER +BSCTEST_CENTRALIZED_RELAYER=0xdb21509ea25ef02ca58aba716537b487c13317e4 +FUJI_CENTRALIZED_RELAYER=0xdb21509ea25ef02ca58aba716537b487c13317e4 +POLYGON_MUMBAI_CENTRALIZED_RELAYER=0xdb21509ea25ef02ca58aba716537b487c13317e4 +LOCAL_CENTRALIZED_RELAYER=0xdb21509ea25ef02ca58aba716537b487c13317e4 + +#CENTRALIZED_ADAPTER +BSCTEST_CENTRALIZED_ADAPTER= +FUJI_CENTRALIZED_ADAPTER= +POLYGON_MUMBAI_CENTRALIZED_ADAPTER=0x64FDC0B87019cEeA603f9DD559b9bAd31F1157b8 +LOCAL_CENTRALIZED_ADAPTER=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 diff --git a/contracts/evm/library/utils/RLPDecodeStruct.sol b/contracts/evm/library/utils/RLPDecodeStruct.sol index d4765df2..d9165c04 100644 --- a/contracts/evm/library/utils/RLPDecodeStruct.sol +++ b/contracts/evm/library/utils/RLPDecodeStruct.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; pragma abicoder v2; -import "@iconfoundation/btp2-solidity-library/utils/RLPDecode.sol"; +import "@iconfoundation/xcall-solidity-library/utils/RLPDecode.sol"; import "./Types.sol"; library RLPDecodeStruct { @@ -15,11 +15,9 @@ library RLPDecodeStruct { uint8 private constant LIST_SHORT_START = 0xc0; uint8 private constant LIST_LONG_START = 0xf7; - function decodeCSMessage(bytes memory _rlp) - internal - pure - returns (Types.CSMessage memory) - { + function decodeCSMessage( + bytes memory _rlp + ) internal pure returns (Types.CSMessage memory) { RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList(); return Types.CSMessage( @@ -28,43 +26,92 @@ library RLPDecodeStruct { ); } - function decodeCSMessageRequest(bytes memory _rlp) + function decodeCSMessageResponse(bytes memory _rlp) internal pure - returns (Types.CSMessageRequest memory) + returns (Types.CSMessageResponse memory) { RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList(); return - Types.CSMessageRequest( - string(ls[0].toBytes()), - string(ls[1].toBytes()), - ls[2].toUint(), - ls[3].toBoolean(), - ls[4].toBytes(), - toStringArray(ls[5]) + Types.CSMessageResponse( + ls[0].toUint(), + int(ls[1].toInt()) ); } - function toStringArray( RLPDecode.RLPItem memory item) internal pure returns (string[] memory) { + function toStringArray( + RLPDecode.RLPItem memory item + ) internal pure returns (string[] memory) { RLPDecode.RLPItem[] memory ls = item.toList(); string[] memory protocols = new string[](ls.length); - for (uint256 i = 0; i < ls.length; i++) { + for (uint256 i = 0; i < ls.length; i++) { protocols[i] = string(ls[i].toBytes()); } return protocols; } - function decodeCSMessageResponse(bytes memory _rlp) + function decodeCSMessageRequest(bytes memory _rlp) internal pure - returns (Types.CSMessageResponse memory) + returns (Types.CSMessageRequest memory) { RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList(); return - Types.CSMessageResponse( - ls[0].toUint(), - int(ls[1].toInt()) + Types.CSMessageRequest( + string(ls[0].toBytes()), + string(ls[1].toBytes()), + ls[2].toUint(), + ls[3].toBoolean(), + ls[4].toBytes(), + toStringArray(ls[5]) ); } + function decodeCSMessageRequestV2( + bytes memory _rlp + ) internal pure returns (Types.CSMessageRequestV2 memory) { + RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList(); + return + Types.CSMessageRequestV2( + string(ls[0].toBytes()), + string(ls[1].toBytes()), + ls[2].toUint(), + ls[3].toInt(), + ls[4].toBytes(), + toStringArray(ls[5]) + ); + } + + function decodeCallMessageWithRollback( + bytes memory _rlp + ) internal pure returns (Types.CallMessageWithRollback memory) { + RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList(); + return + Types.CallMessageWithRollback( + ls[0].toBytes(), + ls[1].toBytes() + ); + } + + + function decodeXCallEnvelope( + bytes memory _rlp + ) internal pure returns (Types.XCallEnvelope memory) { + RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList(); + + return + Types.XCallEnvelope( + ls[0].toInt(), + ls[1].toBytes(), + toStringArray(ls[2]), + toStringArray(ls[3]) + ); + } + + function decodeCSMessageResult( + bytes memory _rlp + ) internal pure returns (Types.CSMessageResult memory) { + RLPDecode.RLPItem[] memory ls = _rlp.toRlpItem().toList(); + return Types.CSMessageResult(ls[0].toUint(), ls[1].toInt(), ls[2].toBytes()); + } } diff --git a/contracts/evm/library/utils/RLPEncodeStruct.sol b/contracts/evm/library/utils/RLPEncodeStruct.sol index f15b3afa..cb9bc5c3 100644 --- a/contracts/evm/library/utils/RLPEncodeStruct.sol +++ b/contracts/evm/library/utils/RLPEncodeStruct.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; pragma abicoder v2; -import "@iconfoundation/btp2-solidity-library/utils/RLPEncode.sol"; +import "@iconfoundation/xcall-solidity-library/utils/RLPEncode.sol"; import "./Types.sol"; library RLPEncodeStruct { @@ -15,15 +15,12 @@ library RLPEncodeStruct { using RLPEncodeStruct for Types.CSMessage; using RLPEncodeStruct for Types.CSMessageRequest; - using RLPEncodeStruct for Types.CSMessageResponse; + using RLPEncodeStruct for Types.CSMessageResult; - function encodeCSMessage(Types.CSMessage memory _bs) - internal - pure - returns (bytes memory) - { - bytes memory _rlp = - abi.encodePacked( + function encodeCSMessage( + Types.CSMessage memory _bs + ) internal pure returns (bytes memory) { + bytes memory _rlp = abi.encodePacked( _bs.msgType.encodeInt(), _bs.payload.encodeBytes() ); @@ -55,6 +52,26 @@ library RLPEncodeStruct { return _rlp.encodeList(); } + function encodeCSMessageRequestV2( + Types.CSMessageRequestV2 memory _bs + ) internal pure returns (bytes memory) { + bytes memory _protocols; + bytes memory temp; + for (uint256 i = 0; i < _bs.protocols.length; i++) { + temp = abi.encodePacked(_bs.protocols[i].encodeString()); + _protocols = abi.encodePacked(_protocols, temp); + } + bytes memory _rlp = abi.encodePacked( + _bs.from.encodeString(), + _bs.to.encodeString(), + _bs.sn.encodeUint(), + _bs.messageType.encodeInt(), + _bs.data.encodeBytes(), + _protocols.encodeList() + ); + return _rlp.encodeList(); + } + function encodeCSMessageResponse(Types.CSMessageResponse memory _bs) internal pure @@ -67,4 +84,53 @@ library RLPEncodeStruct { ); return _rlp.encodeList(); } -} \ No newline at end of file + + function encodeXCallEnvelope( + Types.XCallEnvelope memory env + ) internal pure returns (bytes memory) { + + bytes memory _sources; + bytes memory temp; + + for (uint256 i = 0; i < env.sources.length; i++) { + temp = abi.encodePacked(env.sources[i].encodeString()); + _sources = abi.encodePacked(_sources, temp); + } + + bytes memory _dests; + for (uint256 i = 0; i < env.destinations.length; i++) { + temp = abi.encodePacked(env.destinations[i].encodeString()); + _dests = abi.encodePacked(_dests, temp); + } + + bytes memory _rlp = abi.encodePacked( + env.messageType.encodeInt(), + env.message.encodeBytes(), + _sources.encodeList(), + _dests.encodeList() + ); + + return _rlp.encodeList(); + } + + function encodeCSMessageResult( + Types.CSMessageResult memory _bs + ) internal pure returns (bytes memory) { + bytes memory _rlp = abi.encodePacked( + _bs.sn.encodeUint(), + _bs.code.encodeInt(), + _bs.message.encodeBytes() + ); + return _rlp.encodeList(); + } + + function encodeCallMessageWithRollback( + Types.CallMessageWithRollback memory _bs + ) internal pure returns (bytes memory) { + bytes memory _rlp = abi.encodePacked( + _bs.data.encodeBytes(), + _bs.rollback.encodeBytes() + ); + return _rlp.encodeList(); + } +} diff --git a/contracts/evm/library/utils/Types.sol b/contracts/evm/library/utils/Types.sol index cb9a9b9b..8f0223a3 100644 --- a/contracts/evm/library/utils/Types.sol +++ b/contracts/evm/library/utils/Types.sol @@ -1,16 +1,33 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0; +import "@xcall/utils/RLPEncodeStruct.sol"; +import "@xcall/utils/RLPEncodeStruct.sol"; /** * @notice List of ALL Struct being used to Encode and Decode RLP Messages */ library Types { + using RLPEncodeStruct for Types.CallMessageWithRollback; + using RLPEncodeStruct for Types.XCallEnvelope; + // The name of CallService. string constant NAME = "xcallM"; int constant CS_REQUEST = 1; + /** + * Legacy Code, CS_RESPONSE replaced by CS_RESULT in V2 + */ int constant CS_RESPONSE = 2; + int constant CS_RESULT = 2; + + int constant CALL_MESSAGE_TYPE = 0; + int constant CALL_MESSAGE_ROLLBACK_TYPE = 1; + int constant PERSISTENT_MESSAGE_TYPE = 2; + + /** + * Legacy Code, CallRequest replaced with RollbackData + */ struct CallRequest { address from; string to; @@ -19,11 +36,27 @@ library Types { bool enabled; //whether wait response or received } + struct RollbackData { + address from; + string to; + string[] sources; + bytes rollback; + bool enabled; + } + struct CSMessage { int msgType; bytes payload; } + struct CSMessageResponse { + uint256 sn; + int code; + } + + /** + * Legacy Code, CSMessageRequest replaced with CSMessageRequestV2 + */ struct CSMessageRequest { string from; string to; @@ -31,9 +64,11 @@ library Types { bool rollback; bytes data; string[] protocols; - } + /** + * Legacy Code, ProxyRequest replaced with ProxyRequestV2 + */ struct ProxyRequest { string from; string to; @@ -43,12 +78,31 @@ library Types { string[] protocols; } + struct CSMessageRequestV2 { + string from; + string to; + uint256 sn; + int messageType; + bytes data; + string[] protocols; + } + + struct ProxyRequestV2 { + string from; + string to; + uint256 sn; + int256 messageType; + bytes32 hash; + string[] protocols; + } + int constant CS_RESP_SUCCESS = 1; int constant CS_RESP_FAILURE = 0; - struct CSMessageResponse { + struct CSMessageResult { uint256 sn; int code; + bytes message; } struct PendingResponse { @@ -56,4 +110,59 @@ library Types { string targetNetwork; } + struct XCallEnvelope { + int messageType; + bytes message; + string[] sources; + string[] destinations; + } + + struct CallMessage { + bytes data; + } + + struct CallMessageWithRollback { + bytes data; + bytes rollback; + } + + struct ProcessResult { + bool needResponse; + bytes data; + } + + function createPersistentMessage( + bytes memory data, + string[] memory sources, + string[] memory destinations + ) internal pure returns (bytes memory) { + return + XCallEnvelope(PERSISTENT_MESSAGE_TYPE, data, sources, destinations).encodeXCallEnvelope(); + } + + function createCallMessage( + bytes memory data, + string[] memory sources, + string[] memory destinations + ) internal pure returns (bytes memory) { + return XCallEnvelope(CALL_MESSAGE_TYPE, data, sources, destinations).encodeXCallEnvelope(); + } + + function createCallMessageWithRollback( + bytes memory data, + bytes memory rollback, + string[] memory sources, + string[] memory destinations + ) internal pure returns (bytes memory) { + Types.CallMessageWithRollback memory _msg = Types + .CallMessageWithRollback(data, rollback); + + return + XCallEnvelope( + CALL_MESSAGE_ROLLBACK_TYPE, + _msg.encodeCallMessageWithRollback(), + sources, + destinations + ).encodeXCallEnvelope(); + } } diff --git a/contracts/evm/library/btp2/interfaces/IBSH.sol b/contracts/evm/library/xcall/interfaces/IBSH.sol similarity index 100% rename from contracts/evm/library/btp2/interfaces/IBSH.sol rename to contracts/evm/library/xcall/interfaces/IBSH.sol diff --git a/contracts/evm/library/btp2/interfaces/ICallService.sol b/contracts/evm/library/xcall/interfaces/ICallService.sol similarity index 79% rename from contracts/evm/library/btp2/interfaces/ICallService.sol rename to contracts/evm/library/xcall/interfaces/ICallService.sol index a950073e..f8cd86ab 100644 --- a/contracts/evm/library/btp2/interfaces/ICallService.sol +++ b/contracts/evm/library/xcall/interfaces/ICallService.sol @@ -2,15 +2,9 @@ pragma solidity >=0.8.0; interface ICallService { - function getNetworkAddress( - ) external view returns ( - string memory - ); + function getNetworkAddress() external view returns (string memory); - function getNetworkId( - ) external view returns ( - string memory - ); + function getNetworkId() external view returns (string memory); /** @notice Gets the fee for delivering a message to the _net. @@ -23,17 +17,13 @@ interface ICallService { function getFee( string memory _net, bool _rollback - ) external view returns ( - uint256 - ); + ) external view returns (uint256); function getFee( string memory _net, bool _rollback, string[] memory _sources - ) external view returns ( - uint256 - ); + ) external view returns (uint256); /*======== At the source CALL_BSH ========*/ /** @@ -47,9 +37,7 @@ interface ICallService { string memory _to, bytes memory _data, bytes memory _rollback - ) external payable returns ( - uint256 - ); + ) external payable returns (uint256); function sendCallMessage( string memory _to, @@ -57,9 +45,12 @@ interface ICallService { bytes memory _rollback, string[] memory sources, string[] memory destinations - ) external payable returns ( - uint256 - ); + ) external payable returns (uint256); + + function sendCall( + string memory _to, + bytes memory _data + ) external payable returns (uint256); /** @notice Notifies that the requested call message has been sent. @@ -79,34 +70,25 @@ interface ICallService { @param _code The execution result code (0: Success, -1: Unknown generic failure, >=1: User defined error code) */ - event ResponseMessage( - uint256 indexed _sn, - int _code - ); + event ResponseMessage(uint256 indexed _sn, int _code); /** @notice Notifies the user that a rollback operation is required for the request '_sn'. @param _sn The serial number of the previous request */ - event RollbackMessage( - uint256 indexed _sn - ); + event RollbackMessage(uint256 indexed _sn); /** @notice Rollbacks the caller state of the request '_sn'. @param _sn The serial number of the previous request */ - function executeRollback( - uint256 _sn - ) external; + function executeRollback(uint256 _sn) external; /** @notice Notifies that the rollback has been executed. @param _sn The serial number for the rollback */ - event RollbackExecuted( - uint256 indexed _sn - ); + event RollbackExecuted(uint256 indexed _sn); /*======== At the destination CALL_BSH ========*/ /** @@ -130,10 +112,7 @@ interface ICallService { @param _reqId The request id @param _data The calldata */ - function executeCall( - uint256 _reqId, - bytes memory _data - ) external; + function executeCall(uint256 _reqId, bytes memory _data) external; /** @notice Notifies that the call message has been executed. @@ -142,28 +121,18 @@ interface ICallService { (0: Success, -1: Unknown generic failure) @param _msg The result message if any */ - event CallExecuted( - uint256 indexed _reqId, - int _code, - string _msg - ); + event CallExecuted(uint256 indexed _reqId, int _code, string _msg); - /** + /** @notice BTP Message from other blockchain. @param _from Network Address of source network @param _msg Serialized bytes of ServiceMessage */ - function handleMessage( - string calldata _from, - bytes calldata _msg - ) external; + function handleMessage(string calldata _from, bytes calldata _msg) external; /** @notice Handle the error on delivering the message. @param _sn Serial number of the original message */ - function handleError( - uint256 _sn - ) external; + function handleError(uint256 _sn) external; } - diff --git a/contracts/evm/library/btp2/interfaces/ICallServiceReceiver.sol b/contracts/evm/library/xcall/interfaces/ICallServiceReceiver.sol similarity index 100% rename from contracts/evm/library/btp2/interfaces/ICallServiceReceiver.sol rename to contracts/evm/library/xcall/interfaces/ICallServiceReceiver.sol diff --git a/contracts/evm/library/xcall/interfaces/ICallServiceV1.sol b/contracts/evm/library/xcall/interfaces/ICallServiceV1.sol new file mode 100644 index 00000000..64aba7b2 --- /dev/null +++ b/contracts/evm/library/xcall/interfaces/ICallServiceV1.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +interface ICallServiceV1 { + function getNetworkAddress() external view returns (string memory); + + function getNetworkId() external view returns (string memory); + + /** + @notice Gets the fee for delivering a message to the _net. + If the sender is going to provide rollback data, the _rollback param should set as true. + The returned fee is the sum of the protocol fee and the relay fee. + @param _net (String) The destination network address + @param _rollback (Bool) Indicates whether it provides rollback data + @return (Integer) the sum of the protocol fee and the relay fee + */ + function getFee( + string memory _net, + bool _rollback + ) external view returns (uint256); + + function getFee( + string memory _net, + bool _rollback, + string[] memory _sources + ) external view returns (uint256); + + /*======== At the source CALL_BSH ========*/ + /** + @notice Sends a call message to the contract on the destination chain. + @param _to The BTP address of the callee on the destination chain + @param _data The calldata specific to the target contract + @param _rollback (Optional) The data for restoring the caller state when an error occurred + @return The serial number of the request + */ + function sendCallMessage( + string memory _to, + bytes memory _data, + bytes memory _rollback + ) external payable returns (uint256); + + function sendCallMessage( + string memory _to, + bytes memory _data, + bytes memory _rollback, + string[] memory sources, + string[] memory destinations + ) external payable returns (uint256); + + /** + @notice Notifies that the requested call message has been sent. + @param _from The chain-specific address of the caller + @param _to The BTP address of the callee on the destination chain + @param _sn The serial number of the request + */ + event CallMessageSent( + address indexed _from, + string indexed _to, + uint256 indexed _sn + ); + + /** + @notice Notifies that a response message has arrived for the `_sn` if the request was a two-way message. + @param _sn The serial number of the previous request + @param _code The execution result code + (0: Success, -1: Unknown generic failure, >=1: User defined error code) + */ + event ResponseMessage(uint256 indexed _sn, int _code); + + /** + @notice Notifies the user that a rollback operation is required for the request '_sn'. + @param _sn The serial number of the previous request + */ + event RollbackMessage(uint256 indexed _sn); + + /** + @notice Rollbacks the caller state of the request '_sn'. + @param _sn The serial number of the previous request + */ + function executeRollback(uint256 _sn) external; + + /** + @notice Notifies that the rollback has been executed. + @param _sn The serial number for the rollback + */ + event RollbackExecuted(uint256 indexed _sn); + + /*======== At the destination CALL_BSH ========*/ + /** + @notice Notifies the user that a new call message has arrived. + @param _from The BTP address of the caller on the source chain + @param _to A string representation of the callee address + @param _sn The serial number of the request from the source + @param _reqId The request id of the destination chain + @param _data The calldata + */ + event CallMessage( + string indexed _from, + string indexed _to, + uint256 indexed _sn, + uint256 _reqId, + bytes _data + ); + + /** + @notice Executes the requested call message. + @param _reqId The request id + @param _data The calldata + */ + function executeCall(uint256 _reqId, bytes memory _data) external; + + /** + @notice Notifies that the call message has been executed. + @param _reqId The request id for the call message + @param _code The execution result code + (0: Success, -1: Unknown generic failure) + @param _msg The result message if any + */ + event CallExecuted(uint256 indexed _reqId, int _code, string _msg); + + /** + @notice BTP Message from other blockchain. + @param _from Network Address of source network + @param _msg Serialized bytes of ServiceMessage + */ + function handleMessage(string calldata _from, bytes calldata _msg) external; + + /** + @notice Handle the error on delivering the message. + @param _sn Serial number of the original message + */ + function handleError(uint256 _sn) external; +} diff --git a/contracts/evm/library/btp2/interfaces/IDefaultCallServiceReceiver.sol b/contracts/evm/library/xcall/interfaces/IDefaultCallServiceReceiver.sol similarity index 100% rename from contracts/evm/library/btp2/interfaces/IDefaultCallServiceReceiver.sol rename to contracts/evm/library/xcall/interfaces/IDefaultCallServiceReceiver.sol diff --git a/contracts/evm/library/btp2/utils/Integers.sol b/contracts/evm/library/xcall/utils/Integers.sol similarity index 100% rename from contracts/evm/library/btp2/utils/Integers.sol rename to contracts/evm/library/xcall/utils/Integers.sol diff --git a/contracts/evm/library/btp2/utils/NetworkAddress.sol b/contracts/evm/library/xcall/utils/NetworkAddress.sol similarity index 100% rename from contracts/evm/library/btp2/utils/NetworkAddress.sol rename to contracts/evm/library/xcall/utils/NetworkAddress.sol diff --git a/contracts/evm/library/btp2/utils/ParseAddress.sol b/contracts/evm/library/xcall/utils/ParseAddress.sol similarity index 100% rename from contracts/evm/library/btp2/utils/ParseAddress.sol rename to contracts/evm/library/xcall/utils/ParseAddress.sol diff --git a/contracts/evm/library/btp2/utils/RLPDecode.sol b/contracts/evm/library/xcall/utils/RLPDecode.sol similarity index 100% rename from contracts/evm/library/btp2/utils/RLPDecode.sol rename to contracts/evm/library/xcall/utils/RLPDecode.sol diff --git a/contracts/evm/library/btp2/utils/RLPEncode.sol b/contracts/evm/library/xcall/utils/RLPEncode.sol similarity index 100% rename from contracts/evm/library/btp2/utils/RLPEncode.sol rename to contracts/evm/library/xcall/utils/RLPEncode.sol diff --git a/contracts/evm/library/btp2/utils/Strings.sol b/contracts/evm/library/xcall/utils/Strings.sol similarity index 100% rename from contracts/evm/library/btp2/utils/Strings.sol rename to contracts/evm/library/xcall/utils/Strings.sol diff --git a/contracts/evm/remappings.txt b/contracts/evm/remappings.txt index a81d7a27..2ca2e6f4 100644 --- a/contracts/evm/remappings.txt +++ b/contracts/evm/remappings.txt @@ -1,5 +1,5 @@ @xcall/contracts/=./contracts/ -@iconfoundation/btp2-solidity-library/=./library/btp2/ +@iconfoundation/xcall-solidity-library/=./library/xcall/ @xcall/utils/=./library/utils/ ds-test/=lib/forge-std/lib/ds-test/src/ erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/ diff --git a/contracts/evm/test/adapters/CentralizedConnection.t.sol b/contracts/evm/test/adapters/CentralizedConnection.t.sol index 22d74e4e..a6750974 100644 --- a/contracts/evm/test/adapters/CentralizedConnection.t.sol +++ b/contracts/evm/test/adapters/CentralizedConnection.t.sol @@ -10,8 +10,7 @@ import "@xcall/utils/Types.sol"; contract CentralizedConnectionTest is Test { using RLPEncodeStruct for Types.CSMessage; - using RLPEncodeStruct for Types.CSMessageRequest; - using RLPEncodeStruct for Types.CSMessageResponse; + using RLPEncodeStruct for Types.CSMessageRequestV2; event CallExecuted(uint256 indexed _reqId, int _code, string _msg); @@ -127,17 +126,17 @@ contract CentralizedConnectionTest is Test { nidSource, "0xa" ); - Types.CSMessageRequest memory request = Types.CSMessageRequest( + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( iconDapp, ParseAddress.toString(address(dappSource)), 1, - false, + Types.CALL_MESSAGE_TYPE, data, new string[](0) ); Types.CSMessage memory message = Types.CSMessage( Types.CS_REQUEST, - request.encodeCSMessageRequest() + request.encodeCSMessageRequestV2() ); vm.startPrank(destination_relayer); @@ -155,17 +154,17 @@ contract CentralizedConnectionTest is Test { nidSource, "0xa" ); - Types.CSMessageRequest memory request = Types.CSMessageRequest( + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( iconDapp, ParseAddress.toString(address(dappSource)), 1, - false, + Types.CALL_MESSAGE_TYPE, data, new string[](0) ); Types.CSMessage memory message = Types.CSMessage( Types.CS_REQUEST, - request.encodeCSMessageRequest() + request.encodeCSMessageRequestV2() ); vm.startPrank(user); @@ -184,17 +183,17 @@ contract CentralizedConnectionTest is Test { nidSource, "0xa" ); - Types.CSMessageRequest memory request = Types.CSMessageRequest( + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( iconDapp, ParseAddress.toString(address(dappSource)), 1, - false, + Types.CALL_MESSAGE_TYPE, data, new string[](0) ); Types.CSMessage memory message = Types.CSMessage( Types.CS_REQUEST, - request.encodeCSMessageRequest() + request.encodeCSMessageRequestV2() ); vm.startPrank(destination_relayer); @@ -215,6 +214,7 @@ contract CentralizedConnectionTest is Test { function testRevertMessage() public { vm.startPrank(destination_relayer); + vm.expectRevert("CallRequestNotFound"); adapterTarget.revertMessage(1); vm.stopPrank(); } @@ -279,17 +279,17 @@ contract CentralizedConnectionTest is Test { nidSource, "0xa" ); - Types.CSMessageRequest memory request = Types.CSMessageRequest( + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( iconDapp, ParseAddress.toString(address(dappSource)), 1, - false, + Types.CALL_MESSAGE_TYPE, data, new string[](0) ); Types.CSMessage memory message = Types.CSMessage( Types.CS_REQUEST, - request.encodeCSMessageRequest() + request.encodeCSMessageRequestV2() ); assert(adapterTarget.getReceipt(nidSource, 1) == false); diff --git a/contracts/evm/test/xcall/CallService.t.sol b/contracts/evm/test/xcall/CallService.t.sol index c9026a4f..365f5c88 100644 --- a/contracts/evm/test/xcall/CallService.t.sol +++ b/contracts/evm/test/xcall/CallService.t.sol @@ -7,20 +7,35 @@ import "@xcall/contracts/xcall/interfaces/IConnection.sol"; import "@xcall/utils/Types.sol"; import "@xcall/contracts/mocks/dapp/DAppProxySample.sol"; -import "@iconfoundation/btp2-solidity-library/utils/NetworkAddress.sol"; -import "@iconfoundation/btp2-solidity-library/utils/ParseAddress.sol"; -import "@iconfoundation/btp2-solidity-library/utils/Integers.sol"; -import "@iconfoundation/btp2-solidity-library/utils/Strings.sol"; +import "@iconfoundation/xcall-solidity-library/utils/NetworkAddress.sol"; +import "@iconfoundation/xcall-solidity-library/utils/ParseAddress.sol"; +import "@iconfoundation/xcall-solidity-library/utils/Integers.sol"; +import "@iconfoundation/xcall-solidity-library/utils/Strings.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallServiceReceiver.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/IDefaultCallServiceReceiver.sol"; -import "@iconfoundation/btp2-solidity-library/interfaces/ICallService.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallServiceReceiver.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/IDefaultCallServiceReceiver.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; +contract ResponseContract { + string public to; + bytes public data; + + function setData(string memory _to, bytes memory _data) public { + to = _to; + data = _data; + } + + function handleCallMessage(string memory _from, bytes memory _data, string[] memory protocols) public { + console.log("handleCallMessage"); + ICallService(msg.sender).sendCall(to, data); + } +} contract CallServiceTest is Test { CallService public callService; DAppProxySample public dapp; + ResponseContract public responseContract; IConnection public baseConnection; IConnection public connection1; @@ -35,8 +50,9 @@ contract CallServiceTest is Test { using ParseAddress for string; using NetworkAddress for string; using RLPEncodeStruct for Types.CSMessage; - using RLPEncodeStruct for Types.CSMessageRequest; - using RLPEncodeStruct for Types.CSMessageResponse; + using RLPEncodeStruct for Types.CSMessageRequestV2; + using RLPEncodeStruct for Types.CSMessageResult; + using RLPEncodeStruct for Types.XCallEnvelope; using RLPDecodeStruct for bytes; address public owner = address(0x1111); @@ -105,6 +121,7 @@ contract CallServiceTest is Test { callService = new CallService(); callService.initialize(ethNid); + responseContract = new ResponseContract(); } function testSetAdmin() public { @@ -157,14 +174,13 @@ contract CallServiceTest is Test { vm.expectEmit(); emit CallMessageSent(address(dapp), iconDapp, 1); - Types.CSMessageRequest memory request = Types.CSMessageRequest(ethDappAddress, dstAccount, 1, false, data, _baseDestination); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST, request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(ethDappAddress, dstAccount, 1, 0, data, _baseDestination); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST, request.encodeCSMessageRequestV2()); vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, 0, message.encodeCSMessage()))); uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); assertEq(sn, 1); - } function testSendMessageMultiProtocol() public { @@ -188,8 +204,8 @@ contract CallServiceTest is Test { vm.expectEmit(); emit CallMessageSent(address(dapp), iconDapp, 1); - Types.CSMessageRequest memory request = Types.CSMessageRequest(ethDappAddress, dstAccount, 1, false, data, destinations); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(ethDappAddress, dstAccount, 1, 0, data, destinations); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.expectCall(address(connection1), abi.encodeCall(connection1.sendMessage, (iconNid, Types.NAME, 0, message.encodeCSMessage()))); vm.expectCall(address(connection2), abi.encodeCall(connection2.sendMessage, (iconNid, Types.NAME, 0, message.encodeCSMessage()))); @@ -199,6 +215,50 @@ contract CallServiceTest is Test { assertEq(sn, 1); } + function testHandleReply() public { + bytes memory data = bytes("test"); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, address(dapp).toString(), 1, Types.PERSISTENT_MESSAGE_TYPE, data, _baseSource); + Types.CSMessageResult memory result = Types.CSMessageResult(1, Types.CS_RESP_SUCCESS,request.encodeCSMessageRequestV2()); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT,result.encodeCSMessageResult()); + + vm.prank(address(dapp)); + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, data, _baseSource, _baseDestination); + assertEq(sn, 1); + + vm.expectEmit(); + emit ResponseMessage(1, Types.CS_RESP_SUCCESS); + emit CallMessage(iconDapp, address(dapp).toString(), 1, 1, data); + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + } + + function testHandleReplyInvalidTo() public { + bytes memory data = bytes("test"); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2("otherNid/0x1", address(dapp).toString(), 1, Types.PERSISTENT_MESSAGE_TYPE, data, _baseSource); + Types.CSMessageResult memory result = Types.CSMessageResult(1, Types.CS_RESP_SUCCESS,request.encodeCSMessageRequestV2()); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT,result.encodeCSMessageResult()); + + vm.prank(address(dapp)); + uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, data, _baseSource, _baseDestination); + assertEq(sn, 1); + + vm.expectRevert("Invalid Reply"); + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + } + function testSendMessageDefaultProtocol() public { bytes memory data = bytes("test"); bytes memory rollbackData = bytes("rollback"); @@ -208,8 +268,8 @@ contract CallServiceTest is Test { vm.expectEmit(); emit CallMessageSent(address(dapp), iconDapp, 1); - Types.CSMessageRequest memory request = Types.CSMessageRequest(ethDappAddress, dstAccount, 1, true, data, new string[](0)); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(ethDappAddress, dstAccount, 1, 1, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, 1, message.encodeCSMessage()))); vm.prank(address(dapp)); @@ -217,6 +277,126 @@ contract CallServiceTest is Test { assertEq(sn, 1); } + function testSendMessagePersistent() public { + bytes memory data = bytes("test"); + + bytes memory _msg = Types.createPersistentMessage(data, new string[](0), new string[](0)); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + vm.expectEmit(); + emit CallMessageSent(address(dapp), iconDapp, 1); + + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(ethDappAddress, dstAccount, 1, Types.PERSISTENT_MESSAGE_TYPE, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, 0, message.encodeCSMessage()))); + + vm.prank(address(dapp)); + uint256 sn = callService.sendCall{value: 0 ether}(iconDapp, _msg); + assertEq(sn, 1); + } + + function testSendMessageResponse() public { + bytes memory data = bytes("test"); + bytes memory data2 = bytes("test2"); + + bytes memory _msg = Types.createPersistentMessage(data2, _baseSource, _baseDestination); + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, address(responseContract).toString(), 1, Types.CALL_MESSAGE_ROLLBACK_TYPE, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, message.encodeCSMessage()); + + (string memory nid, string memory iconDappAddress) = iconDapp.parseNetworkAddress(); + + Types.CSMessageRequestV2 memory expectedRequest = Types.CSMessageRequestV2(NetworkAddress.networkAddress(ethNid, address(responseContract).toString()), iconDappAddress, 1, Types.PERSISTENT_MESSAGE_TYPE, data2, _baseDestination); + + responseContract.setData(iconDapp, _msg); + + Types.CSMessageResult memory result = Types.CSMessageResult(1, Types.CS_RESP_SUCCESS,expectedRequest.encodeCSMessageRequestV2()); + Types.CSMessage memory response = Types.CSMessage(Types.CS_RESULT,result.encodeCSMessageResult()); + + vm.expectEmit(); + emit CallMessageSent(address(responseContract), iconDapp, 1); + + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, -1, response.encodeCSMessage()))); + callService.executeCall(1, data); + } + + function testSendMessageResponseAnotherNetwork() public { + bytes memory data = bytes("test"); + bytes memory data2 = bytes("test2"); + + string memory bscNid = "0x61.bsc"; + string memory bscDapp = "bscaddress"; + + callService.setDefaultConnection(iconNid, address(baseConnection)); + callService.setDefaultConnection(bscNid, address(baseConnection)); + + bytes memory _msg = Types.createPersistentMessage(data2, _baseSource, _baseDestination); + + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, address(responseContract).toString(), 1, Types.CALL_MESSAGE_ROLLBACK_TYPE, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, message.encodeCSMessage()); + + (string memory nid, string memory iconDappAddress) = iconDapp.parseNetworkAddress(); + + Types.CSMessageRequestV2 memory expectedRequest = Types.CSMessageRequestV2(NetworkAddress.networkAddress(ethNid, address(responseContract).toString()), bscDapp, 1, Types.PERSISTENT_MESSAGE_TYPE, data2, _baseDestination); + + responseContract.setData(NetworkAddress.networkAddress(bscNid, bscDapp), _msg); + + Types.CSMessageResult memory result = Types.CSMessageResult(1, Types.CS_RESP_SUCCESS,bytes("")); + Types.CSMessage memory response = Types.CSMessage(Types.CS_RESULT,result.encodeCSMessageResult()); + + Types.CSMessage memory message2 = Types.CSMessage(Types.CS_REQUEST,expectedRequest.encodeCSMessageRequestV2()); + + vm.expectEmit(); + emit CallMessageSent(address(responseContract), responseContract.to(), 1); + + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, -1, response.encodeCSMessage()))); + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (bscNid, Types.NAME, 0, message2.encodeCSMessage()))); + callService.executeCall(1, data); + } + + function testSendMessageResponseTwoWayMessage() public { + + callService.setDefaultConnection(iconNid, address(baseConnection)); + + bytes memory data1 = bytes("test1"); + bytes memory data2 = bytes("test2"); + + bytes memory _msg = Types.createCallMessageWithRollback(data2, data2, _baseSource, _baseDestination); + + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, address(responseContract).toString(), 1, Types.CALL_MESSAGE_ROLLBACK_TYPE, data1, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, message.encodeCSMessage()); + + (string memory nid, string memory iconDappAddress) = iconDapp.parseNetworkAddress(); + + Types.CSMessageRequestV2 memory expectedRequest = Types.CSMessageRequestV2(NetworkAddress.networkAddress(ethNid, address(responseContract).toString()), iconDappAddress, 1, Types.CALL_MESSAGE_ROLLBACK_TYPE, data2, _baseDestination); + + responseContract.setData(iconDapp, _msg); + + Types.CSMessageResult memory result = Types.CSMessageResult(1, Types.CS_RESP_SUCCESS, bytes("")); + Types.CSMessage memory response = Types.CSMessage(Types.CS_RESULT,result.encodeCSMessageResult()); + + Types.CSMessage memory message2 = Types.CSMessage(Types.CS_REQUEST,expectedRequest.encodeCSMessageRequestV2()); + + vm.expectEmit(); + emit CallMessageSent(address(responseContract), iconDapp, 1); + + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, -1, response.encodeCSMessage()))); + vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, 1, message2.encodeCSMessage()))); + callService.executeCall(1, data1); + } + function testSendMessageDefaultProtocolNotSet() public { bytes memory data = bytes("test"); bytes memory rollbackData = bytes(""); @@ -230,8 +410,8 @@ contract CallServiceTest is Test { callService.setDefaultConnection(iconNid, address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.expectEmit(); emit CallMessage(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data); @@ -245,8 +425,8 @@ contract CallServiceTest is Test { callService.setDefaultConnection(iconNid, address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(baseConnection)); vm.expectRevert("InvalidServiceName"); @@ -258,8 +438,8 @@ contract CallServiceTest is Test { callService.setDefaultConnection(iconNid, address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.expectEmit(); emit CallMessage(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data); @@ -272,8 +452,8 @@ contract CallServiceTest is Test { bytes memory data = bytes("test"); callService.setDefaultConnection(iconNid, address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(baseConnection)); vm.expectRevert("Invalid Network ID"); @@ -284,8 +464,8 @@ contract CallServiceTest is Test { bytes memory data = bytes("test"); callService.setDefaultConnection(iconNid, address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, new string[](0)); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data, new string[](0)); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(user)); vm.expectRevert("NotAuthorized"); @@ -298,8 +478,8 @@ contract CallServiceTest is Test { string[] memory sources = new string[](1); sources[0] = ParseAddress.toString(address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, sources); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data, sources); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(baseConnection)); vm.expectEmit(); @@ -314,8 +494,8 @@ contract CallServiceTest is Test { string[] memory sources = new string[](1); sources[0] = ParseAddress.toString(address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, sources); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data, sources); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(connection1)); vm.expectRevert("NotAuthorized"); @@ -336,8 +516,8 @@ contract CallServiceTest is Test { connections[0] = ParseAddress.toString(address(connection1)); connections[1] = ParseAddress.toString(address(connection2)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(dapp)), 1, false, data, connections); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(dapp)), 1, 1, data, connections); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(connection1)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -351,8 +531,8 @@ contract CallServiceTest is Test { function testExecuteCallSingleProtocol() public { bytes memory data = bytes("test"); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(receiver)), 1, false, data, _baseSource); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(receiver)), 1, 1, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(baseConnection)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -371,8 +551,8 @@ contract CallServiceTest is Test { defaultServiceReceiver = IDefaultCallServiceReceiver(address(0x5678)); callService.setDefaultConnection(netTo, address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(defaultServiceReceiver)), 1, false, data, _baseSource); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(defaultServiceReceiver)), 1, 1, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(baseConnection)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -384,7 +564,29 @@ contract CallServiceTest is Test { vm.mockCall(address(defaultServiceReceiver), abi.encodeWithSelector(defaultServiceReceiver.handleCallMessage.selector, iconDapp, data), abi.encode(1)); callService.executeCall(1, data); } + + function testExecuteCallPersistent() public { + bytes memory data = bytes("test"); + + defaultServiceReceiver = IDefaultCallServiceReceiver(address(0x5678)); + callService.setDefaultConnection(netTo, address(baseConnection)); + + string[] memory source; + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(defaultServiceReceiver)), 1, Types.PERSISTENT_MESSAGE_TYPE, data, source); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); + + vm.prank(address(baseConnection)); + callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); + vm.mockCall(address(defaultServiceReceiver), abi.encodeWithSelector(defaultServiceReceiver.handleCallMessage.selector, iconDapp, data), abi.encode(1)); + vm.prank(user); + callService.executeCall(1, data); + + vm.expectRevert("InvalidRequestId"); + vm.prank(user); + callService.executeCall(1, data); + } + function testExecuteCallMultiProtocol() public { bytes memory data = bytes("test"); @@ -399,8 +601,8 @@ contract CallServiceTest is Test { vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(0)); vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(0)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(receiver)), 1, false, data, connections); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(receiver)), 1, 1, data, connections); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(connection1)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -427,8 +629,8 @@ contract CallServiceTest is Test { uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); assertEq(sn, 1); - Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); - Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + Types.CSMessageResult memory response = Types.CSMessageResult(1, Types.CS_RESP_FAILURE,bytes("")); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(response)); vm.expectEmit(); emit ResponseMessage(1, Types.CS_RESP_FAILURE); @@ -453,8 +655,8 @@ contract CallServiceTest is Test { uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); assertEq(sn, 1); - Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); - Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + Types.CSMessageResult memory response = Types.CSMessageResult(1, Types.CS_RESP_FAILURE,bytes("")); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(response)); vm.expectEmit(); emit ResponseMessage(1, Types.CS_RESP_FAILURE); @@ -478,8 +680,8 @@ contract CallServiceTest is Test { uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); assertEq(sn, 1); - Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); - Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + Types.CSMessageResult memory response = Types.CSMessageResult(1, Types.CS_RESP_FAILURE,bytes("")); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(response)); vm.prank(address(user)); vm.expectRevert("NotAuthorized"); @@ -513,8 +715,8 @@ contract CallServiceTest is Test { uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, connections, destinations); assertEq(sn, 1); - Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); - Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + Types.CSMessageResult memory response = Types.CSMessageResult(1, Types.CS_RESP_FAILURE,bytes("")); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(response)); vm.prank(address(connection1)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -539,8 +741,8 @@ contract CallServiceTest is Test { uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); assertEq(sn, 1); - Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_SUCCESS); - Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + Types.CSMessageResult memory response = Types.CSMessageResult(1, Types.CS_RESP_SUCCESS,bytes("")); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(response)); vm.expectEmit(); emit ResponseMessage(1, Types.CS_RESP_SUCCESS); @@ -569,8 +771,8 @@ contract CallServiceTest is Test { assertEq(sn, 1); vm.stopPrank(); - Types.CSMessageResponse memory msgRes = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); - Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, msgRes.encodeCSMessageResponse()); + Types.CSMessageResult memory msgRes = Types.CSMessageResult(1, Types.CS_RESP_FAILURE,bytes("")); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT, msgRes.encodeCSMessageResult()); vm.prank(address(baseConnection)); callService.handleMessage(iconNid, message.encodeCSMessage()); @@ -598,8 +800,8 @@ contract CallServiceTest is Test { uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, _baseSource, _baseDestination); assertEq(sn, 1); - Types.CSMessageResponse memory msgRes = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); - Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, msgRes.encodeCSMessageResponse()); + Types.CSMessageResult memory msgRes = Types.CSMessageResult(1, Types.CS_RESP_FAILURE,bytes("")); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT, msgRes.encodeCSMessageResult()); vm.prank(address(baseConnection)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -641,8 +843,8 @@ contract CallServiceTest is Test { uint256 sn = callService.sendCallMessage{value: 0 ether}(iconDapp, data, rollbackData, connections, destinations); assertEq(sn, 1); - Types.CSMessageResponse memory response = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); - Types.CSMessage memory message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(response)); + Types.CSMessageResult memory response = Types.CSMessageResult(1, Types.CS_RESP_FAILURE,bytes("")); + Types.CSMessage memory message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(response)); vm.prank(address(connection1)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -675,8 +877,8 @@ contract CallServiceTest is Test { vm.mockCall(address(connection1), abi.encodeWithSelector(connection1.getFee.selector), abi.encode(0)); vm.mockCall(address(connection2), abi.encodeWithSelector(connection2.getFee.selector), abi.encode(0)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(receiver)), 1, true, data, connections); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(receiver)), 1, 1, data, connections); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(connection1)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -690,8 +892,8 @@ contract CallServiceTest is Test { vm.prank(user); vm.mockCall(address(receiver), abi.encodeWithSelector(receiver.handleCallMessage.selector, iconDapp, data, connections), abi.encode(1)); - Types.CSMessageResponse memory msgResponse = Types.CSMessageResponse(1, Types.CS_RESP_SUCCESS); - message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(msgResponse)); + Types.CSMessageResult memory msgResponse = Types.CSMessageResult(1, Types.CS_RESP_SUCCESS,bytes("")); + message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(msgResponse)); vm.expectCall(address(connection1), abi.encodeCall(connection1.sendMessage, (iconNid, Types.NAME, -1, message.encodeCSMessage()))); vm.expectCall(address(connection2), abi.encodeCall(connection2.sendMessage, (iconNid, Types.NAME, -1, message.encodeCSMessage()))); @@ -705,8 +907,8 @@ contract CallServiceTest is Test { defaultServiceReceiver = IDefaultCallServiceReceiver(address(0x5678)); callService.setDefaultConnection(netTo, address(baseConnection)); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(defaultServiceReceiver)), 1, true, data, _baseSource); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(defaultServiceReceiver)), 1, 1, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(baseConnection)); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); @@ -717,8 +919,8 @@ contract CallServiceTest is Test { vm.prank(user); vm.mockCall(address(defaultServiceReceiver), abi.encodeWithSelector(defaultServiceReceiver.handleCallMessage.selector, iconDapp, data), abi.encode(0)); - Types.CSMessageResponse memory msgResponse = Types.CSMessageResponse(1, Types.CS_RESP_SUCCESS); - message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(msgResponse)); + Types.CSMessageResult memory msgResponse = Types.CSMessageResult(1, Types.CS_RESP_SUCCESS,bytes("")); + message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(msgResponse)); vm.expectCall(address(baseConnection), abi.encodeCall(baseConnection.sendMessage, (iconNid, Types.NAME, -1, message.encodeCSMessage()))); callService.executeCall(1, data); } @@ -727,15 +929,15 @@ contract CallServiceTest is Test { function testExecuteCallFailedExecution() public { bytes memory data = bytes("test"); - Types.CSMessageRequest memory request = Types.CSMessageRequest(iconDapp, ParseAddress.toString(address(receiver)), 1, true, data, _baseSource); - Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequest()); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2(iconDapp, ParseAddress.toString(address(receiver)), 1, 1, data, _baseSource); + Types.CSMessage memory message = Types.CSMessage(Types.CS_REQUEST,request.encodeCSMessageRequestV2()); vm.prank(address(baseConnection)); vm.mockCallRevert(address(baseConnection), abi.encodeWithSelector(receiver.handleCallMessage.selector, iconDapp, data, _baseSource), bytes("UserRevert")); callService.handleMessage(iconNid, RLPEncodeStruct.encodeCSMessage(message)); - Types.CSMessageResponse memory msgResponse = Types.CSMessageResponse(1, Types.CS_RESP_FAILURE); - message = Types.CSMessage(Types.CS_RESPONSE, RLPEncodeStruct.encodeCSMessageResponse(msgResponse)); + Types.CSMessageResult memory msgResponse = Types.CSMessageResult(1, Types.CS_RESP_FAILURE, bytes("")); + message = Types.CSMessage(Types.CS_RESULT, RLPEncodeStruct.encodeCSMessageResult(msgResponse)); vm.expectEmit(); emit CallExecuted(1, 0, "unknownError"); diff --git a/contracts/javascore/example-dapps/dapp-multi-protocol/src/main/java/xcall/sample/dapp/MultiProtocolSampleDapp.java b/contracts/javascore/example-dapps/dapp-multi-protocol/src/main/java/xcall/sample/dapp/MultiProtocolSampleDapp.java index 88cb02d2..caca9df2 100644 --- a/contracts/javascore/example-dapps/dapp-multi-protocol/src/main/java/xcall/sample/dapp/MultiProtocolSampleDapp.java +++ b/contracts/javascore/example-dapps/dapp-multi-protocol/src/main/java/xcall/sample/dapp/MultiProtocolSampleDapp.java @@ -28,6 +28,11 @@ import score.annotation.Payable; import foundation.icon.xcall.CallServiceReceiver; import foundation.icon.xcall.NetworkAddress; +import foundation.icon.xcall.messages.Message; +import foundation.icon.xcall.messages.CallMessage; +import foundation.icon.xcall.messages.CallMessageWithRollback; +import foundation.icon.xcall.messages.XCallEnvelope; +import foundation.icon.xcall.messages.PersistentMessage; public class MultiProtocolSampleDapp implements CallServiceReceiver { private final Address callSvc; @@ -58,7 +63,6 @@ public String[] getDestinations(String nid) { return toArray(this.destinations.at(nid)); } - public String[] toArray(ArrayDB db) { int size = db.size(); String[] arr = new String[size]; @@ -69,6 +73,40 @@ public String[] toArray(ArrayDB db) { return arr; } + @Payable + @External + public void sendNewMessage(String _to, byte[] _data, int messageType, @Optional byte[] _rollback) { + String net = NetworkAddress.valueOf(_to).net(); + + Message msg; + XCallEnvelope envelope; + if (messageType == PersistentMessage.TYPE) { + msg = new PersistentMessage(_data); + envelope = new XCallEnvelope(msg, getSources(net), getDestinations(net)); + _sendCall(Context.getValue(), _to, envelope.toBytes()); + } else if (messageType == CallMessage.TYPE) { + msg = new CallMessage(_data); + envelope = new XCallEnvelope(msg, getSources(net), getDestinations(net)); + _sendCall(Context.getValue(), _to, envelope.toBytes()); + } else if (messageType == CallMessageWithRollback.TYPE) { + msg = new CallMessageWithRollback(_data, _rollback); + envelope = new XCallEnvelope(msg, getSources(net), getDestinations(net)); + _sendCall(Context.getValue(), _to, envelope.toBytes()); + } else { + Context.revert("invalid message type"); + } + } + + @Payable + @External + public void sendMessageAny(String _to, byte[] _data) { + _sendCall(Context.getValue(), _to, _data); + } + + private BigInteger _sendCall(BigInteger value, String to, byte[] envelope) { + return Context.call(BigInteger.class, value, this.callSvc, "sendCall", to, envelope); + } + @Payable @External public void sendMessage(String _to, byte[] _data, @Optional byte[] _rollback) { @@ -77,7 +115,8 @@ public void sendMessage(String _to, byte[] _data, @Optional byte[] _rollback) { private BigInteger _sendCallMessage(BigInteger value, String to, byte[] data, byte[] rollback) { String net = NetworkAddress.valueOf(to).net(); - return Context.call(BigInteger.class, value, this.callSvc, "sendCallMessage", to, data, rollback, getSources(net), getDestinations(net)); + return Context.call(BigInteger.class, value, this.callSvc, "sendCallMessage", to, data, rollback, + getSources(net), getDestinations(net)); } @External @@ -92,12 +131,15 @@ public void handleCallMessage(String _from, byte[] _data, String[] protocols) { Context.require(equals(protocols, getSources(from.net())), "invalid protocols"); Context.require(!new String(_data).equals("rollback"), "failed"); - // normal message delivery + + if (new String(_data).equals("reply-response")) { + // response message + _sendCallMessage(BigInteger.ZERO, _from, new byte[] { 1, 2, 3 }, null); + } MessageReceived(_from, _data); } } - @EventLog public void MessageReceived(String _from, byte[] _data) { } diff --git a/contracts/javascore/example-dapps/dapp-simple/src/main/java/xcall/sample/dapp/SimpleDapp.java b/contracts/javascore/example-dapps/dapp-simple/src/main/java/xcall/sample/dapp/SimpleDapp.java index 7a769a28..35e21715 100644 --- a/contracts/javascore/example-dapps/dapp-simple/src/main/java/xcall/sample/dapp/SimpleDapp.java +++ b/contracts/javascore/example-dapps/dapp-simple/src/main/java/xcall/sample/dapp/SimpleDapp.java @@ -29,6 +29,12 @@ import foundation.icon.xcall.DefaultCallServiceReceiver; import foundation.icon.xcall.NetworkAddress; +import foundation.icon.xcall.messages.Message; +import foundation.icon.xcall.messages.CallMessage; +import foundation.icon.xcall.messages.CallMessageWithRollback; +import foundation.icon.xcall.messages.XCallEnvelope; +import foundation.icon.xcall.messages.PersistentMessage; + public class SimpleDapp implements DefaultCallServiceReceiver { private final Address callSvc; @@ -40,6 +46,27 @@ private void onlyCallService() { Context.require(Context.getCaller().equals(this.callSvc), "onlyCallService"); } + @Payable + @External + public void sendNewMessage(String _to, byte[] _data, @Optional byte[] _rollback, @Optional boolean isPersistent) { + Message msg; + if (isPersistent) { + msg = new PersistentMessage(_data); + } else if (_rollback == null || _rollback.length == 0) { + msg = new CallMessage(_data); + } else { + msg = new CallMessageWithRollback(_data, _rollback); + } + String[] sources = new String[0]; + String[] destinations = new String[0]; + XCallEnvelope envelope = new XCallEnvelope(msg, sources, destinations); + _sendCall(Context.getValue(), _to, envelope.toBytes()); + } + + private BigInteger _sendCall(BigInteger value, String to, byte[] envelope) { + return Context.call(BigInteger.class, value, this.callSvc, "sendCall", to, envelope); + } + @Payable @External public void sendMessage(String _to, byte[] _data, @Optional byte[] _rollback) { diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessage.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessage.java index 46e921cb..24b9198a 100644 --- a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessage.java +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessage.java @@ -23,7 +23,7 @@ public class CSMessage { public static final int REQUEST = 1; - public static final int RESPONSE = 2; + public static final int RESULT = 2; private final int type; private final byte[] data; diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageRequest.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageRequest.java index c2f64bf3..241b347c 100644 --- a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageRequest.java +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageRequest.java @@ -29,16 +29,16 @@ public class CSMessageRequest { private final String from; private final String to; private final BigInteger sn; - private final boolean rollback; + private final int type; private byte[] data; - private final String[] protocols; + private String[] protocols; - public CSMessageRequest(String from, String to, BigInteger sn, boolean rollback, byte[] data, String[] protocols) { + public CSMessageRequest(String from, String to, BigInteger sn, int type, byte[] data, String[] protocols) { this.from = from; this.to = to; this.sn = sn; - this.rollback = rollback; + this.type = type; this.data = data; if (protocols == null) { protocols = new String[]{}; @@ -55,6 +55,10 @@ public String getTo() { return to; } + public void setProtocols(String[] protocols) { + this.protocols = protocols; + } + public String[] getProtocols() { return protocols; } @@ -63,8 +67,8 @@ public BigInteger getSn() { return sn; } - public boolean needRollback() { - return rollback; + public int getType() { + return type; } public byte[] getData() { @@ -81,7 +85,7 @@ public static void writeObject(ObjectWriter w, CSMessageRequest m) { w.write(m.to); w.write(m.sn); - w.write(m.rollback); + w.write(m.type); w.writeNullable(m.data); w.beginList(m.protocols.length); for(String protocol : m.protocols) { @@ -97,7 +101,7 @@ public static CSMessageRequest readObject(ObjectReader r) { r.readString(), r.readString(), r.readBigInteger(), - r.readBoolean(), + r.readInt(), r.readNullable(byte[].class), readProtocols(r) ); @@ -134,4 +138,9 @@ public static CSMessageRequest fromBytes(byte[] bytes) { ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes); return readObject(reader); } + + + public void setData(byte[] data) { + this.data = data; + } } diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageResponse.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageResult.java similarity index 66% rename from contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageResponse.java rename to contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageResult.java index 6b0e4fe9..faf7885c 100644 --- a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageResponse.java +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CSMessageResult.java @@ -16,23 +16,25 @@ package foundation.icon.xcall; +import java.math.BigInteger; + import score.ByteArrayObjectWriter; import score.Context; import score.ObjectReader; import score.ObjectWriter; -import java.math.BigInteger; - -public class CSMessageResponse { +public class CSMessageResult { public static final int SUCCESS = 1; public static final int FAILURE = 0; private final BigInteger sn; private final int code; + private final byte[] msg; - public CSMessageResponse(BigInteger sn, int code) { + public CSMessageResult(BigInteger sn, int code, byte[] msg) { this.sn = sn; this.code = code; + this.msg = msg; } public BigInteger getSn() { @@ -43,30 +45,38 @@ public int getCode() { return code; } - public static void writeObject(ObjectWriter w, CSMessageResponse m) { - w.beginList(2); + public byte[] getMsg() { + return msg; + } + + public static void writeObject(ObjectWriter w, CSMessageResult m) { + w.beginList(3); w.write(m.sn); w.write(m.code); + w.writeNullable(m.msg); w.end(); } - public static CSMessageResponse readObject(ObjectReader r) { + public static CSMessageResult readObject(ObjectReader r) { r.beginList(); - CSMessageResponse m = new CSMessageResponse( - r.readBigInteger(), - r.readInt() - ); + BigInteger sn = r.readBigInteger(); + int code = r.readInt(); + byte[] msg = null; + if (r.hasNext()) { + msg = r.readNullable(byte[].class); + } + r.end(); - return m; + return new CSMessageResult(sn, code, msg); } public byte[] toBytes() { ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn"); - CSMessageResponse.writeObject(writer, this); + CSMessageResult.writeObject(writer, this); return writer.toByteArray(); } - public static CSMessageResponse fromBytes(byte[] bytes) { + public static CSMessageResult fromBytes(byte[] bytes) { ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes); return readObject(reader); } diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CallRequest.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/RollbackData.java similarity index 89% rename from contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CallRequest.java rename to contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/RollbackData.java index d50ffab6..b63dbd7f 100644 --- a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/CallRequest.java +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/RollbackData.java @@ -23,14 +23,14 @@ import score.ObjectWriter; import scorex.util.ArrayList; -public class CallRequest { +public class RollbackData { private final Address from; private final String to; private final String[] protocols; private final byte[] rollback; private boolean enabled; - public CallRequest(Address from, String to, String[] protocols, byte[] rollback) { + public RollbackData(Address from, String to, String[] protocols, byte[] rollback) { this.from = from; this.to = to; if (protocols == null) { @@ -57,7 +57,7 @@ public byte[] getRollback() { return rollback; } - public static void writeObject(ObjectWriter w, CallRequest req) { + public static void writeObject(ObjectWriter w, RollbackData req) { w.beginList(5); w.write(req.from); w.write(req.to); @@ -71,9 +71,9 @@ public static void writeObject(ObjectWriter w, CallRequest req) { w.end(); } - public static CallRequest readObject(ObjectReader r) { + public static RollbackData readObject(ObjectReader r) { r.beginList(); - CallRequest req = new CallRequest( + RollbackData req = new RollbackData( r.readAddress(), r.readString(), readProtocols(r), diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/CallMessage.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/CallMessage.java new file mode 100644 index 00000000..cf645d3c --- /dev/null +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/CallMessage.java @@ -0,0 +1,27 @@ + +package foundation.icon.xcall.messages; + +public class CallMessage extends Message { + public static final int TYPE = 0; + private byte[] data; + + public CallMessage(byte[] data) { + this.data = data; + } + + public int getType() { + return TYPE; + } + + public byte[] getData() { + return data; + } + + public byte[] toBytes() { + return data; + } + + public static CallMessage fromBytes(byte[] bytes) { + return new CallMessage(bytes); + } +} \ No newline at end of file diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/CallMessageWithRollback.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/CallMessageWithRollback.java new file mode 100644 index 00000000..37cc6306 --- /dev/null +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/CallMessageWithRollback.java @@ -0,0 +1,55 @@ +package foundation.icon.xcall.messages; + +import score.ByteArrayObjectWriter; +import score.Context; +import score.ObjectReader; +import score.ObjectWriter; + +public class CallMessageWithRollback extends Message { + public static final int TYPE = 1; + private byte[] data; + private byte[] rollback; + public CallMessageWithRollback(byte[] data, byte[] rollback) { + this.data = data; + this.rollback = rollback; + } + + public int getType() { + return TYPE; + } + + public byte[] getData() { + return data; + } + + public byte[] getRollback() { + return rollback; + } + + public static void writeObject(ObjectWriter w, CallMessageWithRollback call) { + w.beginList(2); + w.write(call.data); + w.write(call.rollback); + w.end(); + } + + public static CallMessageWithRollback readObject(ObjectReader r) { + r.beginList(); + CallMessageWithRollback call = new CallMessageWithRollback( + r.readByteArray(), + r.readByteArray() + ); + return call; + } + + public byte[] toBytes() { + ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn"); + CallMessageWithRollback.writeObject(writer, this); + return writer.toByteArray(); + } + + public static CallMessageWithRollback fromBytes(byte[] bytes) { + ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes); + return readObject(reader); + } +} diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/Message.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/Message.java new file mode 100644 index 00000000..d7869bb3 --- /dev/null +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/Message.java @@ -0,0 +1,7 @@ +package foundation.icon.xcall.messages; + +public abstract class Message { + public abstract int getType(); + + public abstract byte[] toBytes(); +} diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/PersistentMessage.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/PersistentMessage.java new file mode 100644 index 00000000..ffcf41a1 --- /dev/null +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/PersistentMessage.java @@ -0,0 +1,27 @@ + +package foundation.icon.xcall.messages; + +public class PersistentMessage extends Message { + public static final int TYPE = 2; + private byte[] data; + + public PersistentMessage(byte[] data) { + this.data = data; + } + + public int getType() { + return TYPE; + } + + public byte[] getData() { + return data; + } + + public byte[] toBytes() { + return data; + } + + public static CallMessage fromBytes(byte[] bytes) { + return new CallMessage(bytes); + } +} \ No newline at end of file diff --git a/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/XCallEnvelope.java b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/XCallEnvelope.java new file mode 100644 index 00000000..d48808e9 --- /dev/null +++ b/contracts/javascore/xcall-lib/src/main/java/foundation/icon/xcall/messages/XCallEnvelope.java @@ -0,0 +1,107 @@ +package foundation.icon.xcall.messages; + +import java.util.List; + +import score.ByteArrayObjectWriter; +import score.Context; +import score.ObjectReader; +import score.ObjectWriter; +import scorex.util.ArrayList; + +public class XCallEnvelope { + public int type; + public byte[] message; + public String[] sources = new String[]{}; + public String[] destinations = new String[]{};; + + + public XCallEnvelope(int type, byte[] message, String[] sources, String[] destinations) { + this.type = type; + this.message = message; + this.sources = sources; + this.destinations = destinations; + } + + public XCallEnvelope(Message message, String[] sources, String[] destinations) { + this.type = message.getType(); + this.message = message.toBytes(); + this.sources = sources; + this.destinations = destinations; + } + + public XCallEnvelope(Message message) { + this.type = message.getType(); + this.message = message.toBytes(); + } + + public int getType() { + return type; + } + + public byte[] getMessage() { + return message; + } + + public String[] getSources() { + return sources; + } + + public String[] getDestinations() { + return destinations; + } + + public static void writeObject(ObjectWriter w, XCallEnvelope envelope) { + w.beginList(3); + w.write(envelope.type); + w.write(envelope.message); + w.beginList(envelope.sources.length); + for(String protocol : envelope.sources) { + w.write(protocol); + } + w.end(); + w.beginList(envelope.destinations.length); + for(String protocol : envelope.destinations) { + w.write(protocol); + } + w.end(); + w.end(); + } + + public static XCallEnvelope readObject(ObjectReader r) { + r.beginList(); + XCallEnvelope call = new XCallEnvelope( + r.readInt(), + r.readByteArray(), + readProtocols(r), + readProtocols(r) + ); + return call; + } + + private static String[] readProtocols(ObjectReader r) { + r.beginList(); + List protocolsList = new ArrayList<>(); + while(r.hasNext()) { + protocolsList.add(r.readString()); + } + int size = protocolsList.size(); + String[] protocols = new String[size]; + for(int i=0; i < size; i++) { + protocols[i] = protocolsList.get(i); + } + r.end(); + return protocols; + } + + public byte[] toBytes() { + ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn"); + XCallEnvelope.writeObject(writer, this); + return writer.toByteArray(); + } + + public static XCallEnvelope fromBytes(byte[] bytes) { + ObjectReader reader = Context.newByteArrayObjectReader("RLPn", bytes); + return readObject(reader); + } + +} diff --git a/contracts/javascore/xcall/src/main/java/foundation/icon/xcall/CallServiceImpl.java b/contracts/javascore/xcall/src/main/java/foundation/icon/xcall/CallServiceImpl.java index f69e4934..adefe448 100644 --- a/contracts/javascore/xcall/src/main/java/foundation/icon/xcall/CallServiceImpl.java +++ b/contracts/javascore/xcall/src/main/java/foundation/icon/xcall/CallServiceImpl.java @@ -20,8 +20,6 @@ import score.BranchDB; import score.Context; import score.DictDB; -import score.RevertedException; -import score.UserRevertedException; import score.VarDB; import score.annotation.EventLog; import score.annotation.External; @@ -31,27 +29,51 @@ import java.math.BigInteger; import java.util.Arrays; +import foundation.icon.xcall.messages.CallMessage; +import foundation.icon.xcall.messages.CallMessageWithRollback; +import foundation.icon.xcall.messages.Message; +import foundation.icon.xcall.messages.PersistentMessage; +import foundation.icon.xcall.messages.XCallEnvelope; public class CallServiceImpl implements CallService, FeeManage { public static final int MAX_DATA_SIZE = 2048; public static final int MAX_ROLLBACK_SIZE = 1024; public static String NID; - private final VarDB sn = Context.newVarDB("sn", BigInteger.class); - private final VarDB reqId = Context.newVarDB("reqId", BigInteger.class); - - private final DictDB requests = Context.newDictDB("requests", CallRequest.class); - private final DictDB proxyReqs = Context.newDictDB("proxyReqs", CSMessageRequest.class); - private final BranchDB> pendingReqs = Context.newBranchDB("pendingReqs", Boolean.class); - private final BranchDB> pendingResponses = Context.newBranchDB("pendingResponses", Boolean.class); - private final DictDB successfulResponses = Context.newDictDB("successfulResponses", Boolean.class); - - private final DictDB defaultConnection = Context.newDictDB("defaultConnection", Address.class); + public static final String SN = "sn"; + public static final String REQUEST_ID = "reqId"; + public static final String ROLLBACKS = "requests"; + public static final String PROXY_REQUESTS = "proxyReqs"; + public static final String PENDING_REQUESTS = "pendingReqs"; + public static final String PENDING_RESPONSES = "pendingResponses"; + public static final String SUCCESSFUL_RESPONSES = "successfulResponses"; + public static final String DEFAULT_CONNECTION = "defaultConnection"; + public static final String ADMIN = "admin"; + public static final String PROTOCOL_FEE = "protocolFee"; + public static final String FEE_HANDLER = "feeHandler"; + + private final VarDB sn = Context.newVarDB(SN, BigInteger.class); + private final VarDB reqId = Context.newVarDB(REQUEST_ID, BigInteger.class); + + private final DictDB rollbacks = Context.newDictDB(ROLLBACKS, RollbackData.class); + private final DictDB proxyReqs = Context.newDictDB(PROXY_REQUESTS, + CSMessageRequest.class); + private final BranchDB> pendingReqs = Context.newBranchDB(PENDING_REQUESTS, + Boolean.class); + private final BranchDB> pendingResponses = Context.newBranchDB(PENDING_RESPONSES, + Boolean.class); + private final DictDB successfulResponses = Context.newDictDB(SUCCESSFUL_RESPONSES, + Boolean.class); + + private final DictDB defaultConnection = Context.newDictDB(DEFAULT_CONNECTION, Address.class); // for fee-related operations - private final VarDB
admin = Context.newVarDB("admin", Address.class); - private final VarDB protocolFee = Context.newVarDB("protocolFee", BigInteger.class); - private final VarDB
feeHandler = Context.newVarDB("feeHandler", Address.class); + private final VarDB
admin = Context.newVarDB(ADMIN, Address.class); + private final VarDB protocolFee = Context.newVarDB(PROTOCOL_FEE, BigInteger.class); + private final VarDB
feeHandler = Context.newVarDB(FEE_HANDLER, Address.class); + + private static CSMessageRequest replyState = null; + private static byte[] callReply = null; public CallServiceImpl(String networkId) { NID = networkId; @@ -91,64 +113,63 @@ private BigInteger getNextReqId() { } private void cleanupCallRequest(BigInteger sn) { - requests.set(sn, null); + rollbacks.set(sn, null); } - @Override @Payable @External - public BigInteger sendCallMessage(String _to, - byte[] _data, - @Optional byte[] _rollback, - @Optional String[] _sources, - @Optional String[] _destinations) { + public BigInteger sendCall(String _to, byte[] _data) { Address caller = Context.getCaller(); - // check if caller is a contract or rollback data is null in case of EOA - Context.require(_rollback == null || caller.isContract(), "RollbackNotPossible"); - // check size of payloads to avoid abusing - Context.require(_rollback == null || _rollback.length <= MAX_ROLLBACK_SIZE, "MaxRollbackSizeExceeded"); - - boolean needResponse = _rollback != null && _rollback.length > 0; + XCallEnvelope envelope = XCallEnvelope.fromBytes(_data); + BigInteger sn = getNextSn(); NetworkAddress dst = NetworkAddress.valueOf(_to); - BigInteger sn = getNextSn(); - if (needResponse) { - CallRequest req = new CallRequest(caller, dst.net(), _sources, _rollback); - requests.set(sn, req); - } + ProcessResult result = preProcessMessage(sn, dst, envelope); String from = new NetworkAddress(NID, caller.toString()).toString(); - CSMessageRequest msgReq = new CSMessageRequest(from, dst.account(), sn, needResponse, _data, _destinations); + CSMessageRequest msgReq = new CSMessageRequest(from, dst.account(), sn, envelope.getType(), result.data, + envelope.getDestinations()); byte[] msgBytes = msgReq.toBytes(); Context.require(msgBytes.length <= MAX_DATA_SIZE, "MaxDataSizeExceeded"); - - if (_sources == null || _sources.length == 0) { - Address src = defaultConnection.get(dst.net()); - Context.require(src != null, "NoDefaultConnection"); - BigInteger fee = Context.call(BigInteger.class, src, "getFee", dst.net(), needResponse); - sendMessage(src, fee, dst.net(), CSMessage.REQUEST, - needResponse ? sn : BigInteger.ZERO, msgBytes); + if (isReply(dst.net, envelope.sources) && !result.needResponse) { + replyState = null; + callReply = msgBytes; } else { - for (String _src : _sources) { - Address src = Address.fromString(_src); - BigInteger fee = Context.call(BigInteger.class, src, "getFee", dst.net(), needResponse); - sendMessage(src, fee, dst.net(), CSMessage.REQUEST, - needResponse ? sn : BigInteger.ZERO, msgBytes); - } - + BigInteger sendSn = result.needResponse ? sn : BigInteger.ZERO; + sendMessage(envelope.getSources(), dst.net(), CSMessage.REQUEST, sendSn, msgBytes); + claimProtocolFee(); } - BigInteger protocolFee = getProtocolFee(); - BigInteger balance = Context.getBalance(Context.getAddress()); - Context.require(balance.compareTo(protocolFee) >= 0, "InsufficientBalance"); - Context.transfer(feeHandler.get(), balance); - CallMessageSent(caller, dst.toString(), sn); return sn; } + @Payable + @External + public BigInteger sendCallMessage(String _to, + byte[] _data, + @Optional byte[] _rollback, + @Optional String[] _sources, + @Optional String[] _destinations) { + + if (_sources == null || _destinations == null) { + _sources = new String[0]; + _destinations = new String[0]; + } + + Message msg; + if (_rollback == null || _rollback.length == 0) { + msg = new CallMessage(_data); + } else { + msg = new CallMessageWithRollback(_data, _rollback); + } + + XCallEnvelope envelope = new XCallEnvelope(msg, _sources, _destinations); + return sendCall(_to, envelope.toBytes()); + } + @Override @External public void executeCall(BigInteger _reqId, byte[] _data) { @@ -158,55 +179,22 @@ public void executeCall(BigInteger _reqId, byte[] _data) { proxyReqs.set(_reqId, null); // compare the given data hash with the saved one Context.require(Arrays.equals(getDataHash(_data), req.getData()), "DataHashMismatch"); - - NetworkAddress from = NetworkAddress.valueOf(req.getFrom()); - CSMessageResponse msgRes = null; - String msg = ""; - - try { - Address to = Address.fromString(req.getTo()); - sendToDapp(to, req.getFrom(), _data, req.getProtocols()); - msgRes = new CSMessageResponse(req.getSn(), CSMessageResponse.SUCCESS); - } catch (UserRevertedException e) { - int code = e.getCode(); - msg = "UserReverted(" + code + ")"; - msgRes = new CSMessageResponse(req.getSn(), CSMessageResponse.FAILURE); - } catch (IllegalArgumentException | RevertedException e) { - msgRes = new CSMessageResponse(req.getSn(), CSMessageResponse.FAILURE); - msg = e.toString(); - } finally { - if (msgRes == null) { - msgRes = new CSMessageResponse(req.getSn(), CSMessageResponse.FAILURE); - msg = "UnknownFailure"; - } - CallExecuted(_reqId, msgRes.getCode(), msg); - // send response only when there was a rollback - if (!req.needRollback()) { - return; - } - - BigInteger sn = req.getSn().negate(); - if (req.getProtocols().length == 0) { - Address src = defaultConnection.get(from.net()); - Context.require(src != null, "NoDefaultConnection"); - sendMessage(src, BigInteger.ZERO, from.net(), CSMessage.RESPONSE, sn, msgRes.toBytes()); - } else { - for (String protocol : req.getProtocols()) { - sendMessage(Address.fromString(protocol), BigInteger.ZERO, from.net(), CSMessage.RESPONSE, sn, msgRes.toBytes()); - } - } - } + executeMessage(_reqId, req, _data); } @Override @External public void executeRollback(BigInteger _sn) { - CallRequest req = requests.get(_sn); + RollbackData req = rollbacks.get(_sn); Context.require(req != null, "InvalidSerialNum"); Context.require(req.enabled(), "RollbackNotEnabled"); cleanupCallRequest(_sn); - - sendToDapp(req.getFrom(), getNetworkAddress(), req.getRollback(), req.getProtocols()); + String[] protocols = req.getProtocols(); + if (protocols.length == 0) { + Context.call(req.getFrom(), "handleCallMessage", getNetworkAddress(), req.getRollback()); + } else { + Context.call(req.getFrom(), "handleCallMessage", getNetworkAddress(), req.getRollback(), protocols); + } RollbackExecuted(_sn); } @@ -216,6 +204,107 @@ public boolean verifySuccess(BigInteger _sn) { return successfulResponses.getOrDefault(_sn, false); } + /* ========== Interfaces with BMC ========== */ + @External + public void handleBTPMessage(String _from, String _svc, BigInteger _sn, byte[] _msg) { + handleMessage(_from, _msg); + } + + @External + public void handleBTPError(String _src, String _svc, BigInteger _sn, long _code, String _msg) { + handleError(_sn); + } + /* ========================================= */ + + @Override + @External + public void handleMessage(String _fromNid, byte[] _msg) { + CSMessage msg = CSMessage.fromBytes(_msg); + Context.require(!_fromNid.equals(NID), "Invalid network ID"); + switch (msg.getType()) { + case CSMessage.REQUEST: + handleRequest(_fromNid, msg.getData()); + break; + case CSMessage.RESULT: + handleResult(msg.getData()); + break; + default: + Context.revert("UnknownMsgType(" + msg.getType() + ")"); + } + } + + @Override + @External + public void handleError(BigInteger _sn) { + CSMessageResult res = new CSMessageResult(_sn, CSMessageResult.FAILURE, null); + handleResult(res.toBytes()); + } + + @External(readonly = true) + public Address admin() { + return admin.get(); + } + + @External + public void setAdmin(Address _address) { + checkCallerOrThrow(admin(), "OnlyAdmin"); + admin.set(_address); + } + + @External + public void setProtocolFee(BigInteger _protocolFee) { + checkCallerOrThrow(admin(), "OnlyAdmin"); + Context.require(_protocolFee.signum() >= 0, "ValueShouldBePositive"); + protocolFee.set(_protocolFee); + } + + @External + public void setProtocolFeeHandler(Address _address) { + checkCallerOrThrow(admin(), "OnlyAdmin"); + feeHandler.set(_address); + } + + @External + public void setDefaultConnection(String _nid, Address _connection) { + checkCallerOrThrow(admin(), "OnlyAdmin"); + defaultConnection.set(_nid, _connection); + } + + @External + public void getDefaultConnection(String _nid, Address _connection) { + checkCallerOrThrow(admin(), "OnlyAdmin"); + defaultConnection.set(_nid, _connection); + } + + @External(readonly = true) + public BigInteger getProtocolFee() { + return protocolFee.getOrDefault(BigInteger.ZERO); + } + + @External(readonly = true) + public BigInteger getFee(String _net, boolean _rollback, @Optional String[] _sources) { + BigInteger fee = getProtocolFee(); + if (_sources == null) { + _sources = new String[] {}; + } + + if (isReply(_net, _sources) && !_rollback) { + return BigInteger.ZERO; + } + if (_sources == null || _sources.length == 0) { + Address src = defaultConnection.get(_net); + Context.require(src != null, "NoDefaultConnection"); + return fee.add(Context.call(BigInteger.class, src, "getFee", _net, _rollback)); + } + + for (String protocol : _sources) { + Address address = Address.fromString(protocol); + fee = fee.add(Context.call(BigInteger.class, address, "getFee", _net, _rollback)); + } + + return fee; + } + @Override @EventLog(indexed = 3) public void CallMessage(String _from, String _to, BigInteger _sn, BigInteger _reqId, byte[] _data) { @@ -246,183 +335,233 @@ public void RollbackExecuted(BigInteger _sn) { public void CallMessageSent(Address _from, String _to, BigInteger _sn) { } - /* ========== Interfaces with BMC ========== */ - @External - public void handleBTPMessage(String _from, String _svc, BigInteger _sn, byte[] _msg) { - handleMessage(_from, _msg); - } - - @External - public void handleBTPError(String _src, String _svc, BigInteger _sn, long _code, String _msg) { - handleError(_sn); + private void sendMessage(String[] _sources, String netTo, int msgType, BigInteger sn, byte[] data) { + Address[] sources = prepareProtocols(_sources, netTo); + CSMessage msg = new CSMessage(msgType, data); + BigInteger value; + for (Address src : sources) { + value = _getFee(src, netTo, sn); + Context.call(value, src, "sendMessage", netTo, NAME, sn, msg.toBytes()); + } } - /* ========================================= */ - - @Override - @External - public void handleMessage(String _fromNid, byte[] _msg) { - CSMessage msg = CSMessage.fromBytes(_msg); - Context.require(!_fromNid.equals(NID), "Invalid network ID"); - switch (msg.getType()) { - case CSMessage.REQUEST: - handleRequest(_fromNid, msg.getData()); - break; - case CSMessage.RESPONSE: - handleResponse(msg.getData()); - break; - default: - Context.revert("UnknownMsgType(" + msg.getType() + ")"); + private BigInteger _getFee(Address conn, String net, BigInteger sn) { + if (sn.signum() == -1) { + return BigInteger.ZERO; } - } - @Override - @External - public void handleError(BigInteger _sn) { - CSMessageResponse res = new CSMessageResponse(_sn, CSMessageResponse.FAILURE); - handleResponse(res.toBytes()); + return Context.call(BigInteger.class, conn, "getFee", net, sn.signum() == 1); } - private BigInteger sendMessage(Address _connection, BigInteger value, String netTo, int msgType, BigInteger sn, byte[] data) { - CSMessage msg = new CSMessage(msgType, data); - ConnectionScoreInterface connection = new ConnectionScoreInterface(_connection); - return connection.sendMessage(value, netTo, NAME, sn, msg.toBytes()); + private int tryExecuteCall(BigInteger id, Address dapp, String from, byte[] data, String[] protocols) { + try { + _executeCall(id, dapp, from, data, protocols); + } catch (Exception e) { + CallExecuted(id, CSMessageResult.FAILURE, e.toString()); + return CSMessageResult.FAILURE; + } + ; + + return CSMessageResult.SUCCESS; } - private void sendToDapp(Address dapp, String from, byte[] data, String[] protocols) { + private void _executeCall(BigInteger id, Address dapp, String from, byte[] data, String[] protocols) { if (protocols.length == 0) { Context.call(dapp, "handleCallMessage", from, data); } else { Context.call(dapp, "handleCallMessage", from, data, protocols); } + CallExecuted(id, CSMessageResult.SUCCESS, ""); + } + + private Address[] prepareProtocols(String[] protocols, String toNid) { + if (protocols.length == 0) { + Address src = defaultConnection.get(toNid); + Context.require(src != null, "NoDefaultConnection"); + return new Address[] { src }; + } + + Address[] _protocols = new Address[protocols.length]; + for (int i = 0; i < protocols.length; i++) { + _protocols[i] = Address.fromString(protocols[i]); + } + + return _protocols; + } + + private class ProcessResult { + public boolean needResponse; + public byte[] data; + + public ProcessResult(boolean needResponse, byte[] data) { + this.needResponse = needResponse; + this.data = data; + } + } + + private ProcessResult preProcessMessage(BigInteger sn, NetworkAddress to, XCallEnvelope envelope) { + switch (envelope.getType()) { + case CallMessage.TYPE: + case PersistentMessage.TYPE: + return new ProcessResult(false, envelope.getMessage()); + case CallMessageWithRollback.TYPE: + Address caller = Context.getCaller(); + CallMessageWithRollback msg = CallMessageWithRollback.fromBytes(envelope.getMessage()); + Context.require(caller.isContract(), "RollbackNotPossible"); + RollbackData req = new RollbackData(caller, to.net(), envelope.getSources(), msg.getRollback()); + rollbacks.set(sn, req); + return new ProcessResult(true, msg.getData()); + } + + Context.revert("Message type is not supported"); + return null; + } + + private void executeMessage(BigInteger reqId, CSMessageRequest req, byte[] data) { + Address to = Address.fromString(req.getTo()); + String[] protocols = req.getProtocols(); + switch (req.getType()) { + case CallMessage.TYPE: + tryExecuteCall(reqId, to, req.getFrom(), data, protocols); + break; + case PersistentMessage.TYPE: + _executeCall(reqId, to, req.getFrom(), data, protocols); + break; + case CallMessageWithRollback.TYPE: { + replyState = req; + int code = tryExecuteCall(reqId, to, req.getFrom(), data, protocols); + replyState = null; + BigInteger sn = req.getSn().negate(); + NetworkAddress from = NetworkAddress.valueOf(req.getFrom()); + byte[] message = null; + if (callReply != null && code == CSMessageResult.SUCCESS) { + message = callReply; + callReply = null; + } + + CSMessageResult response = new CSMessageResult(req.getSn(), code, message); + sendMessage(protocols, from.net(), CSMessage.RESULT, sn, response.toBytes()); + break; + } + default: + Context.revert("Message type is not yet supported"); + } + } + + private void claimProtocolFee() { + BigInteger protocolFee = getProtocolFee(); + BigInteger balance = Context.getBalance(Context.getAddress()); + Context.require(balance.compareTo(protocolFee) >= 0, "InsufficientBalance"); + Context.transfer(feeHandler.get(), balance); } private void handleRequest(String netFrom, byte[] data) { CSMessageRequest msgReq = CSMessageRequest.fromBytes(data); String from = msgReq.getFrom(); Context.require(NetworkAddress.valueOf(from).net().equals(netFrom)); - Address caller = Context.getCaller(); - if (!verifyProtocols(pendingReqs, netFrom,msgReq.getProtocols(), caller, data)) { + byte[] hash = Context.hash("sha-256", data); + DictDB pending = pendingReqs.at(hash); + if (!verifyProtocols(netFrom, msgReq.getProtocols(), pending)) { return; } - String to = msgReq.getTo(); BigInteger reqId = getNextReqId(); // emit event to notify the user - CallMessage(from, to, msgReq.getSn(), reqId, msgReq.getData()); + CallMessage(from, msgReq.getTo(), msgReq.getSn(), reqId, msgReq.getData()); msgReq.hashData(); proxyReqs.set(reqId, msgReq); } - private void handleResponse(byte[] data) { - CSMessageResponse msgRes = CSMessageResponse.fromBytes(data); + private void handleReply(RollbackData rollback, CSMessageRequest reply) { + Context.require(rollback.getTo().equals(NetworkAddress.valueOf(reply.getFrom()).net), "Invalid Reply"); + reply.setProtocols(rollback.getProtocols()); + + BigInteger reqId = getNextReqId(); + CallMessage(reply.getFrom(), reply.getTo(), reply.getSn(), reqId, reply.getData()); + reply.hashData(); + proxyReqs.set(reqId, reply); + } + + private void handleResult(byte[] data) { + CSMessageResult msgRes = CSMessageResult.fromBytes(data); BigInteger resSn = msgRes.getSn(); - CallRequest req = requests.get(resSn); - Address caller = Context.getCaller(); + RollbackData rollback = rollbacks.get(resSn); - if (req == null) { - Context.println("handleResponse: no request for " + resSn); - return; // just ignore - } + Context.require(rollback != null, "CallRequest Not Found For " + resSn.toString()); - if (!verifyProtocols(pendingResponses, req.getTo(), req.getProtocols(), caller, data)) { + byte[] hash = Context.hash("sha-256", data); + DictDB pending = pendingResponses.at(hash); + if (!verifyProtocols(rollback.getTo(), rollback.getProtocols(), pending)) { return; } ResponseMessage(resSn, msgRes.getCode()); switch (msgRes.getCode()) { - case CSMessageResponse.SUCCESS: + case CSMessageResult.SUCCESS: cleanupCallRequest(resSn); + if (msgRes.getMsg() != null) { + handleReply(rollback, CSMessageRequest.fromBytes(msgRes.getMsg())); + } successfulResponses.set(resSn, true); break; - case CSMessageResponse.FAILURE: + case CSMessageResult.FAILURE: default: // emit rollback event - Context.require(req.getRollback() != null, "NoRollbackData"); - req.setEnabled(); - requests.set(resSn, req); + Context.require(rollback.getRollback() != null, "NoRollbackData"); + rollback.setEnabled(); + rollbacks.set(resSn, rollback); RollbackMessage(resSn); } } - private boolean verifyProtocols(BranchDB> db, String net, String[] protocols, Address caller, byte[] data) { - if (protocols.length > 1) { - byte[] hash = Context.hash("sha-256", data); - DictDB pending = db.at(hash); - pending.set(caller.toString(), true); - for (String protocol : protocols) { - if (!pending.getOrDefault(protocol, false)) { - return false; - } - } - - for (String protocol : protocols) { - pending.set(protocol, null); - } + private boolean verifyProtocols(String fromNid, String[] protocols, DictDB pendingDb) { + Address caller = Context.getCaller(); + if (protocols.length == 0) { + Context.require(caller.equals(defaultConnection.get(fromNid)), "ProtocolSourceMismatch"); + return true; } else if (protocols.length == 1) { Context.require(caller.toString().equals(protocols[0]), "ProtocolSourceMismatch"); - } else { - Context.require(caller.equals(defaultConnection.get(net)), "ProtocolSourceMismatch"); + return true; } - return true; - } - - private byte[] getDataHash(byte[] data) { - return Context.hash("keccak-256", data); - } - @External(readonly = true) - public Address admin() { - return admin.get(); - } - @External - public void setAdmin(Address _address) { - checkCallerOrThrow(admin(), "OnlyAdmin"); - admin.set(_address); - } + pendingDb.set(caller.toString(), true); + for (String protocol : protocols) { + if (!pendingDb.getOrDefault(protocol, false)) { + return false; + } + } - @External - public void setProtocolFee(BigInteger _protocolFee) { - checkCallerOrThrow(admin(), "OnlyAdmin"); - Context.require(_protocolFee.signum() >= 0, "ValueShouldBePositive"); - protocolFee.set(_protocolFee); - } + for (String protocol : protocols) { + pendingDb.set(protocol, null); + } - @External - public void setProtocolFeeHandler(Address _address) { - checkCallerOrThrow(admin(), "OnlyAdmin"); - feeHandler.set(_address); - } - @External - public void setDefaultConnection(String _nid, Address _connection) { - checkCallerOrThrow(admin(), "OnlyAdmin"); - defaultConnection.set(_nid, _connection); + return true; } + private boolean isReply(String netId, String[] sources) { + if (replyState != null) { + return NetworkAddress.valueOf(replyState.getFrom()).net.equals(netId) + && protocolEquals(replyState.getProtocols(), sources); + } - @External(readonly = true) - public BigInteger getProtocolFee() { - return protocolFee.getOrDefault(BigInteger.ZERO); + return false; } - @External(readonly = true) - public BigInteger getFee(String _net, boolean _rollback, @Optional String[] _sources) { - BigInteger fee = getProtocolFee(); - if (_sources == null || _sources.length == 0) { - Address src = defaultConnection.get(_net); - Context.require(src != null, "NoDefaultConnection"); - return fee.add(Context.call(BigInteger.class, src, "getFee", _net, _rollback)); + private boolean protocolEquals(String[] a, String[] b) { + for (int i = 0; i < b.length; i++) { + if (!a[i].equals(b[i])) { + return false; + } } - for (String protocol : _sources) { - Address address = Address.fromString(protocol); - fee = fee.add(Context.call(BigInteger.class, address, "getFee", _net, _rollback)); - } + return true; + } - return fee; + private byte[] getDataHash(byte[] data) { + return Context.hash("keccak-256", data); } } diff --git a/contracts/javascore/xcall/src/test/java/foundation/icon/xcall/CallServiceTest.java b/contracts/javascore/xcall/src/test/java/foundation/icon/xcall/CallServiceTest.java index ecf9134d..2ea35e7f 100644 --- a/contracts/javascore/xcall/src/test/java/foundation/icon/xcall/CallServiceTest.java +++ b/contracts/javascore/xcall/src/test/java/foundation/icon/xcall/CallServiceTest.java @@ -16,14 +16,21 @@ import java.math.BigInteger; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; 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.xcall.messages.CallMessageWithRollback; +import foundation.icon.xcall.messages.Message; +import foundation.icon.xcall.messages.PersistentMessage; +import foundation.icon.xcall.messages.XCallEnvelope; +import score.Context; import score.UserRevertedException; import xcall.icon.test.MockContract; @@ -42,10 +49,22 @@ public class CallServiceTest extends TestBase { protected String baseEthConnection = "0xb"; protected MockContract dapp; protected MockContract baseConnection; + protected Score responseContract; String[] baseSource; String[] baseDestination; + public static class ResponseContract implements CallServiceReceiver { + public static String to = ""; + public static byte[] data = new byte[0]; + + public ResponseContract() { + } + public void handleCallMessage(String _from, byte[] _data, String[] protocols) { + Context.call(Context.getCaller(), "sendCall", to, data); + + } + } @BeforeEach public void setup() throws Exception { @@ -58,6 +77,8 @@ public void setup() throws Exception { xcall = sm.deploy(owner, CallServiceImpl.class, nid); xcallSpy = (CallServiceImpl) spy(xcall.getInstance()); xcall.setInstance(xcallSpy); + responseContract = sm.deploy(owner, ResponseContract.class); + } @Test @@ -69,7 +90,8 @@ public void sendMessage_singleProtocol() { xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, null, baseSource, baseDestination); // Assert - CSMessageRequest request = new CSMessageRequest(iconDappAddress.toString(), ethDapp.account.toString(), BigInteger.ONE, false, data, new String[]{baseEthConnection}); + CSMessageRequest request = new CSMessageRequest(iconDappAddress.toString(), ethDapp.account.toString(), BigInteger.ONE, 0, data, baseDestination); + CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); verify(baseConnection.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ZERO), aryEq(msg.toBytes())); verify(xcallSpy).CallMessageSent(dapp.getAddress(), ethDapp.toString(), BigInteger.ONE); @@ -85,7 +107,7 @@ public void sendMessage_defaultProtocol() { xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data); // Assert - CSMessageRequest request = new CSMessageRequest(iconDappAddress.toString(), ethDapp.account.toString(), BigInteger.ONE, false, data, null); + CSMessageRequest request = new CSMessageRequest(iconDappAddress.toString(), ethDapp.account.toString(), BigInteger.ONE, 0, data, null); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); verify(baseConnection.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ZERO), aryEq(msg.toBytes())); verify(xcallSpy).CallMessageSent(dapp.getAddress(), ethDapp.toString(), BigInteger.ONE); @@ -117,18 +139,126 @@ public void sendMessage_multiProtocol() throws Exception { xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, null, sources, destinations); // Assert - CSMessageRequest request = new CSMessageRequest(iconDappAddress.toString(), ethDapp.account.toString(), BigInteger.ONE, false, data, destinations); + CSMessageRequest request = new CSMessageRequest(iconDappAddress.toString(), ethDapp.account.toString(), BigInteger.ONE, 0, data, destinations); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); verify(connection1.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ZERO), aryEq(msg.toBytes())); verify(connection2.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ZERO), aryEq(msg.toBytes())); verify(xcallSpy).CallMessageSent(dapp.getAddress(), ethDapp.toString(), BigInteger.ONE); } + @Test + public void sendMessage_persistent() { + // Arrange + byte[] data = "test".getBytes(); + Message message = new PersistentMessage(data); + XCallEnvelope envelope = new XCallEnvelope(message); + xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); + + // Act + xcall.invoke(dapp.account, "sendCall", ethDapp.toString(), envelope.toBytes()); + + // Assert + CSMessageRequest request = new CSMessageRequest(iconDappAddress.toString(), ethDapp.account.toString(), BigInteger.ONE, PersistentMessage.TYPE, data, null); + CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); + verify(baseConnection.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ZERO), aryEq(msg.toBytes())); + verify(xcallSpy).CallMessageSent(dapp.getAddress(), ethDapp.toString(), BigInteger.ONE); + } + + @Test + public void sendMessage_response() { + // Arrange + xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); + + byte[] data1 = "test1".getBytes(); + byte[] data2 = "test2".getBytes(); + Message message = new PersistentMessage(data2); + XCallEnvelope envelope = new XCallEnvelope(message, baseSource, baseDestination); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), responseContract.getAddress().toString(), BigInteger.ONE, CallMessageWithRollback.TYPE, data1, baseSource); + CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); + xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes()); + + CSMessageRequest expectedRequest = new CSMessageRequest(new NetworkAddress(nid, responseContract.getAddress()).toString(), ethDapp.account(), BigInteger.ONE, PersistentMessage.TYPE, data2, baseDestination); + + ResponseContract.to = ethDapp.toString(); + ResponseContract.data = envelope.toBytes(); + // Act + xcall.invoke(user, "executeCall", BigInteger.ONE, data1); + + // Assert + CSMessageResult result = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, expectedRequest.toBytes()); + CSMessage res = new CSMessage(CSMessage.RESULT, result.toBytes()); + verify(baseConnection.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ONE.negate()), aryEq(res.toBytes())); + verify(xcallSpy).CallMessageSent(responseContract.getAddress(), ethDapp.toString(), BigInteger.ONE); + } + + @Test + public void sendMessage_response_anotherNetwork() { + // Arrange + String bscNid = "0x1.bsc"; + String to = "0x2212bsc"; + xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); + xcall.invoke(owner, "setDefaultConnection", bscNid, baseConnection.getAddress()); + + byte[] data1 = "test1".getBytes(); + byte[] data2 = "test2".getBytes(); + Message message = new PersistentMessage(data2); + XCallEnvelope envelope = new XCallEnvelope(message, baseSource, baseDestination); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), responseContract.getAddress().toString(), BigInteger.ONE, CallMessageWithRollback.TYPE, data1, baseSource); + CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); + xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes()); + + CSMessageRequest expectedRequest = new CSMessageRequest(new NetworkAddress(nid, responseContract.getAddress()).toString(), to, BigInteger.ONE, PersistentMessage.TYPE, data2, baseDestination); + + ResponseContract.to = new NetworkAddress(bscNid, to).toString(); + ResponseContract.data = envelope.toBytes(); + + // Act + xcall.invoke(user, "executeCall", BigInteger.ONE, data1); + + // Assert + CSMessageResult result = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, null); + CSMessage res = new CSMessage(CSMessage.RESULT, result.toBytes()); + CSMessage req = new CSMessage(CSMessage.REQUEST, expectedRequest.toBytes()); + verify(baseConnection.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ONE.negate()), aryEq(res.toBytes())); + verify(baseConnection.mock).sendMessage(eq(bscNid), eq(CallService.NAME), eq(BigInteger.ZERO), aryEq(req.toBytes())); + verify(xcallSpy).CallMessageSent(responseContract.getAddress(), ResponseContract.to, BigInteger.ONE); + } + + @Test + public void sendMessage_response_twoWayMessage() { + // Arrange + xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); + + byte[] data1 = "test1".getBytes(); + byte[] data2 = "test2".getBytes(); + Message message = new CallMessageWithRollback(data2, data2); + XCallEnvelope envelope = new XCallEnvelope(message, baseSource, baseDestination); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), responseContract.getAddress().toString(), BigInteger.ONE, CallMessageWithRollback.TYPE, data1, baseSource); + CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); + xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes()); + + CSMessageRequest expectedRequest = new CSMessageRequest(new NetworkAddress(nid, responseContract.getAddress()).toString(), ethDapp.account, BigInteger.ONE, CallMessageWithRollback.TYPE, data2, baseDestination); + + ResponseContract.to = ethDapp.toString(); + ResponseContract.data = envelope.toBytes(); + + // Act + xcall.invoke(user, "executeCall", BigInteger.ONE, data1); + + // Assert + CSMessageResult result = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, null); + CSMessage res = new CSMessage(CSMessage.RESULT, result.toBytes()); + CSMessage req = new CSMessage(CSMessage.REQUEST, expectedRequest.toBytes()); + verify(baseConnection.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ONE.negate()), aryEq(res.toBytes())); + verify(baseConnection.mock).sendMessage(eq(ethNid), eq(CallService.NAME), eq(BigInteger.ONE), aryEq(req.toBytes())); + verify(xcallSpy).CallMessageSent(responseContract.getAddress(), ResponseContract.to, BigInteger.ONE); + } + @Test public void handleResponse_singleProtocol() { // Arrange byte[] data = "test".getBytes(); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, false, data, new String[]{baseConnection.getAddress().toString()}); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, baseSource); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); // Act @@ -143,7 +273,7 @@ public void handleResponse_singleProtocol_invalidSender() { // Arrange byte[] data = "test".getBytes(); Account otherConnection = sm.createAccount(); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, false, data, new String[]{baseConnection.getAddress().toString()}); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, baseSource); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); // Act @@ -158,7 +288,7 @@ public void handleResponse_defaultProtocol() { // Arrange byte[] data = "test".getBytes(); xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, false, data, null); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, null); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); // Act @@ -173,7 +303,7 @@ public void handleResponse_defaultProtocol_invalidSender() { // Arrange byte[] data = "test".getBytes(); xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, false, data, null); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, null); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); Account invalidConnection = sm.createAccount(); @@ -192,7 +322,7 @@ public void handleResponse_multiProtocol() throws Exception { MockContract connection2 = new MockContract<>(ConnectionScoreInterface.class, Connection.class, sm, owner); String[] connections = {connection1.getAddress().toString(), connection2.getAddress().toString()}; - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, false, data, connections); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, connections); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); // Act @@ -208,7 +338,7 @@ public void handleResponse_multiProtocol() throws Exception { public void handleRequest_same_network_id() { // Arrange byte[] data = "test".getBytes(); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, false, data, new String[]{baseConnection.getAddress().toString()}); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, new String[]{baseConnection.getAddress().toString()}); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); // Act @@ -219,11 +349,51 @@ public void handleRequest_same_network_id() { verify(xcallSpy, times(0)).CallMessage(anyString(), anyString(), any(BigInteger.class), any(BigInteger.class), any(byte[].class)); } + @Test + public void handleReply() { + // Arrange + xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); + + byte[] data = "test".getBytes(); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, PersistentMessage.TYPE, data, baseSource); + CSMessageResult result = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, request.toBytes()); + CSMessage msg = new CSMessage(CSMessage.RESULT, result.toBytes()); + + xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, data, baseSource, baseDestination); + + // Act + xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes()); + + // Assert + verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResult.SUCCESS); + verify(xcallSpy).CallMessage(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, BigInteger.ONE, data); + } + + @Test + public void handleReply_invalidTo() { + // Arrange + xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); + + byte[] data = "test".getBytes(); + CSMessageRequest request = new CSMessageRequest("otherNid/0x1", dapp.getAddress().toString(), BigInteger.ONE, PersistentMessage.TYPE, data, baseSource); + CSMessageResult result = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, request.toBytes()); + CSMessage msg = new CSMessage(CSMessage.RESULT, result.toBytes()); + + xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, data, baseSource, baseDestination); + + // Act + Executable handleMessage = () -> xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes()); + + // Assert + Exception e = assertThrows(Exception.class, handleMessage); + assertEquals("Reverted(0): Invalid Reply", e.getMessage()); + } + @Test public void executeCall_singleProtocol() { // Arrange byte[] data = "test".getBytes(); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, false, data, new String[]{baseConnection.getAddress().toString()}); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, baseSource); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); xcall.invoke(baseConnection.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ZERO, msg.toBytes()); @@ -240,7 +410,7 @@ public void executeCall_defaultProtocol() throws Exception { // Arrange byte[] data = "test".getBytes(); MockContract defaultDapp = new MockContract<>(DefaultCallServiceReceiverScoreInterface.class, DefaultCallServiceReceiver.class, sm, owner); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), defaultDapp.getAddress().toString(), BigInteger.ONE, false, data, null); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), defaultDapp.getAddress().toString(), BigInteger.ONE, 1, data, null); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); @@ -262,7 +432,7 @@ public void executeCall_multiProtocol() throws Exception{ MockContract connection2 = new MockContract<>(ConnectionScoreInterface.class, Connection.class, sm, owner); String[] connections = {connection1.getAddress().toString(), connection2.getAddress().toString()}; - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, false, data, connections); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, connections); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); xcall.invoke(connection1.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ZERO, msg.toBytes()); xcall.invoke(connection2.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ZERO, msg.toBytes()); @@ -283,7 +453,7 @@ public void executeCall_multiProtocol_rollback() throws Exception { MockContract connection2 = new MockContract<>(ConnectionScoreInterface.class, Connection.class, sm, owner); String[] connections = {connection1.getAddress().toString(), connection2.getAddress().toString()}; - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, true, data, connections); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, 1, data, connections); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); xcall.invoke(connection1.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ZERO, msg.toBytes()); xcall.invoke(connection2.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ZERO, msg.toBytes()); @@ -292,8 +462,8 @@ public void executeCall_multiProtocol_rollback() throws Exception { xcall.invoke(user, "executeCall", BigInteger.ONE, data); // Assert - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.SUCCESS); - msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, null); + msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); verify(dapp.mock).handleCallMessage(ethDapp.toString(), data, connections); verify(connection1.mock).sendMessage(ethNid, CallService.NAME, BigInteger.ONE.negate(), msg.toBytes()); @@ -306,7 +476,7 @@ public void executeCall_defaultProtocol_rollback() throws Exception { // Arrange byte[] data = "test".getBytes(); MockContract defaultDapp = new MockContract<>(DefaultCallServiceReceiverScoreInterface.class, DefaultCallServiceReceiver.class, sm, owner); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), defaultDapp.getAddress().toString(), BigInteger.ONE, true, data, null); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), defaultDapp.getAddress().toString(), BigInteger.ONE, CallMessageWithRollback.TYPE, data, null); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); @@ -316,8 +486,8 @@ public void executeCall_defaultProtocol_rollback() throws Exception { xcall.invoke(user, "executeCall", BigInteger.ONE, data); // Assert - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.SUCCESS); - msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, null); + msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); verify(defaultDapp.mock).handleCallMessage(ethDapp.toString(), data); verify(xcallSpy).CallExecuted(BigInteger.ONE, 1, ""); @@ -328,7 +498,7 @@ public void executeCall_defaultProtocol_rollback() throws Exception { public void executeCall_failedExecution() { // Arrange byte[] data = "test".getBytes(); - CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, true, data, new String[]{baseConnection.getAddress().toString()}); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, CallMessageWithRollback.TYPE, data, baseSource); CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); xcall.invoke(baseConnection.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ZERO, msg.toBytes()); // Act @@ -336,12 +506,68 @@ public void executeCall_failedExecution() { xcall.invoke(user, "executeCall", BigInteger.ONE, data); // Assert - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.FAILURE); - msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.FAILURE, null); + msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); verify(baseConnection.mock).sendMessage(ethNid, CallService.NAME, BigInteger.ONE.negate(), msg.toBytes()); verify(xcallSpy).CallExecuted(BigInteger.ONE, 0, "score.RevertedException"); } + @Test + public void executeCall_persistent_failedExecution() { + // Arrange + byte[] data = "test".getBytes(); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), dapp.getAddress().toString(), BigInteger.ONE, PersistentMessage.TYPE, data, baseSource); + CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); + xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes()); + // Act + doThrow(new UserRevertedException()).when(dapp.mock).handleCallMessage(ethDapp.toString(), data, new String[]{baseConnection.getAddress().toString()}); + assertThrows(Exception.class, () -> xcall.invoke(user, "executeCall", BigInteger.ONE, data)); + } + + @Test + public void executeCall_persistent() throws Exception { + // Arrange + byte[] data = "test".getBytes(); + MockContract defaultDapp = new MockContract<>(DefaultCallServiceReceiverScoreInterface.class, DefaultCallServiceReceiver.class, sm, owner); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), defaultDapp.getAddress().toString(), BigInteger.ONE, PersistentMessage.TYPE, data, null); + CSMessage msg = new CSMessage(CSMessage.REQUEST, request.toBytes()); + + xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); + xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes()); + + // Act + xcall.invoke(user, "executeCall", BigInteger.ONE, data); + + // Assert + verify(defaultDapp.mock).handleCallMessage(ethDapp.toString(), data); + verify(xcallSpy).CallExecuted(BigInteger.ONE, 1, ""); + Exception e = assertThrows(Exception.class, () -> xcall.invoke(user, "executeCall", BigInteger.ONE, data)); + assertEquals("Reverted(0): InvalidRequestId", e.getMessage()); + } + + @Test + public void executeCall_reply() throws Exception { + // Arrange + MockContract defaultDapp = new MockContract<>(DefaultCallServiceReceiverScoreInterface.class, DefaultCallServiceReceiver.class, sm, owner); + xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); + + byte[] data = "test".getBytes(); + CSMessageRequest request = new CSMessageRequest(ethDapp.toString(), defaultDapp.getAddress().toString(), BigInteger.ONE, PersistentMessage.TYPE, data, baseSource); + CSMessageResult result = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, request.toBytes()); + CSMessage msg = new CSMessage(CSMessage.RESULT, result.toBytes()); + + xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, data, baseSource, baseDestination); + xcall.invoke(baseConnection.account, "handleMessage", ethNid, msg.toBytes()); + + // Act + xcall.invoke(user, "executeCall", BigInteger.ONE, data); + + // Assert + verify(defaultDapp.mock).handleCallMessage(ethDapp.toString(), data); + verify(xcallSpy).CallExecuted(BigInteger.ONE, 1, ""); + Exception e = assertThrows(Exception.class, () -> xcall.invoke(user, "executeCall", BigInteger.ONE, data)); + assertEquals("Reverted(0): InvalidRequestId", e.getMessage()); + } @Test public void rollback_singleProtocol() { @@ -351,12 +577,12 @@ public void rollback_singleProtocol() { xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, rollback, baseSource, baseDestination); // Act - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.FAILURE); - CSMessage msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.FAILURE, null); + CSMessage msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); xcall.invoke(baseConnection.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); // Assert - verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResponse.FAILURE); + verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResult.FAILURE); verify(xcallSpy).RollbackMessage(BigInteger.ONE); assertTrue(!xcall.call(Boolean.class, "verifySuccess", BigInteger.ONE)); } @@ -370,12 +596,12 @@ public void rollback_defaultProtocol() { xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, rollback); // Act - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.FAILURE); - CSMessage msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.FAILURE, null); + CSMessage msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); xcall.invoke(baseConnection.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); // Assert - verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResponse.FAILURE); + verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResult.FAILURE); verify(xcallSpy).RollbackMessage(BigInteger.ONE); assertTrue(!xcall.call(Boolean.class, "verifySuccess", BigInteger.ONE)); } @@ -390,12 +616,12 @@ public void rollback_defaultProtocol_invalidSender() { Account invalidConnection = sm.createAccount(); // Act - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.FAILURE); - CSMessage msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.FAILURE, null); + CSMessage msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); assertThrows(Exception.class, ()-> xcall.invoke(invalidConnection, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes())); // Assert - verify(xcallSpy, times(0)).ResponseMessage(BigInteger.ONE, CSMessageResponse.FAILURE); + verify(xcallSpy, times(0)).ResponseMessage(BigInteger.ONE, CSMessageResult.FAILURE); verify(xcallSpy, times(0)).RollbackMessage(BigInteger.ONE); assertTrue(!xcall.call(Boolean.class, "verifySuccess", BigInteger.ONE)); } @@ -416,14 +642,14 @@ public void rollback_multiProtocol() throws Exception { xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, rollback, sources, destinations); // Act - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.FAILURE); - CSMessage msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.FAILURE, null); + CSMessage msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); xcall.invoke(connection1.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); - verify(xcallSpy, times(0)).ResponseMessage(BigInteger.ONE, CSMessageResponse.FAILURE); + verify(xcallSpy, times(0)).ResponseMessage(BigInteger.ONE, CSMessageResult.FAILURE); xcall.invoke(connection2.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); // Assert - verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResponse.FAILURE); + verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResult.FAILURE); verify(xcallSpy).RollbackMessage(BigInteger.ONE); assertTrue(!xcall.call(Boolean.class, "verifySuccess", BigInteger.ONE)); } @@ -436,18 +662,17 @@ public void rollback_success() throws Exception { xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, rollback, baseSource, baseDestination); // Act - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.SUCCESS); - CSMessage msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.SUCCESS, null); + CSMessage msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); xcall.invoke(baseConnection.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); // Assert - verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResponse.SUCCESS); + verify(xcallSpy).ResponseMessage(BigInteger.ONE, CSMessageResult.SUCCESS); verify(xcallSpy, times(0)).RollbackMessage(BigInteger.ONE); assertTrue(xcall.call(Boolean.class, "verifySuccess", BigInteger.ONE)); } - @Test public void executeRollback_singleProtocol() { // Arrange @@ -457,8 +682,8 @@ public void executeRollback_singleProtocol() { xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, rollback, baseSource, baseDestination); - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.FAILURE); - CSMessage msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.FAILURE, null); + CSMessage msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); xcall.invoke(baseConnection.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); // Act @@ -481,8 +706,8 @@ public void executeRollback_defaultProtocol() throws Exception { xcall.invoke(owner, "setDefaultConnection", ethDapp.net(), baseConnection.getAddress()); xcall.invoke(defaultDapp.account, "sendCallMessage", ethDapp.toString(), data, rollback); - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.FAILURE); - CSMessage msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.FAILURE, null); + CSMessage msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); xcall.invoke(baseConnection.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); // Act @@ -510,8 +735,8 @@ public void executeRollback_multiProtocol() throws Exception { String[] sources = {connection1.getAddress().toString(), connection2.getAddress().toString()}; xcall.invoke(dapp.account, "sendCallMessage", ethDapp.toString(), data, rollback, sources, destinations); - CSMessageResponse msgRes = new CSMessageResponse(BigInteger.ONE, CSMessageResponse.FAILURE); - CSMessage msg = new CSMessage(CSMessage.RESPONSE, msgRes.toBytes()); + CSMessageResult msgRes = new CSMessageResult(BigInteger.ONE, CSMessageResult.FAILURE, null); + CSMessage msg = new CSMessage(CSMessage.RESULT, msgRes.toBytes()); xcall.invoke(connection1.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); xcall.invoke(connection2.account, "handleBTPMessage", ethNid, CallService.NAME, BigInteger.ONE, msg.toBytes()); diff --git a/docs/adr/xcall.md b/docs/adr/xcall.md index e2d7bded..b5be5c33 100644 --- a/docs/adr/xcall.md +++ b/docs/adr/xcall.md @@ -18,6 +18,13 @@ A network address is represented as a string with "networkId" and "account" sepa A networkId is a unique id of a network, and there can't be two networks with the same id connected to the same xCall network. +``` +NetworkAddress { + String account, + String net +} +``` + ### Connections XCall is designed to utilize a wide range of bridging protocols that facilitate data transfer, known as connections. @@ -31,7 +38,56 @@ connections at all. ### Sending Messages -Sending messages via xCall is simply done by calling sendCallMessage on the xCall contract. +Sending messages via xCall is done by constructing a xCall message envelope and calling `sendCall` with a +destination networkAddress `_to`. +``` +/** + * Sends a call message to the contract on the destination chain. + * + * @param _to The network address of the callee on the destination chain + * @param _data The xCall envelope + * @return The serial number of the request + */ +payable external sendCall(String _to, byte[] _data) returns Integer +``` + +##### Message Envelope Structure + +All structure are RLP encoded in the order as shown below. + +The Envelope is the structure received by sendCall method. +``` +Envelope { + int messageType, + Message message, + String[] sources, + String[] destinations, +} +``` + +##### Message Objects +All Message objects have a TypeId specifying its type + +A CallMessage will try to execute on the destination chain, and will be removed even if the execution fails and no response will be relayed back +``` +TypeId = 1 +CallMessage { + byte[] data +} + +``` + +``` +TypeId = 2 +CallMessageWithRollback { + byte[] data, + byte[] rollback, + +} +``` + +#### Legacy send Interface +Sending messages via xCall can be done by calling sendCallMessage on the xCall contract. `_to` address is a networkAddress used by xCall to figure out the destination chain. The user can also specify which connections to use, if not specified, the default connections will be used. This also allows dapps to have their messages secured by multiple protocols. @@ -54,9 +110,9 @@ The `_rollback` data is limited to 1024KB. */ payable external sendCallMessage(String _to, byte[] _data, - @Optional bytes _rollback, + @Optional byte[] _rollback, @Optional String[] _sources - @Optional String[] _destinations) return Integer; + @Optional String[] _destinations) return Integer ``` ### Events @@ -173,7 +229,7 @@ Then `xcall` compares it with the saved hash value to validate its integrity. * @param _reqId The request Id * @param _data The calldata */ -external executeCall(BigInteger _reqId, byte[] _data); +external executeCall(BigInteger _reqId, byte[] _data) ``` The user on the source chain recognizes the rollback situation and invokes the following method on xcall with the @@ -187,7 +243,7 @@ It should be reverted when there is no failure response with the call request. * * @param _sn The serial number of the previous request */ -external executeRollback(BigInteger _sn); +external executeRollback(BigInteger _sn) ``` #### Handling @@ -205,8 +261,8 @@ If only using default protocols, implementing only the two parameter versions of * @param _data The calldata delivered from the caller * @param _protocols The contract addresses that delivered the data, if omitted the default protocol was used */ -external handleCallMessage(String _from, byte[] _data); -external handleCallMessage(String _from, byte[] _data, @Optional String[] _protocols); +external handleCallMessage(String _from, byte[] _data) +external handleCallMessage(String _from, byte[] _data, @Optional String[] _protocols) ``` In case of rollback, the `_from` will be the network address of the xCall contract. @@ -250,7 +306,7 @@ If rollback was specified and the call was successful, the success can be verifi * * @return If the '_sn' has received a success response */ -external readonly verifySuccess(BigInteger _sn) returns boolean; +external readonly verifySuccess(BigInteger _sn) returns boolean ``` ### Fee Management @@ -271,7 +327,7 @@ Sending a message through xCall has two types of fees. One for using the protoco */ external readonly getFee(String _net, boolean _rollback - @Optional String[] _sources) returns Integer; + @Optional String[] _sources) returns Integer ``` ``` @@ -280,7 +336,7 @@ external readonly getFee(String _net, * * @return the xCall protocol fee */ -external readonly getProtocolFee() Returns Integer; +external readonly getProtocolFee() Returns Integer ``` ### Security Considerations @@ -345,48 +401,50 @@ RLP encoding order is the same as the order they are defined in below. ``` CSMessageRequest { - String from; - String to; - BigInteger sn; - boolean rollback; - bytes data; - String[] protocols; + String from + String to + BigInteger sn + int messageType + // RLP encoded message + byte[] message + String[] protocols } ``` -##### CSMessageResponse +##### CSMessageResult ``` -int SUCCESS = 1; -int FAILURE = 0; -CSMessageResponse { - BigInteger sn; - int code; +int SUCCESS = 1 +int FAILURE = 0 +CSMessageResult { + BigInteger sn + int code + byte[] message } ``` ##### CSMessage ``` -int REQUEST = 1; -int RESPONSE = 2; +int REQUEST = 1 +int RESULT = 2 CSMessage { - // The message type, either REQUEST or RESPONSE - int type; + // The message type, either REQUEST or RESULT + int type // RLP encoded bytes of the Message - bytes data; + byte[] data } ``` #### Internal structs ``` -CallRequest { - Address from; - String netTo; - String[] protocols; - bytes rollback; - boolean enabled = false; // defaults to false +RollbackData { + Address from + String netTo + String[] protocols + byte[] rollback + boolean enabled = false // defaults to false } ``` @@ -396,40 +454,81 @@ CallRequest { MAX_DATA_SIZE: 2048 MAX_ROLLBACK_SIZE: 1024 NID: -SVC_NAME = xcallM sn: reqId: -requests: sn -> CallRequest +rollbacks: sn -> RollbackData proxyReqs: reqId -> CSMessageRequest -# default values should be false in case of boolean storage -pendingReqs: msgHash -> connection address -> boolean -pendingResponses: msgHash -> connection address -> boolean +// default values should be false in case of boolean storage +pendingMessages: msgHash -> connection address -> boolean successfulResponses: sn -> boolean admin: defaultConnection: networkId -> Address protocolFee: feeHandler:
+replyState: CallRequest, +callReply: CSMessageRequest ``` ### Contract initialization ``` -function init(String networkId) { +function init(String networkId): NID = networkId - if admin == null - admin = getCaller(); - feeHandler = getCaller(); - -} + admin = getCaller() + feeHandler = getCaller() ``` ### Communication #### Sending messages +`sendCall` sends some arbitrary data to `_to` via a path specified by the caller. + +- `_to`: The network address of the target contract. +- `_data`: The rlp encoded xCall envelope. + +``` +payable external sendCall(String _to, byte[] _data) returns Integer { + caller = getCaller() + envelope = Envelope.decode(_data) + sn++ + from = NetworkAddress(NID, caller) + to = NetworkAddress(_to) + + needResponse, msg = preProcessMessage(sn, to, envelope) + + msgReq = CSMessageRequest(from, to.account(), sn, envelope.type, msg, envelope.destinations) + msg = CSMessage(CSMessage.REQUEST, msgReq.toBytes()).toBytes() + assert msg.length <= MAX_DATA_SIZE + + if isReply(_to.netId,envelope.sources) && !needResponse: + replyState = null + callReply = msg + emit CallMessageSent(caller, dst.toString(), sn) + return sn + + sendSn = needResponse ? sn : 0 + if protocolConfig.sources == []: + src = defaultConnection[to.net()] + fee = src->getFee(to.net(), needResponse) + src->sendMessage(fee, to.net(), "xcall-multi", sendSn, msg) + else: + for src in protocolConfig.sources: + fee = src->getFee(to.net(), needResponse) + src->sendMessage(fee, to.net(), "xcall-multi", sendSn, msg) + + + remainingBalance = getBalance() + assert remainingBalance >= getProtocolFee() + transfer(feeHandler, balance) + emit CallMessageSent(caller, dst.toString(), sn) + return sn +} + +``` `sendCallMessage` sends some arbitrary data to `_to` via a path specified by the caller. - `_to`: The network address of the target contract. @@ -442,64 +541,54 @@ function init(String networkId) { ``` payable external function sendCallMessage(String _to, byte[] _data, - @Optional bytes _rollback, + @Optional byte[] _rollback, @Optional String[] _sources @Optional String[] _destinations) returns Integer { - caller = getCaller() - require(caller.isContract() || _rollback == null, "RollbackNotPossible"); - require(_rollback == null || _rollback.length <= MAX_ROLLBACK_SIZE, "MaxRollbackSizeExceeded") - - sn++ - dst = NetworkAddress(_to) - from = NetworkAddress(NID, caller).toString() - - needResponse = _rollback != null && _rollback.length > 0 - if needResponse: - req = CallRequest(caller, dst.net(), _sources, _rollback) - requests[sn] = req - - - msgReq = CSMessageRequest(from, dst.account(), sn, needResponse, _data, _destinations) - msg = CSMessage(CSMessage.REQUEST, msgReq.toBytes()).toBytes(); - require(msg.length <= MAX_DATA_SIZE, "MaxDataSizeExceeded") - - sendSn = needResponse ? sn : 0 - if _sources == []: - src = defaultConnection[dst.net()] - fee = src->getFee(dst.net(), needResponse) - src->sendMessage(fee, dst.net(), SVC_NAME, sendSn, msg) + if (_rollback == null || _rollback.length == 0): + msg = new CallMessage(_data) else: - for src in sources: - fee = src->getFee(dst.net(), needResponse) - src->sendMessage(fee, dst.net(), SVC_NAME, sendSn, msg) + msg = new CallMessageWithRollback(_data, _rollback) + envelope = new Envelope(msg, _sources, _destinations) + return sendCall(_to, envelope.toBytes()) - remaningBalance = getBalance(); - require(remaningBalance >= getProtocolFee()) - transfer(feeHandler, balance) - emit CallMessageSent(caller, dst.toString(), sn) - return sn } +``` +Internal method where message types can implement type specific logic. +``` +internal function preProcessMessage(int sn, NetworkAddress to, Envelope envelope) return (boolean, byte[]) { + switch (envelope.type) { + case CallMessage.Type: + return false, message + case CallMessageWithRollback.Type: + assert caller.isContract() + msg = CallMessageWithRollback(message) + req = RollbackData(caller, to.net(), envelope.sources, msg.rollback) + rollbacks[sn] = req + + return true, msg.data + } +} ``` #### Receiving messages `handleMessage` is the external function used by connections to deliver messages. -```javascript -external function handleMessage(String _fromNid, bytes _msg) { - msg = CSMessage.decode(_msg); +``` +external function handleMessage(String _fromNid, byte[] _msg) { + msg = CSMessage.decode(_msg) assert _fromNid != nid switch (msg.type) : case CSMessage.REQUEST: - handleRequest(_fromNid, msg.data); - break; - case CSMessage.RESPONSE: - handleResponse(msg.data); - break; + handleRequest(_fromNid, msg.data) + break + case CSMessage.RESULT: + handleResult(msg.data) + break default: - Context.revert("UnknownMsgType(" + msg.type + ")"); + Context.revert("UnknownMsgType(" + msg.type + ")") } ``` @@ -507,16 +596,15 @@ external function handleMessage(String _fromNid, bytes _msg) { ``` external function handleError(BigInteger _sn) { - CSMessageResponse res = CSMessageResponse(_sn, CSMessageResponse.FAILURE); - handleResponse(res.toBytes()); + CSMessageResult res = CSMessageResult(_sn, CSMessageResult.FAILURE) + handleResult(res.toBytes()) } ``` `handleBTPMessage` Can be added to natively support the BTP protocol without a standalone connection. ``` -external function handleBTPMessage(String _from, _svc String, Integer _sn, bytes _msg) { - // verify svc is as registered +external function handleBTPMessage(String _from, _svc String, Integer _sn, byte[] _msg) { handleMessage(_from, _msg) } ``` @@ -525,135 +613,174 @@ external function handleBTPMessage(String _from, _svc String, Integer _sn, bytes ``` external function handleBTPError(String _src, String _svc, BigInteger _sn, long _code, String _msg) { - // verify svc is as registered handleError(_sn) } ``` ``` -internal function handleRequest(String srcNet, bytes data) { - msgReq = CSMessageRequest.decode(data); - from = NetworkAddress(msgReq.from); - require(from.net() == srcNet); - source = getCaller(); +internal function verifyProtocols(String srcNet, String[] protocols, byte[] data) returns boolean { + source = getCaller() + _hash = hash(data) + if (protocols.length > 1): + pendingMessages[_hash][source] = true + for (protocol : protocols): + if (!pendingMessages[_hash][protocol]): + return false + for (protocol : protocols): + pendingMessages[_hash][protocol] = null + else if (protocols.length == 1): + assert source == protocols[0] + else: + assert source == defaultConnection[srcNet] - if (msgReq.protocols.length > 1): - _hash = hash(data); - pendingReqs[_hash][source] = true; - for (protocol : msgReq.protocols): - if (!pendingReqs[_hash][protocol]): - return; + return true +} +``` - for (protocol : msgReq.protocols): - pendingReqs[_hash][protocol] = null; - else if (msgReq.protocols.length == 1): - require(source == msgReq.protocols[0]); - else: - require(source == defaultConnection[srcNet]); - reqId = getNextReqId(); +``` +internal function handleRequest(String srcNet, byte[] data) { + msgReq = CSMessageRequest.decode(data) + if !verifyProtocols(srcNet, msgReq.protocolConfig, hash(data)): + return - emit CallMessage(msgReq.from, msgReq.to, msgReq.sn, reqId, msgReq.data); + reqId = getNextReqId() + from = NetworkAddress(msgReq.from) + assert from.net == srcNet + emit CallMessage(msgReq.from, msgReq.to, msgReq.sn, reqId, msgReq.data) msgReq.data = hash(msgReq.data) - proxyReqs[reqId] = msgReq; + proxyReqs[reqId] = msgReq +} +``` +``` +internal function handleReply(RollbackData rollback, CSMessageRequest reply) { + // The reply must be from the same chain as the response originated from + assert rollback.netTo == reply.from.netId + // The protocols need to same as the reply + rollback.protocols = reply.protocols + + reqId = getNextReqId() + emit CallMessage(reply.from, reply.to, reply.sn, reqId, reply.data) + reply.data = hash(reply.data) + proxyReqs[reqId] = msgReq } ``` ``` -internal function handleResponse(data bytes) { - response = CSMessageResponse.decode(data); - resSn = response.sn; - req = requests[resSn]; - source = getCaller +internal function handleResult(data byte[]) { + result = CSMessageResult.decode(data) + resSn = result.sn + req = rollbacks[resSn] if req == null: - return; // just ignore - - if req.protocols.length > 1: - _hash = hash(data); - pendingResponses.at(_hash).set(source, true); - for protocol : req.protocols: - if !pendingResponses[_hash][protocol]: - return; - - for (String protocol : protocols): - pendingResponses[_hash][protocol] = null - else if (msgReq.protocols.length == 1): - require(source == msgReq.protocols[0]); - else: - require(source == defaultConnection[req.netTo]); - - emit ResponseMessage(resSn, response.getCode()); - switch response.getCode(): - case CSMessageResponse.SUCCESS: - requests[resSn] = null; - successfulResponses[resSn] = 1; - break; - case CSMessageResponse.FAILURE: + throw "CallRequest Not Found For {resSn}" + + if !verifyProtocols(req.netTo, req.protocolConfig, hash(data)): + return + + emit ResponseMessage(resSn, result.getCode()) + switch result.getCode(): + case CSMessageResult.SUCCESS: + if result.getMessage() != null: + handleReply(req, result.getMessage()) + + rollbacks[resSn] = null + successfulResponses[resSn] = 1 + break + case CSMessageResult.FAILURE: default: - // emit rollback event - require(req.rollback != null, "NoRollbackData"); - req.enabled = true; - requests[resSn] = req; - emit RollbackMessage(resSn); + assert req.rollback != null + req.enabled = true + rollbacks[resSn] = req + emit RollbackMessage(resSn) } ``` #### Message Execution -In case of a two-message call, -the function should allow the call to fail and send a new message to roll back the message. -While if a one way message fails, re-execution should be allowed. - ``` external function executeCall(Integer _reqId, byte[] _data) { - req = proxyReqs[_reqId]; - require(req != null, "InvalidRequestId"); - proxyReqs[_reqId] == null; + req = proxyReqs[_reqId] + assert req != null + proxyReqs[_reqId] == null assert hash(_data) == req.data + executeMessage(_reqId, r) + } - from = NetworkAddress(req.from); - ErrorMessage = "" - try: - if req.protocols == []: - req.to->handleCallMessage(req.from, _data); - else: - req.to->handleCallMessage(req.from, _data, req.protocols); - response = new CSMessageResponse(req.sn, CSMessageResponse.SUCCESS); - catch err: - response = new CSMessageResponse(req.sn, CSMessageResponse.FAILURE); - ErrorMessage = err.message - - emit CallExecuted(_reqId, response.code, response.msg, ErrorMessage); +``` - if req.needRollback(): - sn = req.sn.negate(); - msg = CSMessage(CSMessage.RESPONSE, response.toBytes()); +Method where message specific execution logic is handled. +``` +internal function executeMessage(int reqId, CallRequest req) { + switch (req.type) { + case CallMessage.Type: + tryExecute(reqId, req.from, req.data, req.protocols) + case CallMessageWithRollback.Type: + replyState = req + code = tryExecute(reqId, req.from, req.data, req.protocols) + replyState = null + result = new CSMessageResult(req.sn, code, callReply) + msg = CSMessage(CSMessage.RESULT, result.toBytes()) + callReply = null + sn = req.sn.negate() if req.protocols == []: protocol = defaultConnection[from.net()] - protocol->sendMessage(from.net(), SVC_NAME, sn, msg.toBytes()) + protocol->sendMessage(from.net(), "xcall-multi", sn, msg.toBytes()) else: for (String protocol : req.protocols): - protocol->sendMessage(from.net(), SVC_NAME, sn, msg.toBytes()) + protocol->sendMessage(from.net(), "xcall-multi", sn, msg.toBytes()) + default: + revert } - +} ``` ``` external function executeRollback(Integer _sn) { - req = requests.get(_sn); - require(req != null, "InvalidSerialNum"); - require(req.enabled, "RollbackNotEnabled"); - requests[_sn] = null; + req = rollbacks.get(_sn) + assert req != null + assert req.enabled + rollbacks[_sn] = null + + if req.protocols == []: + req.to->handleCallMessage(getNetworkAddress(), req.rollback, req.protocols) + else: + req.to->handleCallMessage(getNetworkAddress(), req.rollback, req.protocols) + + emit RollbackExecuted(_sn) +} +``` +``` +internal function tryExecuteCall(int id, String from, byte[] data, String[] protocols) returns String { + try: + executeCall(id, from, data, protocols) + catch Error as e: + emit CallExecuted(id, CSMessageResult.FAILURE, e.message) + return CSMessageResult.FAILURE + + return CSMessageResult.SUCCESS +} +``` + +``` +internal function executeCall(int id, String from, byte[] data, String[] protocols) { if req.protocols == []: - req.from->handleCallMessage(getNetworkAddress(), req.rollback); + req.to->handleCallMessage(from, data) else: - req.from->handleCallMessage(getNetworkAddress(), req.rollback, req.protocols); + req.to->handleCallMessage(from, data, protocols) - emit RollbackExecuted(_sn); + emit CallExecuted(id, CSMessageResult.SUCCESS, "") } +``` + +``` +internal function isReply(String netId, String[] sources) { + if replyState != null: + return replyState.fromNid == netid && replyState.protocols.equals(sources) + return false +} ``` ### Admin methods @@ -680,28 +807,32 @@ adminOnly function setDefaultConnection(String _nid, Address _connection){ ``` external readonly function getNetworkAddress() returns String { - return NetworkAddress(NID, this.address); + return NetworkAddress(NID, this.address) } ``` ``` external readonly function getNetworkId() returns String { - return NID; + return NID } ``` ``` external readonly function getProtocolFee() returns Integer { - return protocolFee; + return protocolFee } ``` ``` -external readonly function getFee(String _net, - boolean _rollback - @Optional String[] _sources) +external readonly function getFee(String _net, + boolean _rollback + @Optional String[] _sources) returns Integer { - fee = protocolFee; + + if isReply(_net, sources) && !_rollback { + return 0 + } + fee = protocolFee if _sources == [] { return defaultConnection[_net]->getFee(_net, _rollback) + fee } @@ -715,7 +846,7 @@ external readonly function getFee(String _net, ``` external readonly function verifySuccess(Integer sn) returns boolean { - return successfulResponses[sn]; + return successfulResponses[sn] } ``` @@ -764,4 +895,4 @@ in [IIP52](https://github.com/icon-project/IIPs/blob/master/IIPS/iip-52.md). ## FAQs -... +... \ No newline at end of file