From e62e2abb8b454d2e7ad5264f83ed1d317985cc17 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Mon, 18 Dec 2023 14:26:27 +0200 Subject: [PATCH 01/80] copy voting-verifier to xrpl-voting-verifier contract --- Cargo.lock | 1833 +++++++++-------- Cargo.toml | 1 + contracts/xrpl-voting-verifier/.cargo/config | 4 + contracts/xrpl-voting-verifier/Cargo.toml | 50 + .../xrpl-voting-verifier/src/bin/schema.rs | 11 + .../xrpl-voting-verifier/src/contract.rs | 64 + contracts/xrpl-voting-verifier/src/error.rs | 46 + contracts/xrpl-voting-verifier/src/events.rs | 208 ++ contracts/xrpl-voting-verifier/src/execute.rs | 270 +++ contracts/xrpl-voting-verifier/src/lib.rs | 7 + contracts/xrpl-voting-verifier/src/msg.rs | 79 + contracts/xrpl-voting-verifier/src/query.rs | 266 +++ contracts/xrpl-voting-verifier/src/state.rs | 83 + contracts/xrpl-voting-verifier/tests/mock.rs | 124 ++ contracts/xrpl-voting-verifier/tests/tests.rs | 641 ++++++ 15 files changed, 2837 insertions(+), 850 deletions(-) create mode 100644 contracts/xrpl-voting-verifier/.cargo/config create mode 100644 contracts/xrpl-voting-verifier/Cargo.toml create mode 100644 contracts/xrpl-voting-verifier/src/bin/schema.rs create mode 100644 contracts/xrpl-voting-verifier/src/contract.rs create mode 100644 contracts/xrpl-voting-verifier/src/error.rs create mode 100644 contracts/xrpl-voting-verifier/src/events.rs create mode 100644 contracts/xrpl-voting-verifier/src/execute.rs create mode 100644 contracts/xrpl-voting-verifier/src/lib.rs create mode 100644 contracts/xrpl-voting-verifier/src/msg.rs create mode 100644 contracts/xrpl-voting-verifier/src/query.rs create mode 100644 contracts/xrpl-voting-verifier/src/state.rs create mode 100644 contracts/xrpl-voting-verifier/tests/mock.rs create mode 100644 contracts/xrpl-voting-verifier/tests/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 635f5ef56..5cefa58bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", @@ -84,8 +84,8 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "error-stack", "report", "schemars", @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ "getrandom", "once_cell", @@ -108,20 +108,21 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -153,7 +154,7 @@ version = "0.1.0" dependencies = [ "async-trait", "axelar-wasm-std", - "base64 0.21.4", + "base64 0.21.5", "bcs", "clap", "config", @@ -186,7 +187,7 @@ dependencies = [ "report", "serde", "serde_json", - "serde_with 3.3.0", + "serde_with 3.4.0", "service-registry", "sui-json-rpc-types", "sui-types", @@ -195,7 +196,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.9", + "tokio-util 0.7.10", "toml 0.5.11", "tonic 0.8.3", "tonic-build", @@ -243,16 +244,16 @@ dependencies = [ "quinn-proto", "rand", "rcgen", - "ring", - "rustls 0.21.7", - "rustls-webpki 0.101.5", + "ring 0.16.20", + "rustls 0.21.10", + "rustls-webpki", "serde", "serde_json", - "socket2 0.5.4", + "socket2 0.5.5", "tap", "thiserror", "tokio", - "tokio-util 0.7.9", + "tokio-util 0.7.10", "tower", "tracing", "x509-parser", @@ -260,9 +261,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -274,43 +275,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" [[package]] name = "ark-bls12-381" @@ -350,7 +351,7 @@ dependencies = [ "blake2", "derivative", "digest 0.10.7", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -396,7 +397,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ - "quote 1.0.33", + "quote 1.0.35", "syn 1.0.109", ] @@ -408,8 +409,8 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint 0.4.4", "num-traits", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -481,8 +482,8 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -551,8 +552,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", "synstructure", ] @@ -563,8 +564,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -599,9 +600,9 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -611,13 +612,13 @@ source = "git+https://github.com/mystenmark/async-task?rev=4e45b26e11126b191701b [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -638,8 +639,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -663,7 +664,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "error-stack", "flagset", "hex", @@ -685,9 +686,9 @@ version = "0.1.0" dependencies = [ "axelar-wasm-std", "error-stack", - "quote 1.0.33", + "quote 1.0.35", "report", - "syn 2.0.37", + "syn 2.0.46", "thiserror", ] @@ -699,7 +700,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "base64 0.21.4", + "base64 0.21.5", "bitflags 1.3.2", "bytes", "futures-util", @@ -708,7 +709,7 @@ dependencies = [ "http-body", "hyper", "itoa", - "matchit 0.7.2", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", @@ -779,9 +780,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64ct" @@ -804,9 +805,9 @@ dependencies = [ [[package]] name = "bcs" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd3ffe8b19a604421a5d461d4a70346223e535903fbc3067138bddbebddcf77" +checksum = "85b6598a2f5d564fb7855dc6b06fd1c38cff5a72bd8b863a4d021938497b440a" dependencies = [ "serde", "thiserror", @@ -863,13 +864,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.15", - "proc-macro2 1.0.67", - "quote 1.0.33", + "prettyplease 0.2.16", + "proc-macro2 1.0.74", + "quote 1.0.35", "regex", "rustc-hash", "shlex", - "syn 2.0.37", + "syn 2.0.46", ] [[package]] @@ -885,7 +886,7 @@ dependencies = [ "pbkdf2 0.12.2", "rand_core 0.6.4", "ripemd", - "sha2 0.10.7", + "sha2 0.10.8", "subtle", "zeroize", ] @@ -928,9 +929,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bitmaps" @@ -998,16 +999,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq 0.3.0", - "digest 0.10.7", ] [[package]] @@ -1075,15 +1075,15 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128a44527fc0d6abf05f9eda748b9027536e12dff93f5acc8449f51583309350" +checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" [[package]] name = "brotli" -version = "3.3.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1092,9 +1092,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1112,7 +1112,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ - "sha2 0.10.7", + "sha2 0.10.8", "tinyvec", ] @@ -1162,9 +1162,9 @@ checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -1207,18 +1207,18 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" -version = "0.17.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7daec1a2a2129eeba1644b220b4647ec537b0b5d4bfd6876fcc5a540056b592" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", @@ -1287,9 +1287,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" dependencies = [ "glob", "libc", @@ -1298,9 +1298,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.4" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" +checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" dependencies = [ "clap_builder", "clap_derive", @@ -1308,9 +1308,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.4" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" dependencies = [ "anstream", "anstyle", @@ -1320,21 +1320,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clear_on_drop" @@ -1378,7 +1378,7 @@ dependencies = [ "hmac", "k256", "serde", - "sha2 0.10.7", + "sha2 0.10.8", "thiserror", ] @@ -1394,7 +1394,7 @@ dependencies = [ "once_cell", "pbkdf2 0.12.2", "rand", - "sha2 0.10.7", + "sha2 0.10.8", "thiserror", ] @@ -1404,7 +1404,7 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bech32", "bs58 0.5.0", "digest 0.10.7", @@ -1413,7 +1413,7 @@ dependencies = [ "ripemd", "serde", "serde_derive", - "sha2 0.10.7", + "sha2 0.10.8", "sha3 0.10.8", "thiserror", ] @@ -1432,20 +1432,19 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", "windows-sys 0.48.0", ] [[package]] name = "config" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" dependencies = [ "async-trait", "json5", @@ -1470,7 +1469,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "error-stack", "flagset", "hex", @@ -1493,7 +1492,7 @@ dependencies = [ "axelar-wasm-std", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "error-stack", "flagset", "regex", @@ -1517,21 +1516,22 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08849ed393c907c90016652a01465a12d86361cd38ad2a7de026c56a520cc259" +checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" dependencies = [ "cfg-if", "cpufeatures", "hex", + "proptest", "serde", ] [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" @@ -1562,9 +1562,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -1572,9 +1572,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core2" @@ -1630,11 +1630,12 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca101fbf2f76723711a30ea3771ef312ec3ec254ad021b237871ed802f9f175" +checksum = "d8bb3c77c3b7ce472056968c745eb501c440fbc07be5004eba02782c35bfbbe3" dependencies = [ "digest 0.10.7", + "ecdsa", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -1643,18 +1644,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c73d2dd292f60e42849d2b07c03d809cf31e128a4299a805abd6d24553bcaaf5" +checksum = "fea73e9162e6efde00018d55ed0061e93a108b5d6ec4548b4f8ce3c706249687" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce34a08020433989af5cc470104f6bd22134320fe0221bd8aeb919fd5ec92d5" +checksum = "0df41ea55f2946b6b43579659eec048cc2f66e8c8e2e3652fc5e5e476f673856" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -1665,22 +1666,23 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96694ec781a7dd6dea1f968a2529ade009c21ad999c88b5f53d6cc495b3b96f7" +checksum = "43609e92ce1b9368aa951b334dd354a2d0dd4d484931a5f83ae10e12a26c8ba9" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "cosmwasm-std" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a44d3f9c25b2f864737c6605a98f2e4675d53fd8bbc7cf4d7c02475661a793d" +checksum = "04d6864742e3a7662d024b51a94ea81c9af21db6faea2f9a6d2232bb97c6e53e" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", + "bech32", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -1690,15 +1692,16 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", - "sha2 0.10.7", + "sha2 0.10.8", + "static_assertions", "thiserror", ] [[package]] name = "cosmwasm-storage" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab544dfcad7c9e971933d522d99ec75cc8ddfa338854bb992b092e11bcd7e818" +checksum = "bd2b4ae72a03e8f56c85df59d172d51d2d7dc9cec6e2bc811e3fb60c588032a4" dependencies = [ "cosmwasm-std", "serde", @@ -1706,9 +1709,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -1724,9 +1727,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1734,9 +1737,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -1745,22 +1748,20 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" dependencies = [ "cfg-if", ] @@ -1773,9 +1774,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1848,9 +1849,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -1899,9 +1900,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" +checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" dependencies = [ "cosmwasm-std", "schemars", @@ -1925,13 +1926,13 @@ dependencies = [ [[package]] name = "cw-utils" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.1.0", + "cw2 1.1.2", "schemars", "semver", "serde", @@ -1953,14 +1954,15 @@ dependencies = [ [[package]] name = "cw2" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "schemars", + "semver", "serde", "thiserror", ] @@ -1993,8 +1995,8 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "strsim", "syn 1.0.109", ] @@ -2007,10 +2009,10 @@ checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "strsim", - "syn 2.0.37", + "syn 2.0.46", ] [[package]] @@ -2020,7 +2022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core 0.14.4", - "quote 1.0.33", + "quote 1.0.35", "syn 1.0.109", ] @@ -2031,8 +2033,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", - "quote 1.0.33", - "syn 2.0.37", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -2042,7 +2044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "lock_api", "once_cell", "parking_lot_core", @@ -2050,15 +2052,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "data-encoding-macro" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -2066,9 +2068,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" dependencies = [ "data-encoding", "syn 1.0.109", @@ -2117,17 +2119,18 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ + "powerfmt", "serde", ] @@ -2137,8 +2140,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4255bb7dd538590188bd0aea52e48bd699b19bd90b0d069ec2ced8461fe23273" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -2148,8 +2151,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -2169,8 +2172,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -2191,8 +2194,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "rustc_version", "syn 1.0.109", ] @@ -2284,9 +2287,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -2315,9 +2318,9 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "ec-gpu" @@ -2327,16 +2330,16 @@ checksum = "bd63582de2b59ea1aa48d7c1941b5d87618d95484397521b3acdfa0e1e9f5e45" [[package]] name = "ecdsa" -version = "0.16.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der 0.7.8", "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature 2.1.0", - "spki 0.7.2", + "signature 2.2.0", + "spki 0.7.3", ] [[package]] @@ -2352,12 +2355,12 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8 0.10.2", - "signature 2.1.0", + "signature 2.2.0", ] [[package]] @@ -2382,11 +2385,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ "curve25519-dalek 4.1.1", - "ed25519 2.2.2", + "ed25519 2.2.3", "rand_core 0.6.4", "serde", - "sha2 0.10.7", - "signature 2.1.0", + "sha2 0.10.8", + "signature 2.2.0", "subtle", "zeroize", ] @@ -2414,9 +2417,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "elliptic-curve" -version = "0.13.5" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", @@ -2462,7 +2465,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "hex", "k256", @@ -2488,8 +2491,8 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f16ef37b2a9b242295d61a154ee91ae884afff6b8b933b486b12481cc58310ca" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -2500,9 +2503,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e" dependencies = [ "once_cell", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -2523,23 +2526,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -2569,7 +2561,7 @@ dependencies = [ "scrypt", "serde", "serde_json", - "sha2 0.10.7", + "sha2 0.10.8", "sha3 0.10.8", "thiserror", "uuid 0.8.2", @@ -2618,16 +2610,16 @@ dependencies = [ "impl-codec 0.6.0", "impl-rlp", "impl-serde 0.4.0", - "primitive-types 0.12.1", + "primitive-types 0.12.2", "scale-info", "uint", ] [[package]] name = "ethers" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad13497f6e0a24292fc7b408e30d22fe9dc262da1f40d7b542c3a44e7fc0476" +checksum = "1a5344eea9b20effb5efeaad29418215c4d27017639fd1f908260f59cbbd226e" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2641,9 +2633,9 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e9e8acd0ed348403cc73a670c24daba3226c40b98dc1a41903766b3ab6240a" +checksum = "8c405f24ea3a517899ba7985385c43dc4a7eb1209af3b1e0a1a32d7dcc7f8d09" dependencies = [ "ethers-core", "once_cell", @@ -2653,9 +2645,9 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d79269278125006bb0552349c03593ffa9702112ca88bc7046cc669f148fb47c" +checksum = "0111ead599d17a7bff6985fd5756f39ca7033edc79a31b23026a8d5d64fa95cd" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -2672,9 +2664,9 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce95a43c939b2e4e2f3191c5ad4a1f279780b8a39139c9905b43a7433531e2ab" +checksum = "51258120c6b47ea9d9bec0d90f9e8af71c977fbefbef8213c91bfed385fe45eb" dependencies = [ "Inflector", "const-hex", @@ -2682,39 +2674,39 @@ dependencies = [ "ethers-core", "ethers-etherscan", "eyre", - "prettyplease 0.2.15", - "proc-macro2 1.0.67", - "quote 1.0.33", + "prettyplease 0.2.16", + "proc-macro2 1.0.74", + "quote 1.0.35", "regex", "reqwest", "serde", "serde_json", - "syn 2.0.37", - "toml 0.7.8", + "syn 2.0.46", + "toml 0.8.2", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9ce44906fc871b3ee8c69a695ca7ec7f70e50cb379c9b9cb5e532269e492f6" +checksum = "936e7a0f1197cee2b62dc89f63eff3201dbf87c283ff7e18d86d38f83b845483" dependencies = [ "Inflector", "const-hex", "ethers-contract-abigen", "ethers-core", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "serde_json", - "syn 2.0.37", + "syn 2.0.46", ] [[package]] name = "ethers-core" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a17f0708692024db9956b31d7a20163607d2745953f5ae8125ab368ba280ad" +checksum = "2f03e0bdc216eeb9e355b90cf610ef6c5bb8aca631f97b5ae9980ce34ea7878d" dependencies = [ "arrayvec", "bytes", @@ -2733,7 +2725,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.37", + "syn 2.0.46", "tempfile", "thiserror", "tiny-keccak", @@ -2742,10 +2734,11 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e53451ea4a8128fbce33966da71132cf9e1040dcfd2a2084fd7733ada7b2045" +checksum = "abbac2c890bdbe0f1b8e549a53b00e2c4c1de86bb077c1094d1f38cdf9381a56" dependencies = [ + "chrono", "ethers-core", "reqwest", "semver", @@ -2757,9 +2750,9 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473f1ccd0c793871bbc248729fa8df7e6d2981d6226e4343e3bbaa9281074d5d" +checksum = "681ece6eb1d10f7cf4f873059a77c04ff1de4f35c63dd7bccde8f438374fcb93" dependencies = [ "async-trait", "auto_impl", @@ -2784,13 +2777,13 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6838fa110e57d572336178b7c79e94ff88ef976306852d8cb87d9e5b1fc7c0b5" +checksum = "25d6c0c9455d93d4990c06e049abf9b30daf148cf461ee939c11d88907c60816" dependencies = [ "async-trait", "auto_impl", - "base64 0.21.4", + "base64 0.21.5", "bytes", "const-hex", "enr", @@ -2821,9 +2814,9 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea44bec930f12292866166f9ddbea6aa76304850e4d8dcd66dc492b43d00ff1" +checksum = "0cb1b714e227bbd2d8c53528adb580b203009728b17d0d0e4119353aa9bc5532" dependencies = [ "async-trait", "coins-bip32", @@ -2833,16 +2826,16 @@ dependencies = [ "eth-keystore", "ethers-core", "rand", - "sha2 0.10.7", + "sha2 0.10.8", "thiserror", "tracing", ] [[package]] name = "ethers-solc" -version = "2.0.10" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de34e484e7ae3cab99fbfd013d6c5dc7f9013676a4e0e414d8b12e1213e8b3ba" +checksum = "a64f710586d147864cff66540a6d64518b9ff37d73ef827fee430538265b595f" dependencies = [ "cfg-if", "const-hex", @@ -2872,16 +2865,16 @@ dependencies = [ [[package]] name = "ethnum" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8ff382b2fa527fb7fb06eeebfc5bbb3f17e3cc6b9d70b006c41daa8824adac" +checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" [[package]] name = "events" version = "0.1.0" dependencies = [ "axelar-wasm-std", - "base64 0.21.4", + "base64 0.21.5", "error-stack", "serde_json", "tendermint 0.33.0", @@ -2894,17 +2887,17 @@ version = "0.1.0" dependencies = [ "error-stack", "events", - "quote 1.0.33", + "quote 1.0.35", "serde", "serde_json", - "syn 2.0.37", + "syn 2.0.46", ] [[package]] name = "eyre" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" dependencies = [ "indenter", "once_cell", @@ -2956,9 +2949,9 @@ dependencies = [ "serde_bytes", "serde_json", "serde_with 2.3.3", - "sha2 0.10.7", + "sha2 0.10.8", "sha3 0.10.8", - "signature 2.1.0", + "signature 2.2.0", "static_assertions", "thiserror", "tokio", @@ -2972,8 +2965,8 @@ version = "0.1.3" source = "git+https://github.com/MystenLabs/fastcrypto?rev=69180dc7275f5f0efb69e11e9d03f6db338d1dd6#69180dc7275f5f0efb69e11e9d03f6db338d1dd6" dependencies = [ "convert_case 0.6.0", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -3024,7 +3017,7 @@ dependencies = [ "once_cell", "regex", "reqwest", - "rustls-webpki 0.101.5", + "rustls-webpki", "schemars", "serde", "serde_json", @@ -3042,9 +3035,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fdlimit" @@ -3079,8 +3072,8 @@ dependencies = [ "num-bigint 0.3.3", "num-integer", "num-traits", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -3137,9 +3130,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -3172,9 +3165,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -3215,9 +3208,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -3230,9 +3223,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -3240,15 +3233,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -3257,9 +3250,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-locks" @@ -3273,26 +3266,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" @@ -3306,9 +3299,9 @@ dependencies = [ [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -3343,7 +3336,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "error-stack", "itertools 0.11.0", "mockall", @@ -3376,9 +3369,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "js-sys", @@ -3399,9 +3392,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -3436,9 +3429,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -3446,10 +3439,10 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap 2.1.0", "slab", "tokio", - "tokio-util 0.7.9", + "tokio-util 0.7.10", "tracing", ] @@ -3459,7 +3452,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", ] [[package]] @@ -3468,14 +3461,14 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", ] [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "hashers" @@ -3488,11 +3481,11 @@ dependencies = [ [[package]] name = "hdrhistogram" -version = "7.5.2" +version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ - "base64 0.13.1", + "base64 0.21.5", "byteorder", "crossbeam-channel", "flate2", @@ -3506,7 +3499,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "headers-core", "http", @@ -3556,9 +3549,9 @@ dependencies = [ [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -3574,18 +3567,18 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -3594,9 +3587,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -3639,9 +3632,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -3654,7 +3647,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.5.5", "tokio", "tower-service", "tracing", @@ -3700,14 +3693,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.7", + "rustls 0.21.10", "tokio", "tokio-rustls 0.24.1", ] @@ -3726,16 +3719,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -3755,9 +3748,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -3792,7 +3785,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec 3.6.5", + "parity-scale-codec 3.6.9", ] [[package]] @@ -3828,8 +3821,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -3852,12 +3845,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "serde", ] @@ -3873,9 +3866,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.31.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" +checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" dependencies = [ "console", "lazy_static", @@ -3908,7 +3901,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "error-stack", "gateway", "itertools 0.11.0", @@ -3933,7 +3926,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab388864246d58a276e60e7569a833d9cc4cd75c66e5ca77c177dad38e59996" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", "dashmap", "hashbrown 0.12.3", "once_cell", @@ -3942,9 +3935,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "iri-string" @@ -3957,13 +3950,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ "hermit-abi", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3986,24 +3979,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -4034,9 +4027,9 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "pem", - "ring", + "ring 0.16.20", "serde", "serde_json", "simple_asn1", @@ -4044,16 +4037,16 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", "once_cell", - "sha2 0.10.7", - "signature 2.1.0", + "sha2 0.10.8", + "signature 2.2.0", ] [[package]] @@ -4099,7 +4092,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] @@ -4110,25 +4103,36 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" dependencies = [ "cfg-if", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.1", + "libc", + "redox_syscall", +] [[package]] name = "librocksdb-sys" @@ -4165,15 +4169,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -4212,33 +4216,25 @@ checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" [[package]] name = "matchit" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest 0.10.7", ] [[package]] name = "memchr" -version = "2.6.3" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "merlin" @@ -4285,9 +4281,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", @@ -4317,8 +4313,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -4630,8 +4626,8 @@ version = "0.1.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ "enum-compat-util", - "quote 1.0.33", - "syn 2.0.37", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -4695,7 +4691,7 @@ name = "msim" version = "0.1.0" source = "git+https://github.com/MystenLabs/mysten-sim.git?rev=1a52783d6600ecc22e15253a982f77881bd47c77#1a52783d6600ecc22e15253a982f77881bd47c77" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.7", "async-task", "bincode", "bytes", @@ -4711,7 +4707,7 @@ dependencies = [ "rand", "real_tokio", "serde", - "socket2 0.4.9", + "socket2 0.4.10", "tap", "tokio-util 0.7.7", "toml 0.5.11", @@ -4725,8 +4721,8 @@ version = "0.1.0" source = "git+https://github.com/MystenLabs/mysten-sim.git?rev=1a52783d6600ecc22e15253a982f77881bd47c77#1a52783d6600ecc22e15253a982f77881bd47c77" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -4777,10 +4773,10 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.1.3", "proc-macro-error", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", "synstructure", ] @@ -4803,7 +4799,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "enum-display-derive", "error-stack", "getrandom", @@ -4830,8 +4826,8 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "either", "elliptic-curve", "error-stack", @@ -4870,7 +4866,7 @@ dependencies = [ "tap", "tokio", "tracing", - "uuid 1.4.1", + "uuid 1.6.1", "workspace-hack", ] @@ -4923,7 +4919,7 @@ name = "mysten-util-mem-derive" version = "0.1.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.74", "syn 1.0.109", "synstructure", "workspace-hack", @@ -5015,7 +5011,7 @@ dependencies = [ "connection-router", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "error-stack", "hex", "mockall", @@ -5120,8 +5116,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -5160,9 +5156,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", @@ -5180,30 +5176,30 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" dependencies = [ - "proc-macro-crate", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro-crate 2.0.1", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -5219,9 +5215,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -5249,8 +5245,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" dependencies = [ "bytes", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -5295,9 +5291,9 @@ checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -5315,7 +5311,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -5354,15 +5350,15 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.5" +version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" dependencies = [ "arrayvec", "bitvec 1.0.1", "byte-slice-cast", "impl-trait-for-tuples", - "parity-scale-codec-derive 3.6.5", + "parity-scale-codec-derive 3.6.9", "serde", ] @@ -5372,21 +5368,21 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" dependencies = [ - "proc-macro-crate", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro-crate 1.1.3", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "parity-scale-codec-derive" -version = "3.6.5" +version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro-crate 2.0.1", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -5402,13 +5398,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", "windows-targets 0.48.5", ] @@ -5469,7 +5465,7 @@ dependencies = [ "digest 0.10.7", "hmac", "password-hash", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -5505,8 +5501,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" dependencies = [ "peg-runtime", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", ] [[package]] @@ -5544,15 +5540,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.3" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" dependencies = [ "memchr", "thiserror", @@ -5561,9 +5557,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.3" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" dependencies = [ "pest", "pest_generator", @@ -5571,26 +5567,26 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.3" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "pest_meta" -version = "2.7.3" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" dependencies = [ "once_cell", "pest", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -5610,7 +5606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.0.0", + "indexmap 2.1.0", ] [[package]] @@ -5651,9 +5647,9 @@ checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", "phf_shared 0.11.2", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -5689,9 +5685,9 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -5735,20 +5731,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der 0.7.8", - "spki 0.7.2", + "spki 0.7.3", ] [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "platforms" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" [[package]] name = "polyval" @@ -5762,6 +5758,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -5810,25 +5812,25 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.74", "syn 1.0.109", ] [[package]] name = "prettyplease" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ - "proc-macro2 1.0.67", - "syn 2.0.37", + "proc-macro2 1.0.74", + "syn 2.0.46", ] [[package]] name = "primeorder" -version = "0.13.2" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] @@ -5847,9 +5849,9 @@ dependencies = [ [[package]] name = "primitive-types" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash 0.8.0", "impl-codec 0.6.0", @@ -5869,6 +5871,16 @@ dependencies = [ "toml 0.5.11", ] +[[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", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -5876,8 +5888,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", "version_check", ] @@ -5888,8 +5900,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "version_check", ] @@ -5904,9 +5916,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" dependencies = [ "unicode-ident", ] @@ -5939,19 +5951,19 @@ dependencies = [ [[package]] name = "proptest" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", - "bitflags 1.3.2", - "byteorder", + "bit-vec", + "bitflags 2.4.1", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.6.29", + "regex-syntax 0.8.2", "rusty-fork", "tempfile", "unarray", @@ -6028,8 +6040,8 @@ checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -6041,8 +6053,8 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -6054,9 +6066,9 @@ checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", "itertools 0.11.0", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -6095,7 +6107,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.21.7", + "rustls 0.21.10", "thiserror", "tokio", "tracing", @@ -6103,15 +6115,15 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13f81c9a9d574310b8351f8666f5a93ac3b0069c45c28ad52c10291389a7cf9" +checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" dependencies = [ "bytes", "rand", - "ring", + "ring 0.16.20", "rustc-hash", - "rustls 0.21.7", + "rustls 0.21.10", "slab", "thiserror", "tinyvec", @@ -6126,7 +6138,7 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2 0.5.4", + "socket2 0.5.5", "tracing", "windows-sys 0.48.0", ] @@ -6142,11 +6154,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ - "proc-macro2 1.0.67", + "proc-macro2 1.0.74", ] [[package]] @@ -6217,18 +6229,18 @@ dependencies = [ [[package]] name = "random-string" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e63111ec5292d8af9c220f06fe3bb87991cc78b6f1f7e291d1ae6b8a60817" +checksum = "e3ada7982e596021551fc3b96413c6077d2ebf57a8f94a3aed70cc71dda78c38" dependencies = [ "fastrand 1.9.0", ] [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -6236,14 +6248,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -6253,20 +6263,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem", - "ring", + "ring 0.16.20", "time", "yasna", ] [[package]] name = "readonly" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f439da1766942fe069954da6058b2e6c1760eb878bae76f5be9fc29f56f574" +checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -6282,58 +6292,49 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.4.9", - "tokio-macros 2.1.0 (git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=e4693500118d5e79ce098ee6dfc2c48f3ef19e45)", + "socket2 0.4.10", + "tokio-macros 2.1.0", "windows-sys 0.48.0", ] [[package]] name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ "getrandom", - "redox_syscall 0.2.16", + "libredox", "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acde58d073e9c79da00f2b5b84eed919c8326832648a5b109b3fce1bb1175280" +checksum = "c4846d4c50d1721b1a3bef8af76924eef20d5e723647333798c1b519b3a9473f" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" +checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -6359,12 +6360,6 @@ dependencies = [ "regex-syntax 0.8.2", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.7.5" @@ -6390,11 +6385,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -6403,7 +6398,7 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-rustls 0.24.1", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -6411,11 +6406,12 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.7", + "rustls 0.21.10", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-rustls 0.24.1", "tower-service", @@ -6423,7 +6419,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.2", + "webpki-roots 0.25.3", "winreg", ] @@ -6442,7 +6438,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "error-stack", "itertools 0.11.0", "mockall", @@ -6470,12 +6466,26 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -6502,8 +6512,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -6554,8 +6564,8 @@ dependencies = [ "pkcs1", "pkcs8 0.9.0", "rand_core 0.6.4", - "sha2 0.10.7", - "signature 2.1.0", + "sha2 0.10.8", + "signature 2.2.0", "subtle", "zeroize", ] @@ -6608,15 +6618,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6627,21 +6637,21 @@ checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.1", "log", - "ring", + "ring 0.16.20", "sct 0.6.1", "webpki", ] [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring", - "rustls-webpki 0.101.5", - "sct 0.7.0", + "ring 0.17.7", + "rustls-webpki", + "sct 0.7.1", ] [[package]] @@ -6658,31 +6668,21 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" -dependencies = [ - "base64 0.21.4", -] - -[[package]] -name = "rustls-webpki" -version = "0.100.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6a5fc258f1c1276dfe3016516945546e2d5383911efc0fc4f1cdc5df3a4ae3" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "ring", - "untrusted", + "base64 0.21.5", ] [[package]] name = "rustls-webpki" -version = "0.101.5" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.7", + "untrusted 0.9.0", ] [[package]] @@ -6705,9 +6705,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "salsa20" @@ -6729,42 +6729,42 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0a159d0c45c12b20c5a844feb1fe4bea86e28f17b92a5f0c42193634d3782" +checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" dependencies = [ "cfg-if", "derive_more", - "parity-scale-codec 3.6.5", + "parity-scale-codec 3.6.9", "scale-info-derive", ] [[package]] name = "scale-info-derive" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29" +checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" dependencies = [ - "proc-macro-crate", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro-crate 1.1.3", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "schemars" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", "either", @@ -6775,12 +6775,12 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "serde_derive_internals", "syn 1.0.109", ] @@ -6800,7 +6800,7 @@ dependencies = [ "hmac", "pbkdf2 0.11.0", "salsa20", - "sha2 0.10.7", + "sha2 0.10.8", ] [[package]] @@ -6809,18 +6809,18 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.7", + "untrusted 0.9.0", ] [[package]] @@ -6882,9 +6882,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" dependencies = [ "serde", ] @@ -6903,9 +6903,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" dependencies = [ "serde_derive", ] @@ -6942,22 +6942,22 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.12" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -6966,18 +6966,18 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -6985,9 +6985,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" dependencies = [ "itoa", "serde", @@ -6995,20 +6995,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -7043,18 +7043,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.0.0", + "indexmap 2.1.0", "serde", "serde_json", - "serde_with_macros 3.3.0", + "serde_with_macros 3.4.0", "time", ] @@ -7065,21 +7065,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ "darling 0.20.3", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "serde_with_macros" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ "darling 0.20.3", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -7105,7 +7105,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "error-stack", "report", "schemars", @@ -7115,9 +7115,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -7139,9 +7139,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -7182,9 +7182,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -7225,9 +7225,9 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -7235,9 +7235,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.2.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" [[package]] name = "simple_asn1" @@ -7278,21 +7278,21 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "snap" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -7300,9 +7300,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", @@ -7310,9 +7310,9 @@ dependencies = [ [[package]] name = "solang-parser" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb9fa2fa2fa6837be8a2495486ff92e3ffe68a99b6eeba288e139efdd842457" +checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" dependencies = [ "itertools 0.11.0", "lalrpop", @@ -7328,6 +7328,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.6.0" @@ -7340,9 +7346,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der 0.7.8", @@ -7388,7 +7394,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ - "strum_macros 0.25.2", + "strum_macros 0.25.3", ] [[package]] @@ -7398,30 +7404,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "rustversion", "syn 1.0.109", ] [[package]] name = "strum_macros" -version = "0.25.2" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "rustversion", - "syn 2.0.37", + "syn 2.0.46", ] [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subtle-encoding" @@ -7555,10 +7561,10 @@ version = "0.7.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ "msim-macros", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "sui-enum-compat-util", - "syn 2.0.37", + "syn 2.0.46", "workspace-hack", ] @@ -7582,8 +7588,8 @@ name = "sui-protocol-config-macros" version = "0.1.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", "workspace-hack", ] @@ -7664,9 +7670,9 @@ dependencies = [ [[package]] name = "svm-rs" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597e3a746727984cb7ea2487b6a40726cad0dbe86628e7d429aa6b8c4c153db4" +checksum = "20689c7d03b6461b502d0b95d6c24874c7d24dea2688af80486a130a06af3b07" dependencies = [ "dirs", "fs2", @@ -7676,7 +7682,7 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2 0.10.7", + "sha2 0.10.8", "thiserror", "url", "zip", @@ -7699,19 +7705,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.37" +version = "2.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "unicode-ident", ] @@ -7727,12 +7733,33 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", "unicode-xid 0.2.4", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tabled" version = "0.12.2" @@ -7752,8 +7779,8 @@ checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -7765,15 +7792,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.8.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", - "fastrand 2.0.0", - "redox_syscall 0.3.5", + "fastrand 2.0.1", + "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -7784,7 +7811,7 @@ checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" dependencies = [ "bytes", "digest 0.10.7", - "ed25519 2.2.2", + "ed25519 2.2.3", "ed25519-consensus", "flex-error", "futures", @@ -7798,8 +7825,8 @@ dependencies = [ "serde_bytes", "serde_json", "serde_repr", - "sha2 0.10.7", - "signature 2.1.0", + "sha2 0.10.8", + "signature 2.2.0", "subtle", "subtle-encoding", "tendermint-proto 0.32.2", @@ -7814,7 +7841,7 @@ source = "git+https://github.com/axelarnetwork/tendermint-rs.git?branch=v0.33.x# dependencies = [ "bytes", "digest 0.10.7", - "ed25519 2.2.2", + "ed25519 2.2.3", "ed25519-consensus", "flex-error", "futures", @@ -7826,8 +7853,8 @@ dependencies = [ "serde_bytes", "serde_json", "serde_repr", - "sha2 0.10.7", - "signature 2.1.0", + "sha2 0.10.8", + "signature 2.2.0", "subtle", "subtle-encoding", "tendermint-proto 0.33.0", @@ -7948,9 +7975,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -7963,22 +7990,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -8002,12 +8029,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -8015,15 +8043,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -8061,23 +8089,23 @@ dependencies = [ "crypto-bigint", "der 0.7.8", "ecdsa", - "ed25519 2.2.2", + "ed25519 2.2.3", "ed25519-dalek", "hmac", "k256", "rand", "rand_chacha", "serde", - "sha2 0.10.7", + "sha2 0.10.8", "tracing", "zeroize", ] [[package]] name = "tokio" -version = "1.32.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -8087,8 +8115,8 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", - "tokio-macros 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.5.5", + "tokio-macros 2.2.0", "windows-sys 0.48.0", ] @@ -8105,22 +8133,22 @@ dependencies = [ [[package]] name = "tokio-macros" version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=e4693500118d5e79ce098ee6dfc2c48f3ef19e45#e4693500118d5e79ce098ee6dfc2c48f3ef19e45" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "tokio-macros" -version = "2.1.0" -source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=e4693500118d5e79ce098ee6dfc2c48f3ef19e45#e4693500118d5e79ce098ee6dfc2c48f3ef19e45" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -8140,7 +8168,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.7", + "rustls 0.21.10", "tokio", ] @@ -8153,22 +8181,22 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util 0.7.9", + "tokio-util 0.7.10", ] [[package]] name = "tokio-tungstenite" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls 0.21.7", + "rustls 0.21.10", "tokio", "tokio-rustls 0.24.1", "tungstenite", - "webpki-roots 0.23.1", + "webpki-roots 0.25.3", ] [[package]] @@ -8190,9 +8218,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -8213,9 +8241,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.8" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", @@ -8234,11 +8262,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", @@ -8269,7 +8297,7 @@ dependencies = [ "prost-derive 0.11.9", "tokio", "tokio-stream", - "tokio-util 0.7.9", + "tokio-util 0.7.10", "tower", "tower-layer", "tower-service", @@ -8286,7 +8314,7 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.4", + "base64 0.21.5", "bytes", "h2", "http", @@ -8296,7 +8324,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost 0.12.3", - "rustls 0.21.7", + "rustls 0.21.10", "rustls-pemfile", "tokio", "tokio-rustls 0.24.1", @@ -8314,9 +8342,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ "prettyplease 0.1.25", - "proc-macro2 1.0.67", + "proc-macro2 1.0.74", "prost-build", - "quote 1.0.33", + "quote 1.0.35", "syn 1.0.109", ] @@ -8348,7 +8376,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.9", + "tokio-util 0.7.10", "tower-layer", "tower-service", "tracing", @@ -8376,12 +8404,12 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tokio", - "tokio-util 0.7.9", + "tokio-util 0.7.10", "tower", "tower-layer", "tower-service", "tracing", - "uuid 1.4.1", + "uuid 1.6.1", ] [[package]] @@ -8398,11 +8426,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -8411,20 +8438,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -8442,12 +8469,12 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -8465,9 +8492,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "nu-ansi-term", "serde", @@ -8488,8 +8515,8 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -8501,15 +8528,15 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ "byteorder", "bytes", @@ -8518,7 +8545,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.21.7", + "rustls 0.21.10", "sha1", "thiserror", "url", @@ -8594,9 +8621,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -8659,11 +8686,17 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -8694,9 +8727,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.4.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "getrandom", "rand", @@ -8717,8 +8750,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d44690c645190cfce32f91a1582281654b2338c6073fa250b0949fd25c55b32" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", + "proc-macro2 1.0.74", + "quote 1.0.35", "syn 1.0.109", ] @@ -8738,7 +8771,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae2faf80ac463422992abf4de234731279c058aaf33171ca70277c98406b124" dependencies = [ - "quote 1.0.33", + "quote 1.0.35", "syn 1.0.109", ] @@ -8765,7 +8798,7 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "either", "error-stack", "report", @@ -8814,9 +8847,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -8824,24 +8857,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -8851,38 +8884,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ - "quote 1.0.33", + "quote 1.0.35", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -8894,8 +8927,8 @@ version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -8909,18 +8942,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.23.1" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" -dependencies = [ - "rustls-webpki 0.100.3", -] - -[[package]] -name = "webpki-roots" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "which" @@ -8982,12 +9006,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] @@ -9008,6 +9032,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -9038,6 +9071,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -9050,6 +9098,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -9062,6 +9116,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -9074,6 +9134,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -9086,6 +9152,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -9098,6 +9170,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -9110,6 +9188,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -9122,11 +9206,17 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" dependencies = [ "memchr", ] @@ -9199,6 +9289,30 @@ dependencies = [ "time", ] +[[package]] +name = "xrpl-voting-verifier" +version = "0.1.0" +dependencies = [ + "axelar-wasm-std", + "axelar-wasm-std-derive", + "connection-router", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "either", + "error-stack", + "report", + "rewards", + "schemars", + "serde", + "serde_json", + "service-registry", + "sha3 0.10.8", + "thiserror", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -9223,11 +9337,31 @@ dependencies = [ "time", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", +] + [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -9238,9 +9372,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.67", - "quote 1.0.33", - "syn 2.0.37", + "proc-macro2 1.0.74", + "quote 1.0.35", + "syn 2.0.46", ] [[package]] @@ -9284,11 +9418,10 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", - "libc", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index b0c180b50..36ec10442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ axelar-wasm-std = { version = "^0.1.0", path = "packages/axelar-wasm-std" } axelar-wasm-std-derive = { version = "^0.1.0", path = "packages/axelar-wasm-std-derive" } itertools = "0.11.0" voting-verifier = { version = "^0.1.0", path = "contracts/voting-verifier" } +xrpl-voting-verifier = { version = "^0.1.0", path = "contracts/voting-verifier" } multisig = { version = "^0.1.0", path = "contracts/multisig" } multisig-prover = { version = "^0.1.0", path = "contracts/multisig-prover" } service-registry = { version = "^0.1.0", path = "contracts/service-registry" } diff --git a/contracts/xrpl-voting-verifier/.cargo/config b/contracts/xrpl-voting-verifier/.cargo/config new file mode 100644 index 000000000..01cb77277 --- /dev/null +++ b/contracts/xrpl-voting-verifier/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/xrpl-voting-verifier/Cargo.toml b/contracts/xrpl-voting-verifier/Cargo.toml new file mode 100644 index 000000000..6d7ee4a2d --- /dev/null +++ b/contracts/xrpl-voting-verifier/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "xrpl-voting-verifier" +version = "0.1.0" +edition = "2021" +description = "Voting verifier contract" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.6 +""" + +[dependencies] +axelar-wasm-std = { workspace = true } +axelar-wasm-std-derive = { workspace = true } +connection-router = { workspace = true, features = ["library"] } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } +cw-storage-plus = { workspace = true } +either = "1.8.1" +error-stack = { workspace = true } +report = { workspace = true } +rewards = { workspace = true, features = ["library"] } +schemars = "0.8.10" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +serde_json = "1.0.89" +service-registry = { workspace = true, features = ["library"] } +sha3 = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +cw-multi-test = "0.15.1" diff --git a/contracts/xrpl-voting-verifier/src/bin/schema.rs b/contracts/xrpl-voting-verifier/src/bin/schema.rs new file mode 100644 index 000000000..6f6a8eb3f --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use voting_verifier::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/xrpl-voting-verifier/src/contract.rs b/contracts/xrpl-voting-verifier/src/contract.rs new file mode 100644 index 000000000..77f1c5007 --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/contract.rs @@ -0,0 +1,64 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Attribute, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, +}; + +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::{Config, CONFIG}; +use crate::{execute, query}; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let config = Config { + service_name: msg.service_name, + service_registry_contract: deps.api.addr_validate(&msg.service_registry_address)?, + source_gateway_address: msg.source_gateway_address, + voting_threshold: msg.voting_threshold, + block_expiry: msg.block_expiry, + confirmation_height: msg.confirmation_height, + source_chain: msg.source_chain, + rewards_contract: deps.api.addr_validate(&msg.rewards_address)?, + }; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::new() + .add_event(Event::new("instantiated").add_attributes(>::from(config)))) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::VerifyMessages { messages } => execute::verify_messages(deps, env, messages), + ExecuteMsg::Vote { poll_id, votes } => execute::vote(deps, env, info, poll_id, votes), + ExecuteMsg::EndPoll { poll_id } => execute::end_poll(deps, env, poll_id), + ExecuteMsg::VerifyWorkerSet { + message_id, + new_operators, + } => execute::verify_worker_set(deps, env, message_id, new_operators), + } + .map_err(axelar_wasm_std::ContractError::from) +} +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::IsVerified { messages } => to_binary(&query::is_verified(deps, &messages)?), + + QueryMsg::GetPoll { poll_id: _ } => { + todo!() + } + QueryMsg::IsWorkerSetVerified { new_operators } => { + to_binary(&query::is_worker_set_verified(deps, &new_operators)?) + } + } +} diff --git a/contracts/xrpl-voting-verifier/src/error.rs b/contracts/xrpl-voting-verifier/src/error.rs new file mode 100644 index 000000000..9dabbc0cc --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/error.rs @@ -0,0 +1,46 @@ +use axelar_wasm_std::{nonempty, voting}; +use axelar_wasm_std_derive::IntoContractError; +use connection_router; +use connection_router::state::ChainName; +use cosmwasm_std::StdError; +use service_registry; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, IntoContractError)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] StdError), + + #[error(transparent)] + RouterError(#[from] connection_router::ContractError), + + #[error(transparent)] + NonEmptyError(#[from] nonempty::Error), + + #[error(transparent)] + ServiceRegistryError(#[from] service_registry::ContractError), + + #[error("empty batch of messages")] + EmptyMessages, + + #[error("all messages must have the same source chain {0}")] + SourceChainMismatch(ChainName), + + #[error("invalid message id {0}")] + InvalidMessageID(String), + + #[error("poll not found")] + PollNotFound, + + #[error(transparent)] + VoteError(#[from] voting::Error), + + #[error("worker set already confirmed")] + WorkerSetAlreadyConfirmed, +} + +impl From for StdError { + fn from(value: ContractError) -> Self { + Self::generic_err(value.to_string()) + } +} diff --git a/contracts/xrpl-voting-verifier/src/events.rs b/contracts/xrpl-voting-verifier/src/events.rs new file mode 100644 index 000000000..2723d066f --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/events.rs @@ -0,0 +1,208 @@ +use std::vec::Vec; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Attribute, Event}; + +use axelar_wasm_std::nonempty; +use axelar_wasm_std::operators::Operators; +use axelar_wasm_std::voting::{PollId, Vote}; +use connection_router::state::{Address, ChainName, Message, ID_SEPARATOR}; + +use crate::error::ContractError; +use crate::state::Config; + +impl From for Vec { + fn from(other: Config) -> Self { + vec![ + ("service_name", other.service_name.to_string()), + ( + "service_registry_contract", + other.service_registry_contract.to_string(), + ), + ( + "source_gateway_address", + other.source_gateway_address.to_string(), + ), + ("voting_threshold", other.voting_threshold.to_string()), + ("block_expiry", other.block_expiry.to_string()), + ("confirmation_height", other.confirmation_height.to_string()), + ] + .into_iter() + .map(Attribute::from) + .collect() + } +} + +pub struct PollMetadata { + pub poll_id: PollId, + pub source_chain: ChainName, + pub source_gateway_address: nonempty::String, + pub confirmation_height: u64, + pub expires_at: u64, + pub participants: Vec, +} + +pub enum PollStarted { + Messages { + messages: Vec, + metadata: PollMetadata, + }, + WorkerSet { + worker_set: WorkerSetConfirmation, + metadata: PollMetadata, + }, +} + +impl From for Vec { + fn from(value: PollMetadata) -> Self { + vec![ + ( + "poll_id", + &serde_json::to_string(&value.poll_id).expect("failed to serialize poll_id"), + ), + ("source_chain", &value.source_chain.to_string()), + ("source_gateway_address", &value.source_gateway_address), + ( + "confirmation_height", + &value.confirmation_height.to_string(), + ), + ("expires_at", &value.expires_at.to_string()), + ( + "participants", + &serde_json::to_string(&value.participants) + .expect("failed to serialize participants"), + ), + ] + .into_iter() + .map(Attribute::from) + .collect() + } +} + +impl From for Event { + fn from(other: PollStarted) -> Self { + match other { + PollStarted::Messages { + messages: data, + metadata, + } => Event::new("messages_poll_started") + .add_attribute( + "messages", + serde_json::to_string(&data).expect("failed to serialize messages"), + ) + .add_attributes(Vec::<_>::from(metadata)), + PollStarted::WorkerSet { + worker_set: data, + metadata, + } => Event::new("worker_set_poll_started") + .add_attribute( + "worker_set", + serde_json::to_string(&data) + .expect("failed to serialize worker set confirmation"), + ) + .add_attributes(Vec::<_>::from(metadata)), + } + } +} + +#[cw_serde] +pub struct WorkerSetConfirmation { + pub tx_id: nonempty::String, + pub event_index: u64, + pub operators: Operators, +} + +impl WorkerSetConfirmation { + pub fn new(message_id: nonempty::String, operators: Operators) -> Result { + let (tx_id, event_index) = parse_message_id(&message_id)?; + Ok(Self { + tx_id, + event_index, + operators, + }) + } +} + +#[cw_serde] +pub struct TxEventConfirmation { + pub tx_id: nonempty::String, + pub event_index: u64, + pub destination_address: Address, + pub destination_chain: ChainName, + pub source_address: Address, + /// for better user experience, the payload hash gets encoded into hex at the edges (input/output), + /// but internally, we treat it as raw bytes to enforce it's format. + #[serde(with = "axelar_wasm_std::hex")] + #[schemars(with = "String")] // necessary attribute in conjunction with #[serde(with ...)] + pub payload_hash: [u8; 32], +} + +impl TryFrom for TxEventConfirmation { + type Error = ContractError; + + fn try_from(other: Message) -> Result { + let (tx_id, event_index) = parse_message_id(&other.cc_id.id)?; + + Ok(TxEventConfirmation { + tx_id, + event_index, + destination_address: other.destination_address, + destination_chain: other.destination_chain, + source_address: other.source_address, + payload_hash: other.payload_hash, + }) + } +} + +fn parse_message_id( + message_id: &nonempty::String, +) -> Result<(nonempty::String, u64), ContractError> { + // expected format: : + let components = message_id.split(ID_SEPARATOR).collect::>(); + + if components.len() != 2 { + return Err(ContractError::InvalidMessageID(message_id.to_string())); + } + + Ok(( + components[0].try_into()?, + components[1] + .parse::() + .map_err(|_| ContractError::InvalidMessageID(message_id.to_string()))?, + )) +} + +pub struct Voted { + pub poll_id: PollId, + pub voter: Addr, +} + +impl From for Event { + fn from(other: Voted) -> Self { + Event::new("voted") + .add_attribute( + "poll_id", + serde_json::to_string(&other.poll_id).expect("failed to serialize poll_id"), + ) + .add_attribute("voter", other.voter) + } +} + +pub struct PollEnded { + pub poll_id: PollId, + pub results: Vec>, +} + +impl From for Event { + fn from(other: PollEnded) -> Self { + Event::new("poll_ended") + .add_attribute( + "poll_id", + serde_json::to_string(&other.poll_id).expect("failed to serialize poll_id"), + ) + .add_attribute( + "results", + serde_json::to_string(&other.results).expect("failed to serialize results"), + ) + } +} diff --git a/contracts/xrpl-voting-verifier/src/execute.rs b/contracts/xrpl-voting-verifier/src/execute.rs new file mode 100644 index 000000000..f9a16dac6 --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/execute.rs @@ -0,0 +1,270 @@ +use axelar_wasm_std::operators::Operators; +use cosmwasm_std::{ + to_binary, Deps, DepsMut, Env, MessageInfo, QueryRequest, Response, Storage, WasmMsg, WasmQuery, +}; + +use axelar_wasm_std::voting::{PollId, Vote}; +use axelar_wasm_std::{nonempty, snapshot, voting::WeightedPoll}; +use connection_router::state::{ChainName, Message}; +use service_registry::msg::QueryMsg; +use service_registry::state::Worker; + +use crate::error::ContractError; +use crate::events::{ + PollEnded, PollMetadata, PollStarted, TxEventConfirmation, Voted, WorkerSetConfirmation, +}; +use crate::msg::{EndPollResponse, VerifyMessagesResponse}; +use crate::query::{ + is_verified, is_worker_set_verified, msg_verification_status, VerificationStatus, +}; +use crate::state::{self, Poll, PollContent, POLL_MESSAGES, POLL_WORKER_SETS}; +use crate::state::{CONFIG, POLLS, POLL_ID}; + +pub fn verify_worker_set( + deps: DepsMut, + env: Env, + message_id: nonempty::String, + new_operators: Operators, +) -> Result { + if is_worker_set_verified(deps.as_ref(), &new_operators)? { + return Err(ContractError::WorkerSetAlreadyConfirmed); + } + + let config = CONFIG.load(deps.storage)?; + let snapshot = take_snapshot(deps.as_ref(), &config.source_chain)?; + let participants = snapshot.get_participants(); + + let poll_id = create_worker_set_poll( + deps.storage, + env.block.height, + config.block_expiry, + snapshot, + )?; + + POLL_WORKER_SETS.save( + deps.storage, + &new_operators.hash(), + &PollContent::::new(new_operators.clone(), poll_id), + )?; + + Ok(Response::new().add_event( + PollStarted::WorkerSet { + worker_set: WorkerSetConfirmation::new(message_id, new_operators)?, + metadata: PollMetadata { + poll_id, + source_chain: config.source_chain, + source_gateway_address: config.source_gateway_address, + confirmation_height: config.confirmation_height, + expires_at: env.block.height + config.block_expiry, + participants, + }, + } + .into(), + )) +} + +pub fn verify_messages( + deps: DepsMut, + env: Env, + messages: Vec, +) -> Result { + if messages.is_empty() { + Err(ContractError::EmptyMessages)?; + } + + let source_chain = CONFIG.load(deps.storage)?.source_chain; + + if messages + .iter() + .any(|message| message.cc_id.chain.ne(&source_chain)) + { + Err(ContractError::SourceChainMismatch(source_chain))?; + } + + let config = CONFIG.load(deps.storage)?; + + let response = Response::new().set_data(to_binary(&VerifyMessagesResponse { + verification_statuses: is_verified(deps.as_ref(), &messages)?, + })?); + + let messages = messages + .into_iter() + .map(|message| { + msg_verification_status(deps.as_ref(), &message).map(|status| (status, message)) + }) + .collect::, _>>()?; + + let msgs_to_verify: Vec = messages + .into_iter() + .filter_map(|(status, message)| match status { + VerificationStatus::FailedToVerify | VerificationStatus::NotVerified => Some(message), + VerificationStatus::InProgress | VerificationStatus::Verified => None, + }) + .collect(); + + if msgs_to_verify.is_empty() { + return Ok(response); + } + + let snapshot = take_snapshot(deps.as_ref(), &msgs_to_verify[0].cc_id.chain)?; + let participants = snapshot.get_participants(); + let id = create_messages_poll( + deps.storage, + env.block.height, + config.block_expiry, + snapshot, + msgs_to_verify.len(), + )?; + + for (idx, message) in msgs_to_verify.iter().enumerate() { + POLL_MESSAGES.save( + deps.storage, + &message.hash(), + &state::PollContent::::new(message.clone(), id, idx), + )?; + } + + let messages = msgs_to_verify + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?; + + Ok(response.add_event( + PollStarted::Messages { + messages, + metadata: PollMetadata { + poll_id: id, + source_chain: config.source_chain, + source_gateway_address: config.source_gateway_address, + confirmation_height: config.confirmation_height, + expires_at: env.block.height + config.block_expiry, + participants, + }, + } + .into(), + )) +} + +pub fn vote( + deps: DepsMut, + env: Env, + info: MessageInfo, + poll_id: PollId, + votes: Vec, +) -> Result { + let poll = POLLS + .may_load(deps.storage, poll_id)? + .ok_or(ContractError::PollNotFound)? + .try_map(|poll| { + poll.cast_vote(env.block.height, &info.sender, votes) + .map_err(ContractError::from) + })?; + + POLLS.save(deps.storage, poll_id, &poll)?; + + Ok(Response::new().add_event( + Voted { + poll_id, + voter: info.sender, + } + .into(), + )) +} + +pub fn end_poll(deps: DepsMut, env: Env, poll_id: PollId) -> Result { + let config = CONFIG.load(deps.storage)?; + + let poll = POLLS + .may_load(deps.storage, poll_id)? + .ok_or(ContractError::PollNotFound)? + .try_map(|poll| poll.finish(env.block.height).map_err(ContractError::from))?; + + POLLS.save(deps.storage, poll_id, &poll)?; + + let poll_result = match &poll { + Poll::Messages(poll) | Poll::ConfirmWorkerSet(poll) => poll.state(), + }; + + // TODO: change rewards contract interface to accept a list of addresses to avoid creating multiple wasm messages + let rewards_msgs = poll_result + .consensus_participants + .iter() + .map(|address| WasmMsg::Execute { + contract_addr: config.rewards_contract.to_string(), + msg: to_binary(&rewards::msg::ExecuteMsg::RecordParticipation { + event_id: poll_id + .to_string() + .try_into() + .expect("couldn't convert poll id to nonempty string"), + worker_address: address.to_string(), + }) + .expect("failed to serialize message for rewards contract"), + funds: vec![], + }); + + Ok(Response::new() + .add_messages(rewards_msgs) + .add_event( + PollEnded { + poll_id: poll_result.poll_id, + results: poll_result.results.clone(), + } + .into(), + ) + .set_data(to_binary(&EndPollResponse { poll_result })?)) +} + +fn take_snapshot(deps: Deps, chain: &ChainName) -> Result { + let config = CONFIG.load(deps.storage)?; + + // todo: add chain param to query after service registry updated + // query service registry for active workers + let active_workers_query = QueryMsg::GetActiveWorkers { + service_name: config.service_name.to_string(), + chain_name: chain.clone(), + }; + + let workers: Vec = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: config.service_registry_contract.to_string(), + msg: to_binary(&active_workers_query)?, + }))?; + + let participants = workers + .into_iter() + .map(service_registry::state::Worker::try_into) + .collect::, _>>()?; + + Ok(snapshot::Snapshot::new( + config.voting_threshold, + participants.try_into()?, + )) +} + +fn create_worker_set_poll( + store: &mut dyn Storage, + block_height: u64, + expiry: u64, + snapshot: snapshot::Snapshot, +) -> Result { + let id = POLL_ID.incr(store)?; + + let poll = WeightedPoll::new(id, snapshot, block_height + expiry, 1); + POLLS.save(store, id, &state::Poll::ConfirmWorkerSet(poll))?; + + Ok(id) +} + +fn create_messages_poll( + store: &mut dyn Storage, + block_height: u64, + expiry: u64, + snapshot: snapshot::Snapshot, + poll_size: usize, +) -> Result { + let id = POLL_ID.incr(store)?; + + let poll = WeightedPoll::new(id, snapshot, block_height + expiry, poll_size); + POLLS.save(store, id, &state::Poll::Messages(poll))?; + + Ok(id) +} diff --git a/contracts/xrpl-voting-verifier/src/lib.rs b/contracts/xrpl-voting-verifier/src/lib.rs new file mode 100644 index 000000000..bcd75891e --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/lib.rs @@ -0,0 +1,7 @@ +pub mod contract; +pub mod error; +pub mod events; +pub mod execute; +pub mod msg; +pub mod query; +pub mod state; diff --git a/contracts/xrpl-voting-verifier/src/msg.rs b/contracts/xrpl-voting-verifier/src/msg.rs new file mode 100644 index 000000000..6cfe8404a --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/msg.rs @@ -0,0 +1,79 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; + +use axelar_wasm_std::{ + nonempty, + operators::Operators, + voting::{PollId, PollState, Vote}, + Threshold, +}; +use connection_router::state::{ChainName, CrossChainId, Message}; + +#[cw_serde] +pub struct InstantiateMsg { + // params to query register service + pub service_registry_address: nonempty::String, + pub service_name: nonempty::String, + + pub source_gateway_address: nonempty::String, + pub voting_threshold: Threshold, + pub block_expiry: u64, + pub confirmation_height: u64, + pub source_chain: ChainName, + pub rewards_address: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + // Computes the results of a poll + // For all verified messages, calls MessagesVerified on the verifier + EndPoll { + poll_id: PollId, + }, + + // Casts votes for specified poll + Vote { + poll_id: PollId, + votes: Vec, + }, + + // returns a vector of true/false values, indicating current verification status for each message + // starts a poll for any not yet verified messages + VerifyMessages { + messages: Vec, + }, + + // Starts a poll to confirm a worker set update on the external evm gateway + VerifyWorkerSet { + message_id: nonempty::String, + new_operators: Operators, + }, +} + +#[cw_serde] +pub struct Poll { + poll_id: PollId, + messages: Vec, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Poll)] + GetPoll { poll_id: PollId }, + + #[returns(Vec<(connection_router::state::CrossChainId, bool)>)] + IsVerified { messages: Vec }, + + #[returns(bool)] + IsWorkerSetVerified { new_operators: Operators }, +} + +#[cw_serde] +pub struct VerifyMessagesResponse { + pub verification_statuses: Vec<(CrossChainId, bool)>, +} + +#[cw_serde] +pub struct EndPollResponse { + pub poll_result: PollState, +} diff --git a/contracts/xrpl-voting-verifier/src/query.rs b/contracts/xrpl-voting-verifier/src/query.rs new file mode 100644 index 000000000..1d64f8280 --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/query.rs @@ -0,0 +1,266 @@ +use axelar_wasm_std::operators::Operators; +use axelar_wasm_std::voting::{PollStatus, Vote}; +use connection_router::state::{CrossChainId, Message}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Deps; + +use crate::error::ContractError; +use crate::state::{self, Poll, PollContent, POLLS, POLL_MESSAGES, POLL_WORKER_SETS}; + +#[cw_serde] +pub enum VerificationStatus { + Verified, + FailedToVerify, + InProgress, // still in an open poll + NotVerified, // not in a poll +} + +pub fn is_verified( + deps: Deps, + messages: &[Message], +) -> Result, ContractError> { + messages + .iter() + .map(|message| { + msg_verification_status(deps, message).map(|status| { + ( + message.cc_id.to_owned(), + matches!(status, VerificationStatus::Verified), + ) + }) + }) + .collect::, _>>() +} + +pub fn is_worker_set_verified(deps: Deps, operators: &Operators) -> Result { + Ok(matches!( + worker_set_verification_status(deps, operators)?, + VerificationStatus::Verified + )) +} + +pub fn msg_verification_status( + deps: Deps, + message: &Message, +) -> Result { + let loaded_poll_content = POLL_MESSAGES.may_load(deps.storage, &message.hash())?; + Ok(verification_status(deps, loaded_poll_content, message)) +} + +pub fn worker_set_verification_status( + deps: Deps, + operators: &Operators, +) -> Result { + let poll_content = POLL_WORKER_SETS.may_load(deps.storage, &operators.hash())?; + Ok(verification_status(deps, poll_content, operators)) +} + +fn verification_status( + deps: Deps, + stored_poll_content: Option>, + content: &T, +) -> VerificationStatus { + match stored_poll_content { + Some(stored) => { + assert_eq!( + stored.content, *content, + "invalid invariant: content mismatch with the stored one" + ); + + let poll = POLLS + .load(deps.storage, stored.poll_id) + .expect("invalid invariant: message poll not found"); + + let verified = match &poll { + Poll::Messages(poll) | Poll::ConfirmWorkerSet(poll) => { + poll.consensus(stored.index_in_poll) + .expect("invalid invariant: message not found in poll") + == Some(Vote::SucceededOnChain) // TODO: consider Vote::FailedOnChain? + } + }; + + if verified { + VerificationStatus::Verified + } else if is_finished(&poll) { + VerificationStatus::FailedToVerify + } else { + VerificationStatus::InProgress + } + } + None => VerificationStatus::NotVerified, + } +} + +fn is_finished(poll: &state::Poll) -> bool { + match poll { + state::Poll::Messages(poll) | state::Poll::ConfirmWorkerSet(poll) => { + poll.status == PollStatus::Finished + } + } +} + +#[cfg(test)] +mod tests { + use axelar_wasm_std::{ + nonempty, + voting::{PollId, Tallies, Vote, WeightedPoll}, + Participant, Snapshot, Threshold, + }; + use cosmwasm_std::{testing::mock_dependencies, Addr, Uint256, Uint64}; + + use crate::state::PollContent; + + use super::*; + + #[test] + fn verification_status_in_progress() { + let mut deps = mock_dependencies(); + let idx = 0; + + let poll = poll(); + POLLS + .save( + deps.as_mut().storage, + poll.poll_id, + &state::Poll::Messages(poll.clone()), + ) + .unwrap(); + + let msg = message(1); + POLL_MESSAGES + .save( + deps.as_mut().storage, + &msg.hash(), + &PollContent::::new(msg.clone(), poll.poll_id, idx), + ) + .unwrap(); + + assert_eq!( + msg_verification_status(deps.as_ref(), &msg).unwrap(), + VerificationStatus::InProgress + ); + assert_eq!( + vec![(msg.cc_id.clone(), false)], + is_verified(deps.as_ref(), &[msg]).unwrap() + ); + } + + #[test] + fn verification_status_verified() { + let mut deps = mock_dependencies(); + let idx = 0; + + let mut poll = poll(); + poll.tallies[idx] = Tallies::default(); + poll.tallies[idx].tally(&Vote::SucceededOnChain, &Uint256::from(5u64)); + + POLLS + .save( + deps.as_mut().storage, + poll.poll_id, + &state::Poll::Messages(poll.clone()), + ) + .unwrap(); + + let msg = message(1); + POLL_MESSAGES + .save( + deps.as_mut().storage, + &msg.hash(), + &PollContent::::new(msg.clone(), poll.poll_id, idx), + ) + .unwrap(); + + assert_eq!( + msg_verification_status(deps.as_ref(), &msg).unwrap(), + VerificationStatus::Verified + ); + assert_eq!( + vec![(msg.cc_id.clone(), true)], + is_verified(deps.as_ref(), &[msg]).unwrap() + ); + } + + #[test] + fn verification_status_failed_to_verify() { + let mut deps = mock_dependencies(); + let idx = 0; + + let mut poll = poll(); + poll.status = PollStatus::Finished; + + POLLS + .save( + deps.as_mut().storage, + poll.poll_id, + &state::Poll::Messages(poll.clone()), + ) + .unwrap(); + + let msg = message(1); + POLL_MESSAGES + .save( + deps.as_mut().storage, + &msg.hash(), + &PollContent::::new(msg.clone(), poll.poll_id, idx), + ) + .unwrap(); + + assert_eq!( + msg_verification_status(deps.as_ref(), &msg).unwrap(), + VerificationStatus::FailedToVerify + ); + assert_eq!( + vec![(msg.cc_id.clone(), false)], + is_verified(deps.as_ref(), &[msg]).unwrap() + ); + } + + #[test] + fn verification_status_not_verified() { + let deps = mock_dependencies(); + let msg = message(1); + + assert_eq!( + msg_verification_status(deps.as_ref(), &msg).unwrap(), + VerificationStatus::NotVerified + ); + assert_eq!( + vec![(msg.cc_id.clone(), false)], + is_verified(deps.as_ref(), &[msg]).unwrap() + ); + } + + fn message(id: u64) -> Message { + Message { + cc_id: CrossChainId { + chain: "source_chain".parse().unwrap(), + id: format!("id:{id}").parse().unwrap(), + }, + source_address: format!("source_address{id}").parse().unwrap(), + destination_chain: format!("destination_chain{id}").parse().unwrap(), + destination_address: format!("destination_address{id}").parse().unwrap(), + payload_hash: [0; 32], + } + } + + pub fn poll() -> WeightedPoll { + let participants: nonempty::Vec = vec!["addr1", "addr2", "addr3"] + .into_iter() + .map(|participant| Participant { + address: Addr::unchecked(participant), + weight: nonempty::Uint256::try_from(Uint256::one()).unwrap(), + }) + .collect::>() + .try_into() + .unwrap(); + + let numerator: nonempty::Uint64 = Uint64::from(2u8).try_into().unwrap(); + let denominator: nonempty::Uint64 = Uint64::from(3u8).try_into().unwrap(); + let threshold: Threshold = (numerator, denominator).try_into().unwrap(); + + let snapshot = Snapshot::new(threshold, participants); + + WeightedPoll::new(PollId::from(Uint64::one()), snapshot, 0, 5) + } +} diff --git a/contracts/xrpl-voting-verifier/src/state.rs b/contracts/xrpl-voting-verifier/src/state.rs new file mode 100644 index 000000000..2639411ab --- /dev/null +++ b/contracts/xrpl-voting-verifier/src/state.rs @@ -0,0 +1,83 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; + +use axelar_wasm_std::{ + counter, + hash::Hash, + nonempty, + operators::Operators, + voting::{PollId, WeightedPoll}, + Threshold, +}; +use connection_router::state::{ChainName, Message}; + +use crate::error::ContractError; + +#[cw_serde] +pub struct Config { + pub service_registry_contract: Addr, + pub service_name: nonempty::String, + pub source_gateway_address: nonempty::String, + pub voting_threshold: Threshold, + pub block_expiry: u64, // number of blocks after which a poll expires + pub confirmation_height: u64, + pub source_chain: ChainName, + pub rewards_contract: Addr, +} + +#[cw_serde] +pub enum Poll { + Messages(WeightedPoll), + ConfirmWorkerSet(WeightedPoll), +} + +impl Poll { + pub fn try_map(self, func: F) -> Result + where + F: FnOnce(WeightedPoll) -> Result, + E: From, + { + match self { + Poll::Messages(poll) => Ok(Poll::Messages(func(poll)?)), + Poll::ConfirmWorkerSet(poll) => Ok(Poll::ConfirmWorkerSet(func(poll)?)), + } + } +} + +#[cw_serde] +pub struct PollContent { + pub content: T, // content is stored for migration purposes in case the hash changes + pub poll_id: PollId, + pub index_in_poll: u32, +} + +impl PollContent { + pub fn new(message: Message, poll_id: PollId, index_in_poll: usize) -> Self { + Self { + content: message, + poll_id, + index_in_poll: index_in_poll.try_into().unwrap(), + } + } +} + +impl PollContent { + pub fn new(operators: Operators, poll_id: PollId) -> Self { + Self { + content: operators, + poll_id, + index_in_poll: 0, + } + } +} + +pub const POLL_ID: counter::Counter = counter::Counter::new("poll_id"); + +pub const POLLS: Map = Map::new("polls"); + +pub const POLL_MESSAGES: Map<&Hash, PollContent> = Map::new("poll_messages"); + +pub const CONFIG: Item = Item::new("config"); + +pub const POLL_WORKER_SETS: Map<&Hash, PollContent> = Map::new("poll_worker_sets"); diff --git a/contracts/xrpl-voting-verifier/tests/mock.rs b/contracts/xrpl-voting-verifier/tests/mock.rs new file mode 100644 index 000000000..01cca445c --- /dev/null +++ b/contracts/xrpl-voting-verifier/tests/mock.rs @@ -0,0 +1,124 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, +}; +use cw_multi_test::{App, ContractWrapper, Executor}; + +use service_registry::{ + msg::{ExecuteMsg, InstantiateMsg}, + state::{AuthorizationState, BondingState, Worker}, + ContractError, +}; + +pub fn mock_service_registry_execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsg, +) -> Result { + Ok(Response::new()) +} + +#[cw_serde] +pub enum MockServiceRegistryQueryMsg { + GetActiveWorkers { + service_name: String, + chain_name: String, + }, +} +pub fn mock_service_registry_query( + _deps: Deps, + _env: Env, + msg: MockServiceRegistryQueryMsg, +) -> StdResult { + match msg { + MockServiceRegistryQueryMsg::GetActiveWorkers { + service_name, + chain_name: _, + } => { + let res = vec![ + Worker { + address: Addr::unchecked("addr1"), + bonding_state: BondingState::Bonded { + amount: Uint128::from(100u128), + }, + authorization_state: AuthorizationState::Authorized, + service_name: service_name.clone(), + }, + Worker { + address: Addr::unchecked("addr2"), + bonding_state: BondingState::Bonded { + amount: Uint128::from(100u128), + }, + authorization_state: AuthorizationState::Authorized, + service_name: service_name.clone(), + }, + ]; + Ok(to_binary(&res)?) + } + } +} + +pub fn make_mock_service_registry(app: &mut App) -> Addr { + let code = ContractWrapper::new( + mock_service_registry_execute, + |_, _, _, _: InstantiateMsg| Ok::(Response::new()), + mock_service_registry_query, + ); + let code_id = app.store_code(Box::new(code)); + + let contract_address = app + .instantiate_contract( + code_id, + Addr::unchecked("sender"), + &InstantiateMsg { + governance_account: Addr::unchecked("governance").into(), + }, + &[], + "Contract", + None, + ) + .unwrap(); + contract_address +} + +#[cw_serde] +pub struct MockRewardsInstantiateMsg; + +pub fn mock_rewards_execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: rewards::msg::ExecuteMsg, +) -> Result { + Ok(Response::new()) +} + +pub fn mock_rewards_query( + _deps: Deps, + _env: Env, + _msg: rewards::msg::QueryMsg, +) -> StdResult { + Ok(to_binary("")?) +} + +pub fn make_mock_rewards(app: &mut App) -> Addr { + let code = ContractWrapper::new( + mock_rewards_execute, + |_, _, _, _: MockRewardsInstantiateMsg| Ok::(Response::new()), + mock_rewards_query, + ); + let code_id = app.store_code(Box::new(code)); + + let contract_address = app + .instantiate_contract( + code_id, + Addr::unchecked("sender"), + &MockRewardsInstantiateMsg, + &[], + "Contract", + None, + ) + .unwrap(); + contract_address +} diff --git a/contracts/xrpl-voting-verifier/tests/tests.rs b/contracts/xrpl-voting-verifier/tests/tests.rs new file mode 100644 index 000000000..9988e841a --- /dev/null +++ b/contracts/xrpl-voting-verifier/tests/tests.rs @@ -0,0 +1,641 @@ +use axelar_wasm_std::voting::Vote; +use cosmwasm_std::{from_binary, Addr, Uint64}; +use cw_multi_test::{App, ContractWrapper, Executor}; + +use axelar_wasm_std::operators::Operators; +use axelar_wasm_std::{nonempty, Threshold}; +use connection_router::state::{ChainName, CrossChainId, Message, ID_SEPARATOR}; +use mock::make_mock_rewards; +use service_registry::state::Worker; +use voting_verifier::events::TxEventConfirmation; +use voting_verifier::{contract, error::ContractError, msg}; + +use crate::mock::make_mock_service_registry; + +pub mod mock; + +const SENDER: &str = "sender"; +const POLL_BLOCK_EXPIRY: u64 = 100; +fn source_chain() -> ChainName { + "source_chain".parse().unwrap() +} + +fn initialize_contract(app: &mut App, service_registry_address: nonempty::String) -> Addr { + let rewards_address = make_mock_rewards(app).into(); + + let msg = msg::InstantiateMsg { + service_registry_address, + service_name: "service_name".parse().unwrap(), + voting_threshold: Threshold::try_from((1u64, 2u64)).unwrap(), + block_expiry: POLL_BLOCK_EXPIRY, + confirmation_height: 100, + source_gateway_address: "gateway_address".parse().unwrap(), + source_chain: source_chain(), + rewards_address, + }; + + let code = ContractWrapper::new(contract::execute, contract::instantiate, contract::query); + let code_id = app.store_code(Box::new(code)); + + let address = app + .instantiate_contract( + code_id, + Addr::unchecked(SENDER), + &msg, + &[], + "voting-verifier", + None, + ) + .unwrap(); + + address +} + +fn message_id(id: &str, index: u64) -> nonempty::String { + format!("{}{}{}", id, ID_SEPARATOR, index) + .try_into() + .unwrap() +} + +fn messages(len: u64) -> Vec { + (0..len) + .map(|i| Message { + cc_id: CrossChainId { + chain: source_chain(), + id: format!("id:{i}").parse().unwrap(), + }, + source_address: format!("source_address{i}").parse().unwrap(), + destination_chain: format!("destination_chain{i}").parse().unwrap(), + destination_address: format!("destination_address{i}").parse().unwrap(), + payload_hash: [0; 32], + }) + .collect() +} + +#[test] +fn should_failed_if_messages_are_not_from_same_source() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let msg = msg::ExecuteMsg::VerifyMessages { + messages: vec![ + Message { + cc_id: CrossChainId { + chain: source_chain(), + id: "id:1".parse().unwrap(), + }, + source_address: "source_address1".parse().unwrap(), + destination_chain: "destination_chain1".parse().unwrap(), + destination_address: "destination_address1".parse().unwrap(), + payload_hash: [0; 32], + }, + Message { + cc_id: CrossChainId { + chain: "other_chain".parse().unwrap(), + id: "id:2".parse().unwrap(), + }, + source_address: "source_address2".parse().unwrap(), + destination_chain: "destination_chain2".parse().unwrap(), + destination_address: "destination_address2".parse().unwrap(), + payload_hash: [0; 32], + }, + ], + }; + + let err = app + .execute_contract(Addr::unchecked(SENDER), contract_address, &msg, &[]) + .unwrap_err(); + assert_eq!( + err.downcast::() + .unwrap() + .to_string(), + axelar_wasm_std::ContractError::from(ContractError::SourceChainMismatch(source_chain(),)) + .to_string() + ); +} + +#[test] +fn should_verify_messages_if_not_verified() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let msg = msg::ExecuteMsg::VerifyMessages { + messages: messages(2), + }; + + let res = app + .execute_contract(Addr::unchecked(SENDER), contract_address, &msg, &[]) + .unwrap(); + let reply: msg::VerifyMessagesResponse = from_binary(&res.data.unwrap()).unwrap(); + assert_eq!(reply.verification_statuses.len(), 2); + assert_eq!( + reply.verification_statuses, + vec![ + ( + CrossChainId { + id: "id:0".parse().unwrap(), + chain: source_chain() + }, + false + ), + ( + CrossChainId { + id: "id:1".parse().unwrap(), + chain: source_chain() + }, + false + ), + ] + ); +} + +#[test] +fn should_not_verify_messages_if_in_progress() { + let mut app = App::default(); + let messages_in_progress = 3; + let new_messages = 2; + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + app.execute_contract( + Addr::unchecked(SENDER), + contract_address.clone(), + &msg::ExecuteMsg::VerifyMessages { + messages: messages(messages_in_progress), + }, + &[], + ) + .unwrap(); + + let res = app + .execute_contract( + Addr::unchecked(SENDER), + contract_address, + &msg::ExecuteMsg::VerifyMessages { + messages: messages(messages_in_progress + new_messages), // creates the same messages + some new ones + }, + &[], + ) + .unwrap(); + + let messages: Vec = serde_json::from_str( + &res.events + .into_iter() + .find(|event| event.ty == "wasm-messages_poll_started") + .unwrap() + .attributes + .into_iter() + .find_map(|attribute| { + if attribute.key == "messages" { + Some(attribute.value) + } else { + None + } + }) + .unwrap(), + ) + .unwrap(); + + assert_eq!(messages.len() as u64, new_messages); +} + +#[test] +fn should_retry_if_message_not_verified() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let msg = msg::ExecuteMsg::VerifyMessages { + messages: messages(1), + }; + app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]) + .unwrap(); + + app.update_block(|block| block.height += POLL_BLOCK_EXPIRY); + + app.execute_contract( + Addr::unchecked(SENDER), + contract_address.clone(), + &msg::ExecuteMsg::EndPoll { + poll_id: Uint64::one().into(), + }, + &[], + ) + .unwrap(); + + // retries same message + let res = app + .execute_contract(Addr::unchecked(SENDER), contract_address, &msg, &[]) + .unwrap(); + + let messages: Vec = serde_json::from_str( + &res.events + .into_iter() + .find(|event| event.ty == "wasm-messages_poll_started") + .unwrap() + .attributes + .into_iter() + .find_map(|attribute| { + if attribute.key == "messages" { + Some(attribute.value) + } else { + None + } + }) + .unwrap(), + ) + .unwrap(); + + assert_eq!(messages.len() as u64, 1); +} + +#[test] +fn should_query_message_statuses() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let messages = messages(10); + + let msg = msg::ExecuteMsg::VerifyMessages { + messages: messages.clone(), + }; + + let res = app + .execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]) + .unwrap(); + + let reply: msg::VerifyMessagesResponse = from_binary(&res.data.unwrap()).unwrap(); + + assert_eq!(reply.verification_statuses.len(), messages.len()); + assert_eq!( + reply.verification_statuses, + messages + .iter() + .map(|message| (message.cc_id.clone(), false)) + .collect::>() + ); + + let query = msg::QueryMsg::IsVerified { + messages: messages.clone(), + }; + + let statuses: Vec<(CrossChainId, bool)> = app + .wrap() + .query_wasm_smart(contract_address.clone(), &query) + .unwrap(); + + assert_eq!( + statuses, + messages + .iter() + .map(|message| (message.cc_id.clone(), false)) + .collect::>() + ); + + let msg: msg::ExecuteMsg = msg::ExecuteMsg::Vote { + poll_id: Uint64::one().into(), + votes: (0..messages.len()) + .map(|i| { + if i % 2 == 0 { + Vote::SucceededOnChain + } else { + Vote::NotFound + } + }) + .collect::>(), + }; + + app.execute_contract( + Addr::unchecked("addr1"), + contract_address.clone(), + &msg, + &[], + ) + .unwrap(); + app.execute_contract( + Addr::unchecked("addr2"), + contract_address.clone(), + &msg, + &[], + ) + .unwrap(); + + app.update_block(|block| block.height += POLL_BLOCK_EXPIRY); + + let msg: msg::ExecuteMsg = msg::ExecuteMsg::EndPoll { + poll_id: Uint64::one().into(), + }; + app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]) + .unwrap(); + + let statuses: Vec<(CrossChainId, bool)> = app + .wrap() + .query_wasm_smart(contract_address.clone(), &query) + .unwrap(); + + assert_eq!( + statuses, + messages + .iter() + .enumerate() + .map(|(i, message)| (message.cc_id.clone(), i % 2 == 0)) + .collect::>() + ); +} + +#[test] +fn should_start_worker_set_confirmation() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); + let msg = msg::ExecuteMsg::VerifyWorkerSet { + message_id: message_id("id", 0), + new_operators: operators.clone(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let query = msg::QueryMsg::IsWorkerSetVerified { + new_operators: operators, + }; + let res: Result = app.wrap().query_wasm_smart(contract_address, &query); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), false); +} + +#[test] +fn should_confirm_worker_set() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let workers: Vec = app + .wrap() + .query_wasm_smart( + service_registry_address, + &service_registry::msg::QueryMsg::GetActiveWorkers { + service_name: "service_name".to_string(), + chain_name: source_chain(), + }, + ) + .unwrap(); + + let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); + let msg = msg::ExecuteMsg::VerifyWorkerSet { + message_id: message_id("id", 0), + new_operators: operators.clone(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let msg = msg::ExecuteMsg::Vote { + poll_id: 1u64.into(), + votes: vec![Vote::SucceededOnChain], + }; + for worker in workers { + let res = app.execute_contract(worker.address.clone(), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + } + + app.update_block(|block| block.height += POLL_BLOCK_EXPIRY); + + let msg = msg::ExecuteMsg::EndPoll { + poll_id: 1u64.into(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let query = msg::QueryMsg::IsWorkerSetVerified { + new_operators: operators, + }; + let res: Result = app.wrap().query_wasm_smart(contract_address, &query); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), true); +} + +#[test] +fn should_not_confirm_worker_set() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let workers: Vec = app + .wrap() + .query_wasm_smart( + service_registry_address, + &service_registry::msg::QueryMsg::GetActiveWorkers { + service_name: "service_name".to_string(), + chain_name: source_chain(), + }, + ) + .unwrap(); + + let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); + let msg = msg::ExecuteMsg::VerifyWorkerSet { + message_id: message_id("id", 0), + new_operators: operators.clone(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let msg = msg::ExecuteMsg::Vote { + poll_id: 1u64.into(), + votes: vec![Vote::NotFound], + }; + for worker in workers { + let res = app.execute_contract(worker.address.clone(), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + } + + app.update_block(|block| block.height += POLL_BLOCK_EXPIRY); + + let msg = msg::ExecuteMsg::EndPoll { + poll_id: 1u64.into(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let query = msg::QueryMsg::IsWorkerSetVerified { + new_operators: operators, + }; + let res: Result = app.wrap().query_wasm_smart(contract_address, &query); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), false); +} + +#[test] +fn should_confirm_worker_set_after_failed() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let workers: Vec = app + .wrap() + .query_wasm_smart( + service_registry_address, + &service_registry::msg::QueryMsg::GetActiveWorkers { + service_name: "service_name".to_string(), + chain_name: source_chain(), + }, + ) + .unwrap(); + + let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); + let msg = msg::ExecuteMsg::VerifyWorkerSet { + message_id: message_id("id", 0), + new_operators: operators.clone(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let msg = msg::ExecuteMsg::Vote { + poll_id: 1u64.into(), + votes: vec![Vote::NotFound], + }; + for worker in &workers { + let res = app.execute_contract(worker.address.clone(), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + } + + app.update_block(|block| block.height += POLL_BLOCK_EXPIRY); + + let msg = msg::ExecuteMsg::EndPoll { + poll_id: 1u64.into(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let query = msg::QueryMsg::IsWorkerSetVerified { + new_operators: operators.clone(), + }; + let res: Result = app + .wrap() + .query_wasm_smart(contract_address.clone(), &query); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), false); + + // try again, and this time vote true + let msg = msg::ExecuteMsg::VerifyWorkerSet { + message_id: message_id("id", 0), + new_operators: operators.clone(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let msg = msg::ExecuteMsg::Vote { + poll_id: 2u64.into(), + votes: vec![Vote::SucceededOnChain], + }; + for worker in workers { + let res = app.execute_contract(worker.address.clone(), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + } + + app.update_block(|block| block.height += POLL_BLOCK_EXPIRY); + + let msg = msg::ExecuteMsg::EndPoll { + poll_id: 2u64.into(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let query = msg::QueryMsg::IsWorkerSetVerified { + new_operators: operators, + }; + let res: Result = app.wrap().query_wasm_smart(contract_address, &query); + assert!(res.is_ok()); + assert_eq!(res.unwrap(), true); +} + +#[test] +fn should_not_confirm_twice() { + let mut app = App::default(); + + let service_registry_address = make_mock_service_registry(&mut app); + + let contract_address = + initialize_contract(&mut app, service_registry_address.as_ref().parse().unwrap()); + + let workers: Vec = app + .wrap() + .query_wasm_smart( + service_registry_address, + &service_registry::msg::QueryMsg::GetActiveWorkers { + service_name: "service_name".to_string(), + chain_name: source_chain(), + }, + ) + .unwrap(); + + let operators = Operators::new(vec![(vec![0, 1, 0, 1].into(), 1u64.into())], 1u64.into()); + let msg = msg::ExecuteMsg::VerifyWorkerSet { + message_id: message_id("id", 0), + new_operators: operators.clone(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + let msg = msg::ExecuteMsg::Vote { + poll_id: 1u64.into(), + votes: vec![Vote::SucceededOnChain], + }; + for worker in workers { + let res = app.execute_contract(worker.address.clone(), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + } + + app.update_block(|block| block.height += POLL_BLOCK_EXPIRY); + + let msg = msg::ExecuteMsg::EndPoll { + poll_id: 1u64.into(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_ok()); + + // try again, should fail + let msg = msg::ExecuteMsg::VerifyWorkerSet { + message_id: message_id("id", 0), + new_operators: operators.clone(), + }; + let res = app.execute_contract(Addr::unchecked(SENDER), contract_address.clone(), &msg, &[]); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err() + .downcast::() + .unwrap() + .to_string(), + axelar_wasm_std::ContractError::from(ContractError::WorkerSetAlreadyConfirmed).to_string() + ); +} From fe07d1cb19935463748602c4e097e18a5f42bcb1 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Mon, 18 Dec 2023 15:57:49 +0200 Subject: [PATCH 02/80] xrpl-voting-verifier: verify message status --- .../xrpl-voting-verifier/src/contract.rs | 3 + contracts/xrpl-voting-verifier/src/events.rs | 14 +++ contracts/xrpl-voting-verifier/src/execute.rs | 109 +++++++++++++++++- contracts/xrpl-voting-verifier/src/msg.rs | 16 +++ contracts/xrpl-voting-verifier/src/query.rs | 33 +++++- contracts/xrpl-voting-verifier/src/state.rs | 24 +++- 6 files changed, 191 insertions(+), 8 deletions(-) diff --git a/contracts/xrpl-voting-verifier/src/contract.rs b/contracts/xrpl-voting-verifier/src/contract.rs index 77f1c5007..b3b36b919 100644 --- a/contracts/xrpl-voting-verifier/src/contract.rs +++ b/contracts/xrpl-voting-verifier/src/contract.rs @@ -40,6 +40,7 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::VerifyMessages { messages } => execute::verify_messages(deps, env, messages), + ExecuteMsg::VerifyMessageStatuses { message_statuses } => execute::verify_message_statuses(deps, env, message_statuses), ExecuteMsg::Vote { poll_id, votes } => execute::vote(deps, env, info, poll_id, votes), ExecuteMsg::EndPoll { poll_id } => execute::end_poll(deps, env, poll_id), ExecuteMsg::VerifyWorkerSet { @@ -54,6 +55,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::IsVerified { messages } => to_binary(&query::is_verified(deps, &messages)?), + QueryMsg::IsStatusVerified { message_statuses } => to_binary(&query::is_status_verified(deps, &message_statuses)?), + QueryMsg::GetPoll { poll_id: _ } => { todo!() } diff --git a/contracts/xrpl-voting-verifier/src/events.rs b/contracts/xrpl-voting-verifier/src/events.rs index 2723d066f..08440ffce 100644 --- a/contracts/xrpl-voting-verifier/src/events.rs +++ b/contracts/xrpl-voting-verifier/src/events.rs @@ -9,6 +9,7 @@ use axelar_wasm_std::voting::{PollId, Vote}; use connection_router::state::{Address, ChainName, Message, ID_SEPARATOR}; use crate::error::ContractError; +use crate::execute::MessageStatus; use crate::state::Config; impl From for Vec { @@ -47,6 +48,10 @@ pub enum PollStarted { messages: Vec, metadata: PollMetadata, }, + MessageStatuses { + message_statuses: Vec<(TxEventConfirmation, MessageStatus)>, + metadata: PollMetadata, + }, WorkerSet { worker_set: WorkerSetConfirmation, metadata: PollMetadata, @@ -91,6 +96,15 @@ impl From for Event { serde_json::to_string(&data).expect("failed to serialize messages"), ) .add_attributes(Vec::<_>::from(metadata)), + PollStarted::MessageStatuses { + message_statuses: data, + metadata, + } => Event::new("message_statuses_poll_started") + .add_attribute( + "message_statuses", + serde_json::to_string(&data).expect("failed to serialize message statuses"), + ) + .add_attributes(Vec::<_>::from(metadata)), PollStarted::WorkerSet { worker_set: data, metadata, diff --git a/contracts/xrpl-voting-verifier/src/execute.rs b/contracts/xrpl-voting-verifier/src/execute.rs index f9a16dac6..c7e5f2551 100644 --- a/contracts/xrpl-voting-verifier/src/execute.rs +++ b/contracts/xrpl-voting-verifier/src/execute.rs @@ -1,4 +1,5 @@ use axelar_wasm_std::operators::Operators; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_binary, Deps, DepsMut, Env, MessageInfo, QueryRequest, Response, Storage, WasmMsg, WasmQuery, }; @@ -13,11 +14,11 @@ use crate::error::ContractError; use crate::events::{ PollEnded, PollMetadata, PollStarted, TxEventConfirmation, Voted, WorkerSetConfirmation, }; -use crate::msg::{EndPollResponse, VerifyMessagesResponse}; +use crate::msg::{EndPollResponse, VerifyMessagesResponse, VerifyMessageStatusesResponse}; use crate::query::{ - is_verified, is_worker_set_verified, msg_verification_status, VerificationStatus, + is_verified, is_worker_set_verified, msg_verification_status, VerificationStatus, msg_status_verification_status, is_status_verified, }; -use crate::state::{self, Poll, PollContent, POLL_MESSAGES, POLL_WORKER_SETS}; +use crate::state::{self, Poll, PollContent, POLL_MESSAGES, POLL_WORKER_SETS, message_and_status_key, POLL_MESSAGE_STATUSES}; use crate::state::{CONFIG, POLLS, POLL_ID}; pub fn verify_worker_set( @@ -145,6 +146,106 @@ pub fn verify_messages( )) } +#[cw_serde] +pub enum MessageStatus { + Succeeded, + FailedOffChain, + FailedOnChain +} + +impl Into for MessageStatus { + fn into(self) -> u8 { + match self { + MessageStatus::Succeeded => 0, + MessageStatus::FailedOffChain => 1, + MessageStatus::FailedOnChain => 2 + } + } +} + +pub fn verify_message_statuses( + deps: DepsMut, + env: Env, + message_statuses: Vec<(Message, MessageStatus)>, +) -> Result { + if message_statuses.is_empty() { + return Err(ContractError::EmptyMessages)?; + } + + let source_chain = CONFIG.load(deps.storage)?.source_chain; + + if message_statuses + .iter() + .any(|(message, _)| message.cc_id.chain.ne(&source_chain)) + { + return Err(ContractError::SourceChainMismatch(source_chain))?; + } + + let config = CONFIG.load(deps.storage)?; + + let response = Response::new().set_data(to_binary(&VerifyMessageStatusesResponse { + verification_statuses: is_status_verified(deps.as_ref(), &message_statuses)?, + })?); + + let message_statuses = message_statuses + .into_iter() + .map(|(message, status)| { + msg_status_verification_status(deps.as_ref(), &(message.clone(), status.clone())) + .map(|verification_status| (verification_status, message, status)) + }) + .collect::, _>>()?; + + let msg_statuses_to_verify: Vec<(Message, MessageStatus)> = message_statuses + .into_iter() + .filter_map(|(status, message, message_status)| match status { + VerificationStatus::FailedToVerify | VerificationStatus::NotVerified => Some((message, message_status)), + VerificationStatus::InProgress | VerificationStatus::Verified => None, + }) + .collect(); + + if msg_statuses_to_verify.is_empty() { + return Ok(response); + } + + let snapshot = take_snapshot(deps.as_ref(), &source_chain)?; + let participants = snapshot.get_participants(); + let id = create_messages_poll( + deps.storage, + env.block.height, + config.block_expiry, + snapshot, + msg_statuses_to_verify.len(), + )?; + + for (idx, (message, status)) in msg_statuses_to_verify.iter().enumerate() { + POLL_MESSAGE_STATUSES.save( + deps.storage, + &message_and_status_key(message, status), + &state::PollContent::<(Message, MessageStatus)>::new(message.clone(), status.clone(), id, idx), + )?; + } + + let message_statuses = msg_statuses_to_verify + .into_iter() + .map(|(msg, status)| { TryInto::try_into(msg).map(|t| (t, status)) }) + .collect::, _>>()?; + + Ok(response.add_event( + PollStarted::MessageStatuses { + message_statuses, + metadata: PollMetadata { + poll_id: id, + source_chain: config.source_chain, + source_gateway_address: config.source_gateway_address, + confirmation_height: config.confirmation_height, + expires_at: env.block.height + config.block_expiry, + participants, + }, + } + .into(), + )) +} + pub fn vote( deps: DepsMut, env: Env, @@ -182,7 +283,7 @@ pub fn end_poll(deps: DepsMut, env: Env, poll_id: PollId) -> Result poll.state(), + Poll::Messages(poll) | Poll::MessageStatuses(poll) | Poll::ConfirmWorkerSet(poll) => poll.state(), }; // TODO: change rewards contract interface to accept a list of addresses to avoid creating multiple wasm messages diff --git a/contracts/xrpl-voting-verifier/src/msg.rs b/contracts/xrpl-voting-verifier/src/msg.rs index 6cfe8404a..1b4d3b87d 100644 --- a/contracts/xrpl-voting-verifier/src/msg.rs +++ b/contracts/xrpl-voting-verifier/src/msg.rs @@ -8,6 +8,8 @@ use axelar_wasm_std::{ }; use connection_router::state::{ChainName, CrossChainId, Message}; +use crate::execute::MessageStatus; + #[cw_serde] pub struct InstantiateMsg { // params to query register service @@ -42,6 +44,12 @@ pub enum ExecuteMsg { messages: Vec, }, + // returns a vector of true/false values, indicating current verification status for each message and status + // starts a poll for any not yet verified message statuses + VerifyMessageStatuses { + message_statuses: Vec<(Message, MessageStatus)>, + }, + // Starts a poll to confirm a worker set update on the external evm gateway VerifyWorkerSet { message_id: nonempty::String, @@ -64,6 +72,9 @@ pub enum QueryMsg { #[returns(Vec<(connection_router::state::CrossChainId, bool)>)] IsVerified { messages: Vec }, + #[returns(Vec<(connection_router::state::CrossChainId, MessageStatus, bool)>)] + IsStatusVerified { message_statuses: Vec<(Message, MessageStatus)> }, + #[returns(bool)] IsWorkerSetVerified { new_operators: Operators }, } @@ -73,6 +84,11 @@ pub struct VerifyMessagesResponse { pub verification_statuses: Vec<(CrossChainId, bool)>, } +#[cw_serde] +pub struct VerifyMessageStatusesResponse { + pub verification_statuses: Vec<(CrossChainId, MessageStatus, bool)>, +} + #[cw_serde] pub struct EndPollResponse { pub poll_result: PollState, diff --git a/contracts/xrpl-voting-verifier/src/query.rs b/contracts/xrpl-voting-verifier/src/query.rs index 1d64f8280..024a35963 100644 --- a/contracts/xrpl-voting-verifier/src/query.rs +++ b/contracts/xrpl-voting-verifier/src/query.rs @@ -5,7 +5,8 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Deps; use crate::error::ContractError; -use crate::state::{self, Poll, PollContent, POLLS, POLL_MESSAGES, POLL_WORKER_SETS}; +use crate::state::{self, Poll, PollContent, POLLS, POLL_MESSAGES, POLL_WORKER_SETS, POLL_MESSAGE_STATUSES, message_and_status_key}; +use crate::execute::MessageStatus; #[cw_serde] pub enum VerificationStatus { @@ -32,6 +33,24 @@ pub fn is_verified( .collect::, _>>() } +pub fn is_status_verified( + deps: Deps, + message_statuses: &[(Message, MessageStatus)], +) -> Result, ContractError> { + message_statuses + .iter() + .map(|message_status| { + msg_status_verification_status(deps, message_status).map(|status| { + ( + message_status.0.cc_id.to_owned(), + message_status.1.clone(), + matches!(status, VerificationStatus::Verified), + ) + }) + }) + .collect::, _>>() +} + pub fn is_worker_set_verified(deps: Deps, operators: &Operators) -> Result { Ok(matches!( worker_set_verification_status(deps, operators)?, @@ -47,6 +66,14 @@ pub fn msg_verification_status( Ok(verification_status(deps, loaded_poll_content, message)) } +pub fn msg_status_verification_status( + deps: Deps, + message_status: &(Message, MessageStatus) +) -> Result { + let loaded_poll_content = POLL_MESSAGE_STATUSES.may_load(deps.storage, &message_and_status_key(&message_status.0, &message_status.1))?; + Ok(verification_status(deps, loaded_poll_content, message_status)) +} + pub fn worker_set_verification_status( deps: Deps, operators: &Operators, @@ -72,7 +99,7 @@ fn verification_status( .expect("invalid invariant: message poll not found"); let verified = match &poll { - Poll::Messages(poll) | Poll::ConfirmWorkerSet(poll) => { + Poll::Messages(poll) | Poll::MessageStatuses(poll) | Poll::ConfirmWorkerSet(poll) => { poll.consensus(stored.index_in_poll) .expect("invalid invariant: message not found in poll") == Some(Vote::SucceededOnChain) // TODO: consider Vote::FailedOnChain? @@ -93,7 +120,7 @@ fn verification_status( fn is_finished(poll: &state::Poll) -> bool { match poll { - state::Poll::Messages(poll) | state::Poll::ConfirmWorkerSet(poll) => { + state::Poll::Messages(poll) | state::Poll::MessageStatuses(poll) | state::Poll::ConfirmWorkerSet(poll) => { poll.status == PollStatus::Finished } } diff --git a/contracts/xrpl-voting-verifier/src/state.rs b/contracts/xrpl-voting-verifier/src/state.rs index 2639411ab..31bea31c2 100644 --- a/contracts/xrpl-voting-verifier/src/state.rs +++ b/contracts/xrpl-voting-verifier/src/state.rs @@ -12,7 +12,7 @@ use axelar_wasm_std::{ }; use connection_router::state::{ChainName, Message}; -use crate::error::ContractError; +use crate::{error::ContractError, execute::MessageStatus}; #[cw_serde] pub struct Config { @@ -29,6 +29,7 @@ pub struct Config { #[cw_serde] pub enum Poll { Messages(WeightedPoll), + MessageStatuses(WeightedPoll), ConfirmWorkerSet(WeightedPoll), } @@ -40,6 +41,7 @@ impl Poll { { match self { Poll::Messages(poll) => Ok(Poll::Messages(func(poll)?)), + Poll::MessageStatuses(poll) => Ok(Poll::MessageStatuses(func(poll)?)), Poll::ConfirmWorkerSet(poll) => Ok(Poll::ConfirmWorkerSet(func(poll)?)), } } @@ -62,6 +64,16 @@ impl PollContent { } } +impl PollContent<(Message, MessageStatus)> { + pub fn new(message: Message, status: MessageStatus, poll_id: PollId, index_in_poll: usize) -> Self { + Self { + content: (message, status), + poll_id, + index_in_poll: index_in_poll.try_into().unwrap(), + } + } +} + impl PollContent { pub fn new(operators: Operators, poll_id: PollId) -> Self { Self { @@ -72,12 +84,22 @@ impl PollContent { } } + +pub fn message_and_status_key(msg: &Message, status: &MessageStatus) -> [u8; 33] { + let mut result = [0u8; 33]; + result[..32].copy_from_slice(&msg.hash()); + result[32] = status.clone().into(); + result +} + pub const POLL_ID: counter::Counter = counter::Counter::new("poll_id"); pub const POLLS: Map = Map::new("polls"); pub const POLL_MESSAGES: Map<&Hash, PollContent> = Map::new("poll_messages"); +pub const POLL_MESSAGE_STATUSES: Map<&[u8; 33], PollContent<(Message, MessageStatus)>> = Map::new("poll_message_statuses"); + pub const CONFIG: Item = Item::new("config"); pub const POLL_WORKER_SETS: Map<&Hash, PollContent> = Map::new("poll_worker_sets"); From 7379b5ed5a8930b7e0590194dc766b91c65157cf Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Mon, 18 Dec 2023 16:50:31 +0200 Subject: [PATCH 03/80] xrpl-voting-verifier: confirm ccid status instead of message status --- .../xrpl-voting-verifier/src/bin/schema.rs | 2 +- contracts/xrpl-voting-verifier/src/events.rs | 25 +++++++++++++++++-- contracts/xrpl-voting-verifier/src/execute.rs | 20 +++++++-------- contracts/xrpl-voting-verifier/src/msg.rs | 4 +-- contracts/xrpl-voting-verifier/src/query.rs | 10 ++++---- contracts/xrpl-voting-verifier/src/state.rs | 22 ++++++++-------- 6 files changed, 52 insertions(+), 31 deletions(-) diff --git a/contracts/xrpl-voting-verifier/src/bin/schema.rs b/contracts/xrpl-voting-verifier/src/bin/schema.rs index 6f6a8eb3f..ab7846043 100644 --- a/contracts/xrpl-voting-verifier/src/bin/schema.rs +++ b/contracts/xrpl-voting-verifier/src/bin/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use voting_verifier::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use xrpl_voting_verifier::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/xrpl-voting-verifier/src/events.rs b/contracts/xrpl-voting-verifier/src/events.rs index 08440ffce..1a96bb135 100644 --- a/contracts/xrpl-voting-verifier/src/events.rs +++ b/contracts/xrpl-voting-verifier/src/events.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{Addr, Attribute, Event}; use axelar_wasm_std::nonempty; use axelar_wasm_std::operators::Operators; use axelar_wasm_std::voting::{PollId, Vote}; -use connection_router::state::{Address, ChainName, Message, ID_SEPARATOR}; +use connection_router::state::{Address, ChainName, Message, ID_SEPARATOR, CrossChainId}; use crate::error::ContractError; use crate::execute::MessageStatus; @@ -49,7 +49,7 @@ pub enum PollStarted { metadata: PollMetadata, }, MessageStatuses { - message_statuses: Vec<(TxEventConfirmation, MessageStatus)>, + message_statuses: Vec, metadata: PollMetadata, }, WorkerSet { @@ -137,6 +137,27 @@ impl WorkerSetConfirmation { } } +#[cw_serde] +pub struct TxStatusConfirmation { + pub tx_id: nonempty::String, + pub event_index: u64, + pub status: MessageStatus +} + +impl TryFrom<(CrossChainId, MessageStatus)> for TxStatusConfirmation { + type Error = ContractError; + + fn try_from(other: (CrossChainId, MessageStatus)) -> Result { + let (tx_id, event_index) = parse_message_id(&other.0.id)?; + + Ok(TxStatusConfirmation { + tx_id, + event_index, + status: other.1 + }) + } +} + #[cw_serde] pub struct TxEventConfirmation { pub tx_id: nonempty::String, diff --git a/contracts/xrpl-voting-verifier/src/execute.rs b/contracts/xrpl-voting-verifier/src/execute.rs index c7e5f2551..16c5b59bc 100644 --- a/contracts/xrpl-voting-verifier/src/execute.rs +++ b/contracts/xrpl-voting-verifier/src/execute.rs @@ -6,19 +6,19 @@ use cosmwasm_std::{ use axelar_wasm_std::voting::{PollId, Vote}; use axelar_wasm_std::{nonempty, snapshot, voting::WeightedPoll}; -use connection_router::state::{ChainName, Message}; +use connection_router::state::{ChainName, Message, CrossChainId}; use service_registry::msg::QueryMsg; use service_registry::state::Worker; use crate::error::ContractError; use crate::events::{ - PollEnded, PollMetadata, PollStarted, TxEventConfirmation, Voted, WorkerSetConfirmation, + PollEnded, PollMetadata, PollStarted, TxEventConfirmation, Voted, WorkerSetConfirmation, TxStatusConfirmation, }; use crate::msg::{EndPollResponse, VerifyMessagesResponse, VerifyMessageStatusesResponse}; use crate::query::{ is_verified, is_worker_set_verified, msg_verification_status, VerificationStatus, msg_status_verification_status, is_status_verified, }; -use crate::state::{self, Poll, PollContent, POLL_MESSAGES, POLL_WORKER_SETS, message_and_status_key, POLL_MESSAGE_STATUSES}; +use crate::state::{self, Poll, PollContent, POLL_MESSAGES, POLL_WORKER_SETS, message_status_key, POLL_MESSAGE_STATUSES}; use crate::state::{CONFIG, POLLS, POLL_ID}; pub fn verify_worker_set( @@ -166,7 +166,7 @@ impl Into for MessageStatus { pub fn verify_message_statuses( deps: DepsMut, env: Env, - message_statuses: Vec<(Message, MessageStatus)>, + message_statuses: Vec<(CrossChainId, MessageStatus)>, ) -> Result { if message_statuses.is_empty() { return Err(ContractError::EmptyMessages)?; @@ -176,7 +176,7 @@ pub fn verify_message_statuses( if message_statuses .iter() - .any(|(message, _)| message.cc_id.chain.ne(&source_chain)) + .any(|(cc_id, _)| cc_id.chain.ne(&source_chain)) { return Err(ContractError::SourceChainMismatch(source_chain))?; } @@ -195,7 +195,7 @@ pub fn verify_message_statuses( }) .collect::, _>>()?; - let msg_statuses_to_verify: Vec<(Message, MessageStatus)> = message_statuses + let msg_statuses_to_verify: Vec<(CrossChainId, MessageStatus)> = message_statuses .into_iter() .filter_map(|(status, message, message_status)| match status { VerificationStatus::FailedToVerify | VerificationStatus::NotVerified => Some((message, message_status)), @@ -220,15 +220,15 @@ pub fn verify_message_statuses( for (idx, (message, status)) in msg_statuses_to_verify.iter().enumerate() { POLL_MESSAGE_STATUSES.save( deps.storage, - &message_and_status_key(message, status), - &state::PollContent::<(Message, MessageStatus)>::new(message.clone(), status.clone(), id, idx), + &message_status_key(message, status), + &state::PollContent::<(CrossChainId, MessageStatus)>::new(message.clone(), status.clone(), id, idx), )?; } let message_statuses = msg_statuses_to_verify .into_iter() - .map(|(msg, status)| { TryInto::try_into(msg).map(|t| (t, status)) }) - .collect::, _>>()?; + .map(|(cc_id, status)| TryInto::try_into((cc_id, status))) + .collect::, _>>()?; Ok(response.add_event( PollStarted::MessageStatuses { diff --git a/contracts/xrpl-voting-verifier/src/msg.rs b/contracts/xrpl-voting-verifier/src/msg.rs index 1b4d3b87d..f5410a69a 100644 --- a/contracts/xrpl-voting-verifier/src/msg.rs +++ b/contracts/xrpl-voting-verifier/src/msg.rs @@ -47,7 +47,7 @@ pub enum ExecuteMsg { // returns a vector of true/false values, indicating current verification status for each message and status // starts a poll for any not yet verified message statuses VerifyMessageStatuses { - message_statuses: Vec<(Message, MessageStatus)>, + message_statuses: Vec<(CrossChainId, MessageStatus)>, }, // Starts a poll to confirm a worker set update on the external evm gateway @@ -73,7 +73,7 @@ pub enum QueryMsg { IsVerified { messages: Vec }, #[returns(Vec<(connection_router::state::CrossChainId, MessageStatus, bool)>)] - IsStatusVerified { message_statuses: Vec<(Message, MessageStatus)> }, + IsStatusVerified { message_statuses: Vec<(CrossChainId, MessageStatus)> }, #[returns(bool)] IsWorkerSetVerified { new_operators: Operators }, diff --git a/contracts/xrpl-voting-verifier/src/query.rs b/contracts/xrpl-voting-verifier/src/query.rs index 024a35963..f9a0508e6 100644 --- a/contracts/xrpl-voting-verifier/src/query.rs +++ b/contracts/xrpl-voting-verifier/src/query.rs @@ -5,7 +5,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Deps; use crate::error::ContractError; -use crate::state::{self, Poll, PollContent, POLLS, POLL_MESSAGES, POLL_WORKER_SETS, POLL_MESSAGE_STATUSES, message_and_status_key}; +use crate::state::{self, Poll, PollContent, POLLS, POLL_MESSAGES, POLL_WORKER_SETS, POLL_MESSAGE_STATUSES, message_status_key}; use crate::execute::MessageStatus; #[cw_serde] @@ -35,14 +35,14 @@ pub fn is_verified( pub fn is_status_verified( deps: Deps, - message_statuses: &[(Message, MessageStatus)], + message_statuses: &[(CrossChainId, MessageStatus)], ) -> Result, ContractError> { message_statuses .iter() .map(|message_status| { msg_status_verification_status(deps, message_status).map(|status| { ( - message_status.0.cc_id.to_owned(), + message_status.0.to_owned(), message_status.1.clone(), matches!(status, VerificationStatus::Verified), ) @@ -68,9 +68,9 @@ pub fn msg_verification_status( pub fn msg_status_verification_status( deps: Deps, - message_status: &(Message, MessageStatus) + message_status: &(CrossChainId, MessageStatus) ) -> Result { - let loaded_poll_content = POLL_MESSAGE_STATUSES.may_load(deps.storage, &message_and_status_key(&message_status.0, &message_status.1))?; + let loaded_poll_content = POLL_MESSAGE_STATUSES.may_load(deps.storage, &message_status_key(&message_status.0, &message_status.1))?; Ok(verification_status(deps, loaded_poll_content, message_status)) } diff --git a/contracts/xrpl-voting-verifier/src/state.rs b/contracts/xrpl-voting-verifier/src/state.rs index 31bea31c2..916329a8a 100644 --- a/contracts/xrpl-voting-verifier/src/state.rs +++ b/contracts/xrpl-voting-verifier/src/state.rs @@ -10,7 +10,8 @@ use axelar_wasm_std::{ voting::{PollId, WeightedPoll}, Threshold, }; -use connection_router::state::{ChainName, Message}; +use connection_router::state::{ChainName, Message, CrossChainId}; +use sha3::{Keccak256, Digest}; use crate::{error::ContractError, execute::MessageStatus}; @@ -64,10 +65,10 @@ impl PollContent { } } -impl PollContent<(Message, MessageStatus)> { - pub fn new(message: Message, status: MessageStatus, poll_id: PollId, index_in_poll: usize) -> Self { +impl PollContent<(CrossChainId, MessageStatus)> { + pub fn new(cc_id: CrossChainId, status: MessageStatus, poll_id: PollId, index_in_poll: usize) -> Self { Self { - content: (message, status), + content: (cc_id, status), poll_id, index_in_poll: index_in_poll.try_into().unwrap(), } @@ -84,12 +85,11 @@ impl PollContent { } } - -pub fn message_and_status_key(msg: &Message, status: &MessageStatus) -> [u8; 33] { - let mut result = [0u8; 33]; - result[..32].copy_from_slice(&msg.hash()); - result[32] = status.clone().into(); - result +pub fn message_status_key(cc_id: &CrossChainId, status: &MessageStatus) -> [u8; 32] { + let mut hasher = Keccak256::new(); + hasher.update(cc_id.to_string()); + hasher.update([status.clone().into()]); + hasher.finalize().into() } pub const POLL_ID: counter::Counter = counter::Counter::new("poll_id"); @@ -98,7 +98,7 @@ pub const POLLS: Map = Map::new("polls"); pub const POLL_MESSAGES: Map<&Hash, PollContent> = Map::new("poll_messages"); -pub const POLL_MESSAGE_STATUSES: Map<&[u8; 33], PollContent<(Message, MessageStatus)>> = Map::new("poll_message_statuses"); +pub const POLL_MESSAGE_STATUSES: Map<&[u8; 32], PollContent<(CrossChainId, MessageStatus)>> = Map::new("poll_message_statuses"); pub const CONFIG: Item = Item::new("config"); From ccf929d68b29973f514bbf0f760a1272765869d6 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Mon, 18 Dec 2023 22:51:56 +0530 Subject: [PATCH 04/80] xrpl-voting-verifier: fix "message" instead of "cc_id" typo --- contracts/xrpl-voting-verifier/src/execute.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/xrpl-voting-verifier/src/execute.rs b/contracts/xrpl-voting-verifier/src/execute.rs index 16c5b59bc..4c8e8ea1e 100644 --- a/contracts/xrpl-voting-verifier/src/execute.rs +++ b/contracts/xrpl-voting-verifier/src/execute.rs @@ -189,16 +189,16 @@ pub fn verify_message_statuses( let message_statuses = message_statuses .into_iter() - .map(|(message, status)| { - msg_status_verification_status(deps.as_ref(), &(message.clone(), status.clone())) - .map(|verification_status| (verification_status, message, status)) + .map(|(cc_id, status)| { + msg_status_verification_status(deps.as_ref(), &(cc_id.clone(), status.clone())) + .map(|verification_status| (verification_status, cc_id, status)) }) .collect::, _>>()?; let msg_statuses_to_verify: Vec<(CrossChainId, MessageStatus)> = message_statuses .into_iter() - .filter_map(|(status, message, message_status)| match status { - VerificationStatus::FailedToVerify | VerificationStatus::NotVerified => Some((message, message_status)), + .filter_map(|(status, cc_id, message_status)| match status { + VerificationStatus::FailedToVerify | VerificationStatus::NotVerified => Some((cc_id, message_status)), VerificationStatus::InProgress | VerificationStatus::Verified => None, }) .collect(); @@ -217,11 +217,11 @@ pub fn verify_message_statuses( msg_statuses_to_verify.len(), )?; - for (idx, (message, status)) in msg_statuses_to_verify.iter().enumerate() { + for (idx, (cc_id, status)) in msg_statuses_to_verify.iter().enumerate() { POLL_MESSAGE_STATUSES.save( deps.storage, - &message_status_key(message, status), - &state::PollContent::<(CrossChainId, MessageStatus)>::new(message.clone(), status.clone(), id, idx), + &message_status_key(cc_id, status), + &state::PollContent::<(CrossChainId, MessageStatus)>::new(cc_id.clone(), status.clone(), id, idx), )?; } From 6f850a0d3876e37bf12718eb1aed2c63053d80f4 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 19 Dec 2023 14:49:23 +0200 Subject: [PATCH 05/80] xrpl-multisig-prover --- Cargo.lock | 26 + Cargo.toml | 3 +- contracts/xrpl-multisig-prover/Cargo.toml | 31 + .../src/axelar_workers.rs | 250 +++++ .../xrpl-multisig-prover/src/contract.rs | 310 +++++++ contracts/xrpl-multisig-prover/src/error.rs | 79 ++ contracts/xrpl-multisig-prover/src/events.rs | 54 ++ contracts/xrpl-multisig-prover/src/lib.rs | 10 + contracts/xrpl-multisig-prover/src/querier.rs | 84 ++ contracts/xrpl-multisig-prover/src/query.rs | 66 ++ contracts/xrpl-multisig-prover/src/reply.rs | 41 + contracts/xrpl-multisig-prover/src/state.rs | 45 + contracts/xrpl-multisig-prover/src/types.rs | 113 +++ .../xrpl-multisig-prover/src/xrpl_multisig.rs | 857 ++++++++++++++++++ 14 files changed, 1968 insertions(+), 1 deletion(-) create mode 100644 contracts/xrpl-multisig-prover/Cargo.toml create mode 100644 contracts/xrpl-multisig-prover/src/axelar_workers.rs create mode 100644 contracts/xrpl-multisig-prover/src/contract.rs create mode 100644 contracts/xrpl-multisig-prover/src/error.rs create mode 100644 contracts/xrpl-multisig-prover/src/events.rs create mode 100644 contracts/xrpl-multisig-prover/src/lib.rs create mode 100644 contracts/xrpl-multisig-prover/src/querier.rs create mode 100644 contracts/xrpl-multisig-prover/src/query.rs create mode 100644 contracts/xrpl-multisig-prover/src/reply.rs create mode 100644 contracts/xrpl-multisig-prover/src/state.rs create mode 100644 contracts/xrpl-multisig-prover/src/types.rs create mode 100644 contracts/xrpl-multisig-prover/src/xrpl_multisig.rs diff --git a/Cargo.lock b/Cargo.lock index 5cefa58bc..e44a21457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9289,6 +9289,32 @@ dependencies = [ "time", ] +[[package]] +name = "xrpl-multisig-prover" +version = "0.1.0" +dependencies = [ + "axelar-wasm-std", + "axelar-wasm-std-derive", + "bs58 0.5.0", + "connection-router", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "error-stack", + "gateway", + "hex", + "multisig", + "report", + "ripemd", + "serde_json", + "service-registry", + "sha2 0.10.8", + "sha3 0.10.8", + "thiserror", + "xrpl-voting-verifier", +] + [[package]] name = "xrpl-voting-verifier" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 36ec10442..2f6aaa5cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ axelar-wasm-std = { version = "^0.1.0", path = "packages/axelar-wasm-std" } axelar-wasm-std-derive = { version = "^0.1.0", path = "packages/axelar-wasm-std-derive" } itertools = "0.11.0" voting-verifier = { version = "^0.1.0", path = "contracts/voting-verifier" } -xrpl-voting-verifier = { version = "^0.1.0", path = "contracts/voting-verifier" } +xrpl-voting-verifier = { version = "^0.1.0", path = "contracts/xrpl-voting-verifier" } +xrpl-multisig-prover = { version = "^0.1.0", path = "contracts/xrpl-multisig-prover" } multisig = { version = "^0.1.0", path = "contracts/multisig" } multisig-prover = { version = "^0.1.0", path = "contracts/multisig-prover" } service-registry = { version = "^0.1.0", path = "contracts/service-registry" } diff --git a/contracts/xrpl-multisig-prover/Cargo.toml b/contracts/xrpl-multisig-prover/Cargo.toml new file mode 100644 index 000000000..6f3e6ee60 --- /dev/null +++ b/contracts/xrpl-multisig-prover/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "xrpl-multisig-prover" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] + +[dependencies] +cosmwasm-std = { workspace = true, version = "1.5.0" } +cosmwasm-schema = { workspace = true } +axelar-wasm-std = { workspace = true } +axelar-wasm-std-derive = { workspace = true } +cw-storage-plus = { workspace = true } +connection-router = { workspace = true, features = ["library"] } +service-registry = { workspace = true } +multisig = { workspace = true, features = ["library"] } +gateway = { workspace = true } +thiserror = { workspace = true } +error-stack = { workspace = true } +report = { workspace = true } +serde_json = "1.0.89" +sha2 = { version = "0.10.7" } +sha3 = { version = "0.10", default-features = false, features = [] } +cw-utils = "1.0.1" +bs58 = "0.5.0" +ripemd = "0.1.3" +hex = "0.4.3" +xrpl-voting-verifier = { workspace = true, features = ["library"] } diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs new file mode 100644 index 000000000..e004902af --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -0,0 +1,250 @@ +use std::collections::hash_map::RandomState; +use std::collections::{BTreeSet, HashMap}; + +use axelar_wasm_std::{Threshold, nonempty}; +use axelar_wasm_std::Participant; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, Uint256, Addr, Fraction}; +use multisig::key::KeyType; +use service_registry::state::Worker; +use sha2::Digest; +use multisig::key::PublicKey; +use sha3::Keccak256; + +use crate::querier::Querier; +use crate::error::ContractError; + +#[cw_serde] +#[derive(Eq, Ord, PartialOrd)] +pub struct AxelarSigner { + pub address: Addr, + pub weight: u16, + pub pub_key: PublicKey, +} + +impl Into for AxelarSigner { + fn into(self) -> Participant { + let weight = nonempty::Uint256::try_from(Uint256::from(self.weight as u128)).unwrap(); + Participant { + address: self.address, + weight, + } + } +} + +#[cw_serde] +pub struct WorkerSet { + pub signers: BTreeSet, + pub quorum: u32, + // for hash uniqueness. The same exact worker set could be in use at two different times, + // and we need to be able to distinguish between the two + pub created_at: u64, +} + +impl Into for WorkerSet { + fn into(self) -> multisig::worker_set::WorkerSet { + let participants = self.signers.into_iter() + .map(|s| (s.clone().into(), s.pub_key)) + .collect(); + multisig::worker_set::WorkerSet::new( + participants, + Uint256::from(self.quorum as u128), + self.created_at + ) + } +} + +/* +fn convert_u32_to_nonempty_uint256(value: u32) -> nonempty::Uint256 { + nonempty::Uint256::try_from(Uint256::from(value as u128)).unwrap() +} + +impl Into for WorkerSet { + fn into(self) -> Snapshot { + Snapshot { + quorum: convert_u32_to_nonempty_uint256(self.quorum), + participants: self.signers + .into_iter() + .map(|signer| (signer.address.to_string(), signer.into())) + .collect(), + } + } +} +*/ + +impl WorkerSet { + /*pub fn new( + participants: Vec<(Participant, PublicKey)>, + threshold: u32, + block_height: u64, + ) -> Self { + let signers = participants + .into_iter() + .map(|(participant, pub_key)| Signer { + address: participant.address.clone(), + weight: participant.weight.into(), + pub_key, + }) + .collect(); + + WorkerSet { + signers, + threshold, + created_at: block_height, + } + }*/ + + pub fn pub_keys_by_address(&self) -> HashMap { + self + .signers + .clone() + .into_iter() + .map(|signer| { + ( + signer.address.to_string(), + (KeyType::Ecdsa, signer.pub_key.as_ref().into()), + ) + }) + .collect() + } + + pub fn hash(&self) -> HexBinary { + Keccak256::digest(serde_json::to_vec(&self).expect("couldn't serialize worker set")) + .as_slice() + .into() + } + + pub fn id(&self) -> String { + self.hash().to_hex() + } +} + +fn convert_uint256_to_u16_unsafely(value: Uint256) -> u16 { + let bytes = value.to_le_bytes(); + (bytes[0] as u16) | (bytes[1] as u16) << 8 +} + +// Converts a Vec to Vec, scaling down with precision loss, if necessary. +// We make sure that XRPL multisig and Axelar multisig both use the same scaled down numbers and have the same precision loss +fn convert_or_scale_weights(weights: Vec) -> Vec { + let max_weight: Option<&Uint256> = weights.iter().max(); + match max_weight { + Some(max_weight) => { + let max_u16_as_uint256 = Uint256::from(u16::MAX); + // Scaling down + weights + .clone() + .into_iter() + .map(|weight| { + // multiply_ratio returns a rounded down value + let scaled = weight.multiply_ratio(max_u16_as_uint256, *max_weight); + convert_uint256_to_u16_unsafely(scaled) + }) + .collect() + }, + None => vec![], + } +} + +pub fn get_active_worker_set( + querier: Querier, + signing_threshold: Threshold, + block_height: u64, +) -> Result { + let workers: Vec = querier.get_active_workers()?; + + let participants: Vec = workers + .into_iter() + .map(|worker| Participant::try_from(worker)) + .filter(|result| result.is_ok()) + .map(|result| result.unwrap()) + .collect(); + + let weights = convert_or_scale_weights(participants + .clone() + .into_iter() + .map(|participant| Uint256::from(participant.weight)) + .collect()); + + let mut signers: Vec = vec![]; + for (i, participant) in participants.iter().enumerate() { + let pub_key: PublicKey = querier.get_public_key(participant.address.clone().to_string())?; + signers.push(AxelarSigner { + address: participant.address.clone(), + weight: weights[i], + pub_key, + }); + } + + let sum_of_weights: u16 = weights.iter().sum(); + + let quorum = (sum_of_weights as u64) + .checked_mul(signing_threshold.numerator().into()) + .unwrap() + .checked_div(signing_threshold.denominator().into()) + .unwrap() as u32; + + let worker_set = WorkerSet { + signers: signers.into_iter().collect(), + quorum, + created_at: block_height, + }; + + Ok(worker_set) +} + +pub fn should_update_worker_set( + new_workers: &WorkerSet, + cur_workers: &WorkerSet, + max_diff: usize, +) -> bool { + new_workers.signers.difference(&cur_workers.signers).count() + + cur_workers.signers.difference(&new_workers.signers).count() + > max_diff +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_convert_or_scale_weights() { + let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128)]; + let scaled_weights = convert_or_scale_weights(weights); + assert_eq!(scaled_weights, vec![21845, 43690, 65535]); + + let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128), Uint256::from(4u128)]; + let scaled_weights = convert_or_scale_weights(weights); + assert_eq!(scaled_weights, vec![16383, 32767, 49151, 65535]); + + let weights = vec![ + Uint256::MAX - Uint256::from(3u128), + Uint256::MAX - Uint256::from(2u128), + Uint256::MAX - Uint256::from(1u128), + Uint256::MAX + ]; + let scaled_weights = convert_or_scale_weights(weights); + assert_eq!(scaled_weights, vec![65534, 65534, 65534, 65535]); + + let weights = vec![ + Uint256::from(0u128), + Uint256::from(1u128), + Uint256::MAX - Uint256::from(1u128), + Uint256::MAX + ]; + let scaled_weights = convert_or_scale_weights(weights); + assert_eq!(scaled_weights, vec![0, 0, 65534, 65535]); + + let weights = vec![ + Uint256::from(100000u128), + Uint256::from(2000000u128), + Uint256::from(30000000u128), + Uint256::from(400000000u128), + Uint256::from(50000000000u128), + ]; + let scaled_weights = convert_or_scale_weights(weights); + assert_eq!(scaled_weights, vec![0, 2, 39, 524, 65535]); + + assert_eq!(convert_or_scale_weights(vec![] as Vec), vec![] as Vec); + } +} diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs new file mode 100644 index 000000000..ea0bc8f1f --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -0,0 +1,310 @@ +use std::str::FromStr; + +#[cfg(not(feature = "library"))] +use axelar_wasm_std::Threshold; +use connection_router::state::{CrossChainId, ChainName}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + entry_point, Storage, wasm_execute, SubMsg, Reply, + DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_binary, Deps, StdResult, Binary, Addr, +}; +use xrpl_voting_verifier::execute::MessageStatus; + +use crate::{ + error::ContractError, + state::{Config, CONFIG, REPLY_TX_HASH, TOKENS, CURRENT_WORKER_SET, NEXT_WORKER_SET, SIGNED_TO_UNSIGNED_TX_HASH}, + reply, + types::*, + xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query::{get_proof, GetProofResponse, QueryMsg, self}, +}; + +pub const START_MULTISIG_REPLY_ID: u64 = 1; + +#[cw_serde] +pub struct InstantiateMsg { + axelar_multisig_address: String, + gateway_address: String, + signing_threshold: Threshold, + xrpl_multisig_address: String, + voting_verifier_address: String, + service_registry_address: String, + service_name: String, + worker_set_diff_threshold: u32, + xrpl_fee: u64, + ticket_count_threshold: u32, +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let axelar_multisig_address = deps.api.addr_validate(&msg.axelar_multisig_address)?; + let gateway_address = deps.api.addr_validate(&msg.gateway_address)?; + let xrpl_multisig_address = deps.api.addr_validate(&msg.xrpl_multisig_address)?; + let voting_verifier_address = deps.api.addr_validate(&msg.voting_verifier_address)?; + let service_registry_address = deps.api.addr_validate(&msg.service_registry_address)?; + + if msg.signing_threshold.numerator() > u32::MAX.into() { + return Err(ContractError::InvalidSigningThreshold.into()); + } + + let config = Config { + axelar_multisig_address, + gateway_address, + xrpl_multisig_address, + signing_threshold: msg.signing_threshold, + voting_verifier_address, + service_registry_address, + service_name: msg.service_name, + worker_set_diff_threshold: msg.worker_set_diff_threshold, + xrpl_fee: msg.xrpl_fee, + ticket_count_threshold: msg.ticket_count_threshold, + }; + + CONFIG.save(deps.storage, &config)?; + + let querier = Querier::new(deps.querier, config.clone()); + let new_worker_set = axelar_workers::get_active_worker_set(querier, msg.signing_threshold, env.block.height)?; + + CURRENT_WORKER_SET.save(deps.storage, &new_worker_set)?; + + Ok(Response::new().add_message(wasm_execute( + config.axelar_multisig_address, + &multisig::msg::ExecuteMsg::RegisterWorkerSet { + worker_set: new_worker_set.into(), + }, + vec![], + )?)) +} + +#[cw_serde] +pub enum ExecuteMsg { + ConstructProof(CrossChainId), + FinalizeProof(Uint64), + UpdateTxStatus(CrossChainId, MessageStatus), + UpdateWorkerSet(), + TicketCreate(), +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let querier = Querier::new(deps.querier, config.clone()); + + let res = match msg { + ExecuteMsg::ConstructProof(message_id) => { + construct_payment_proof(deps.storage, querier, info, env.contract.address, &config, message_id) + }, + ExecuteMsg::FinalizeProof(session_id) => { + finalize_proof(deps.storage, querier, &session_id) + } + ExecuteMsg::UpdateWorkerSet() => { + construct_signer_list_set_proof(deps.storage, querier, env, &config) + }, + ExecuteMsg::UpdateTxStatus(cc_id, status) => { + update_tx_status(deps.storage, querier, cc_id, &status) + }, + ExecuteMsg::TicketCreate() => { + construct_ticket_create_proof(deps.storage, env.contract.address, &config) + }, + }?; + + Ok(res) +} + +fn finalize_proof( + storage: &mut dyn Storage, + querier: Querier, + multisig_session_id: &Uint64 +) -> Result { + match get_proof(storage, querier, multisig_session_id)? { + GetProofResponse::Pending { unsigned_tx_hash: _ } => { + Err(ContractError::SigningSessionNotCompleted) + }, + GetProofResponse::Completed { unsigned_tx_hash, tx_blob } => { + let tx_hash = xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?; + SIGNED_TO_UNSIGNED_TX_HASH.save(storage, tx_hash, &unsigned_tx_hash)?; + Ok(Response::default()) + } + } +} + +fn construct_payment_proof( + storage: &mut dyn Storage, + querier: Querier, + info: MessageInfo, + self_address: Addr, + config: &Config, + message_id: CrossChainId, +) -> Result { + if info.funds.len() != 1 { + return Err(ContractError::InvalidPaymentAmount); + } + + let mut funds = info.funds; + let coin = funds.remove(0); + let xrpl_token = TOKENS.load(storage, coin.denom.clone())?; + let message = querier.get_message(message_id.clone())?; + let drops = u64::try_from(coin.amount.u128() / 10u128.pow(12)).map_err(|_| ContractError::InvalidAmount)?; + let xrpl_payment_amount = if xrpl_token.currency == XRPLToken::NATIVE_CURRENCY { + XRPLPaymentAmount::Drops(drops) + } else { + XRPLPaymentAmount::Token( + XRPLToken { + issuer: xrpl_token.issuer, + currency: xrpl_token.currency, + }, + XRPLTokenAmount(drops.to_string()), + ) + }; + + let tx_hash = xrpl_multisig::issue_payment( + storage, + config, + message.destination_address.to_string().try_into()?, + xrpl_payment_amount, + message_id, + )?; + + Ok( + start_signing_session( + storage, + config, + tx_hash, + self_address + )? + ) +} + +pub fn start_signing_session( + storage: &mut dyn Storage, + config: &Config, + tx_hash: TxHash, + self_address: Addr, +) -> Result { + REPLY_TX_HASH.save(storage, &tx_hash)?; + let cur_worker_set = CURRENT_WORKER_SET.load(storage)?; + let start_sig_msg: multisig::msg::ExecuteMsg = multisig::msg::ExecuteMsg::StartSigningSession { + worker_set_id: cur_worker_set.id(), + chain_name: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), + msg: tx_hash.into(), + sig_verifier: Some(self_address.into()) + }; + + let wasm_msg = wasm_execute(config.axelar_multisig_address.clone(), &start_sig_msg, vec![])?; + + Ok(Response::new().add_submessage(SubMsg::reply_on_success(wasm_msg, START_MULTISIG_REPLY_ID))) +} + +fn construct_signer_list_set_proof( + storage: &mut dyn Storage, + querier: Querier, + env: Env, + config: &Config, +) -> Result { + if !CURRENT_WORKER_SET.exists(storage) { + return Err(ContractError::WorkerSetIsNotSet.into()) + } + + let new_worker_set = axelar_workers::get_active_worker_set(querier, config.signing_threshold, env.block.height)?; + let cur_worker_set = CURRENT_WORKER_SET.load(storage)?; + if !axelar_workers::should_update_worker_set( + &new_worker_set, + &cur_worker_set, + config.worker_set_diff_threshold as usize, + ) { + return Err(ContractError::WorkerSetUnchanged.into()) + } + + let tx_hash = xrpl_multisig::issue_signer_list_set( + storage, + config, + cur_worker_set, + )?; + + NEXT_WORKER_SET.save(storage, tx_hash.clone(), &new_worker_set)?; + + Ok( + start_signing_session( + storage, + config, + tx_hash, + env.contract.address + )? + ) +} + +fn construct_ticket_create_proof( + storage: &mut dyn Storage, + self_address: Addr, + config: &Config, +) -> Result { + let ticket_count = xrpl_multisig::available_ticket_count(storage)?; + if ticket_count < config.ticket_count_threshold { + return Err(ContractError::TicketCountThresholdNotReached.into()); + } + + let tx_hash = xrpl_multisig::issue_ticket_create( + storage, + config, + ticket_count, + )?; + + let response = start_signing_session( + storage, + config, + tx_hash, + self_address + )?; + + Ok(response) +} + +fn update_tx_status( + storage: &mut dyn Storage, + querier: Querier, + cc_id: CrossChainId, + status: &MessageStatus +) -> Result { + if !querier.get_message_confirmation(cc_id.clone(), status)? { + return Err(ContractError::InvalidMessageStatus) + } + + let tx_hash: TxHash = TxHash::try_from(cc_id)?; + + xrpl_multisig::update_tx_status(storage, tx_hash, status.clone().into())?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply( + deps: DepsMut, + _env: Env, + reply: Reply, +) -> Result { + match reply.id { + START_MULTISIG_REPLY_ID => reply::start_multisig_reply(deps, reply), + _ => unreachable!("unknown reply ID"), + } + .map_err(axelar_wasm_std::ContractError::from) +} + + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let querier = Querier::new(deps.querier, config.clone()); + match msg { + QueryMsg::GetProof { + multisig_session_id, + } => to_binary(&query::get_proof(deps.storage, querier, &multisig_session_id)?), + } +} \ No newline at end of file diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs new file mode 100644 index 000000000..d9fd4ed6e --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -0,0 +1,79 @@ +use axelar_wasm_std::nonempty; +use axelar_wasm_std_derive::IntoContractError; +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, IntoContractError)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] StdError), + + #[error("invalid amount")] + InvalidAmount, + + #[error("serialization failed")] + SerializationFailed, + + #[error("invalid contract reply: {reason}")] + InvalidContractReply { reason: String }, + + #[error("caller is not authorized")] + Unauthorized, + + #[error("chain name is invalid")] + InvalidChainName, + + #[error(transparent)] + ServiceRegistryError(#[from] service_registry::ContractError), + + #[error(transparent)] + NonEmptyError(#[from] nonempty::Error), + + #[error("worker set has not changed sufficiently since last update")] + WorkerSetUnchanged, + + #[error("ticket count threshold has not been reached")] + TicketCountThresholdNotReached, + + #[error("transaction status is already updated")] + TransactionStatusAlreadyUpdated, + + #[error("previous ticket create transaction is pending")] + PreviousTicketCreateTxPending, + + #[error("invalid message status")] + InvalidMessageStatus, + + #[error("transaction has not been confirmed")] + TransactionStatusNotConfirmed, + + #[error("invalid payment amount")] + InvalidPaymentAmount, + + #[error("invalid signing threshold")] + InvalidSigningThreshold, + + #[error("worker set is not set")] + WorkerSetIsNotSet, + + #[error("invalid address")] + InvalidAddress, + + #[error("invalid currency")] + InvalidCurrency, + + #[error("invalid signing pub key")] + InvalidSigningPubKey, + + #[error("invalid transaction signature")] + InvalidSignature, + + #[error("signing session not completed")] + SigningSessionNotCompleted, +} + +impl From for StdError { + fn from(value: ContractError) -> Self { + Self::generic_err(value.to_string()) + } +} diff --git a/contracts/xrpl-multisig-prover/src/events.rs b/contracts/xrpl-multisig-prover/src/events.rs new file mode 100644 index 000000000..94fc2262a --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/events.rs @@ -0,0 +1,54 @@ +use std::collections::HashMap; + +use axelar_wasm_std::Snapshot; +use cosmwasm_std::{HexBinary, Uint64}; +use serde_json::to_string; +use crate::types::TxHash; + +pub enum Event { + ProofUnderConstruction { + tx_hash: TxHash, + multisig_session_id: Uint64, + }, + SnapshotRotated { + key_id: String, + snapshot: Snapshot, + pub_keys: HashMap, + }, +} + +impl From for cosmwasm_std::Event { + fn from(other: Event) -> Self { + match other { + Event::ProofUnderConstruction { + tx_hash, + multisig_session_id, + } => cosmwasm_std::Event::new("proof_under_construction") + .add_attribute( + "tx_hash", + to_string(&tx_hash) + .expect("violated invariant: tx_hash is not serializable"), + ) + .add_attribute( + "multisig_session_id", + to_string(&multisig_session_id) + .expect("violated invariant: multisig_session_id is not serializable"), + ), + Event::SnapshotRotated { + key_id, + snapshot, + pub_keys, + } => cosmwasm_std::Event::new("snapshot_rotated") + .add_attribute("key_id", key_id) + .add_attribute( + "snapshot", + to_string(&snapshot).expect("violated invariant: snapshot is not serializable"), + ) + .add_attribute( + "pub_keys", + to_string(&pub_keys) + .expect("violated invariant: pub_keys are not serializable"), + ), + } + } +} diff --git a/contracts/xrpl-multisig-prover/src/lib.rs b/contracts/xrpl-multisig-prover/src/lib.rs new file mode 100644 index 000000000..f1c0dbfc9 --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/lib.rs @@ -0,0 +1,10 @@ +pub mod contract; +pub mod state; +pub mod error; +pub mod reply; +pub mod events; +pub mod types; +pub mod query; +pub mod xrpl_multisig; +pub mod axelar_workers; +pub mod querier; diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs new file mode 100644 index 000000000..f109c3932 --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -0,0 +1,84 @@ +use std::str::FromStr; + +#[cfg(not(feature = "library"))] +use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; +use cosmwasm_std::{ + to_binary, QueryRequest, WasmQuery, QuerierWrapper, Uint64, +}; +use multisig::{key::{KeyType, PublicKey}, msg::Multisig}; +use xrpl_voting_verifier::execute::MessageStatus; + +use crate::{ + error::ContractError, + state::Config, +}; + +use connection_router::state::{Message, CrossChainId, ChainName}; +use service_registry::state::Worker; + +pub const XRPL_CHAIN_NAME: &str = "XRPL"; + +fn query(querier: QuerierWrapper, contract_addr: String, query_msg: &T) -> Result +where U: DeserializeOwned, T: Serialize + ?Sized { + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr, + msg: to_binary(&query_msg)?, + })).map_err(ContractError::from) +} + +pub struct Querier<'a> { + querier: QuerierWrapper<'a>, + config: Config, +} + +impl<'a> Querier<'a> { + pub fn new(querier: QuerierWrapper<'a>, config: Config) -> Self { + Self { + querier, + config, + } + } + + pub fn get_active_workers(&self) -> Result, ContractError> { + query(self.querier, self.config.service_registry_address.to_string(), + &service_registry::msg::QueryMsg::GetActiveWorkers { + service_name: self.config.service_name.clone(), + chain_name: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), + }, + ) + } + + pub fn get_public_key(&self, worker_address: String) -> Result { + query(self.querier, self.config.axelar_multisig_address.to_string(), + &multisig::msg::QueryMsg::GetPublicKey { + worker_address, + key_type: KeyType::Ecdsa, + }, + ) + } + + pub fn get_message(&self, message_id: CrossChainId) -> Result { + let messages: Vec = query(self.querier, self.config.gateway_address.to_string(), + &gateway::msg::QueryMsg::GetMessages { + message_ids: vec![message_id], + } + )?; + Ok(messages[0].clone()) + } + + pub fn get_message_confirmation(&self, cc_id: CrossChainId, status: &MessageStatus) -> Result { + let confirmations: Vec<(CrossChainId, MessageStatus, bool)> = query(self.querier, self.config.voting_verifier_address.to_string(), + &xrpl_voting_verifier::msg::QueryMsg::IsStatusVerified { + message_statuses: vec![(cc_id, status.clone())], + } + )?; + Ok(confirmations[0].2) + } + + pub fn get_multisig_session(&self, multisig_session_id: Uint64) -> Result { + let query_msg = multisig::msg::QueryMsg::GetMultisig { + session_id: multisig_session_id, + }; + query(self.querier, self.config.axelar_multisig_address.to_string(), &query_msg) + } +} diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs new file mode 100644 index 000000000..736246c24 --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -0,0 +1,66 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{StdResult, Uint64, HexBinary, Storage}; + +use multisig::types::MultisigState; + +use crate::{ + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO}, types::TxHash, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self}, querier::Querier, +}; + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(GetProofResponse)] + GetProof { multisig_session_id: Uint64 }, +} + +#[cw_serde] +#[serde(tag = "status")] +pub enum GetProofResponse { + Completed { unsigned_tx_hash: TxHash, tx_blob: HexBinary}, + Pending { unsigned_tx_hash: TxHash }, +} + +pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> XRPLSignedTransaction { + let xrpl_signers: Vec = axelar_signers + .iter() + .map(|(axelar_signer, signature)| { + let xrpl_address = xrpl_multisig::public_key_to_xrpl_address(axelar_signer.pub_key.clone()); + XRPLSigner { + account: xrpl_address, + signing_pub_key: axelar_signer.pub_key.clone().into(), + txn_signature: HexBinary::from(signature.clone().as_ref()) + } + }) + .collect::>(); + + XRPLSignedTransaction { + unsigned_tx, + signers: xrpl_signers, + } +} + +pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: &Uint64) -> StdResult { + let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; + + let tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; + + let multisig_session= querier.get_multisig_session(multisig_session_id.clone())?; + + let response = match multisig_session.state { + MultisigState::Pending => GetProofResponse::Pending { unsigned_tx_hash }, + MultisigState::Completed { .. } => { + let axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)> = multisig_session.signers + .iter() + .filter(|(_, signature)| signature.is_some()) + .map(|(signer, signature)| (signer.clone(), signature.clone().unwrap())) + .collect(); + + let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers); + let tx_blob: HexBinary = HexBinary::from(xrpl_multisig::serialize_signed_tx(signed_tx)?); + GetProofResponse::Completed { unsigned_tx_hash, tx_blob } + } + }; + + Ok(response) +} diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs new file mode 100644 index 000000000..e1a6844f5 --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -0,0 +1,41 @@ +use cosmwasm_std::{from_binary, DepsMut, Reply, Response, Uint64}; +use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; + +use crate::{ + error::ContractError, + events::Event, + state::{MULTISIG_SESSION_TX, REPLY_TX_HASH}, +}; + +pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { + match parse_reply_execute_data(reply) { + Ok(MsgExecuteContractResponse { data: Some(data) }) => { + let tx_hash = REPLY_TX_HASH.load(deps.storage)?; + + let multisig_session_id: Uint64 = + from_binary(&data).map_err(|_| ContractError::InvalidContractReply { + reason: "invalid multisig session ID".to_string(), + })?; + + MULTISIG_SESSION_TX.save( + deps.storage, + multisig_session_id.u64(), + &tx_hash, + )?; + + Ok(Response::new().add_event( + Event::ProofUnderConstruction { + tx_hash, + multisig_session_id, + } + .into(), + )) + } + Ok(MsgExecuteContractResponse { data: None }) => Err(ContractError::InvalidContractReply { + reason: "no data".to_string(), + }), + Err(_) => { + unreachable!("violated invariant: replied failed submessage with ReplyOn::Success") + } + } +} diff --git a/contracts/xrpl-multisig-prover/src/state.rs b/contracts/xrpl-multisig-prover/src/state.rs new file mode 100644 index 000000000..cbef70824 --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/state.rs @@ -0,0 +1,45 @@ +use axelar_wasm_std::Threshold; +use connection_router::state::CrossChainId; +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; +use cosmwasm_schema::cw_serde; +use crate::{types::{TransactionInfo, TxHash, XRPLToken}, axelar_workers::WorkerSet}; + +#[cw_serde] +pub struct Config { + pub axelar_multisig_address: Addr, + pub gateway_address: Addr, + pub signing_threshold: Threshold, + pub xrpl_multisig_address: Addr, + pub voting_verifier_address: Addr, + pub service_registry_address: Addr, + pub service_name: String, + pub worker_set_diff_threshold: u32, + pub xrpl_fee: u64, + pub ticket_count_threshold: u32, +} + +pub const CONFIG: Item = Item::new("config"); +pub const REPLY_TX_HASH: Item = Item::new("reply_tx_hash"); +pub const MULTISIG_SESSION_TX: Map = Map::new("multisig_session_tx"); + +// The next seq. no. is determined on TicketCreate and depends on the number of created tickets, +// not solely on the last sequence number used. +// On the contrary, the next ticket number to be used cannot be determined before proof construction, +// as it depends on the tickets available at the time. +// After all ticket numbers are used, we reuse the smallest available ticket number, +// going over all ticket numbers again, wrapping around. +pub const NEXT_SEQUENCE_NUMBER: Item = Item::new("next_sequence_number"); +pub const LAST_ASSIGNED_TICKET_NUMBER: Item = Item::new("last_assigned_ticket_number"); + +pub const SIGNED_TO_UNSIGNED_TX_HASH: Map = Map::new("signed_to_unsigned_tx_hash"); +pub const MESSAGE_ID_TO_TICKET: Map = Map::new("message_id_to_ticket"); +pub const CONFIRMED_TRANSACTIONS: Map = Map::new("confirmed_transactions"); +pub const AVAILABLE_TICKETS: Item> = Item::new("available_tickets"); +pub const TRANSACTION_INFO: Map = Map::new("transaction_info"); +pub const LATEST_SEQUENTIAL_TX_HASH: Item = Item::new("latest_sequential_tx_hash"); + +pub const TOKENS: Map = Map::new("tokens"); + +pub const CURRENT_WORKER_SET: Item = Item::new("current_worker_set"); +pub const NEXT_WORKER_SET: Map = Map::new("next_worker_set"); diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs new file mode 100644 index 000000000..d733f7185 --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -0,0 +1,113 @@ +use std::ops::Deref; + +use connection_router::state::CrossChainId; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256}; +use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; +use multisig::key::Signature; +use xrpl_voting_verifier::execute::MessageStatus; + +use crate::{xrpl_multisig::XRPLUnsignedTx, error::ContractError}; + +#[cw_serde] +pub enum TransactionStatus { + Pending, + Succeeded, + FailedOnChain, + FailedOffChain, +} + +#[cw_serde] +pub struct TxHash(pub HexBinary); + +impl TryFrom for TxHash { + type Error = ContractError; + fn try_from(cc_id: CrossChainId) -> Result { + // TODO check this is correct + Ok(Self(HexBinary::from_hex(cc_id.id.deref().as_str())?)) + } +} + +impl Into for TxHash { + fn into(self) -> HexBinary { + self.0 + } +} + +impl Into for MessageStatus { + fn into(self) -> TransactionStatus { + match self { + MessageStatus::Succeeded => TransactionStatus::Succeeded, + MessageStatus::FailedOnChain => TransactionStatus::FailedOnChain, + MessageStatus::FailedOffChain => TransactionStatus::FailedOffChain, + } + } +} + +#[cw_serde] +pub struct TransactionInfo { + pub status: TransactionStatus, + // TODO: save only the hash of the unsigned tx + pub unsigned_contents: XRPLUnsignedTx, + // TODO: rename: original_message_id or similar, the message id that triggered this tx + pub message_id: Option, +} + +impl From for TxHash { + fn from(id: HexBinary) -> Self { + Self(id) + } +} + +impl From<&[u8]> for TxHash { + fn from(id: &[u8]) -> Self { + Self(id.into()) + } +} + +impl<'a> PrimaryKey<'a> for TxHash { + type Prefix = (); + type SubPrefix = (); + type Suffix = TxHash; + type SuperSuffix = TxHash; + + fn key(&self) -> Vec { + vec![Key::Ref(self.0.as_slice())] + } +} + +impl KeyDeserialize for TxHash { + type Output = TxHash; + + fn from_vec(value: Vec) -> StdResult { + Ok(from_binary(&value.into()).expect("violated invariant: TxHash is not deserializable")) + } +} + +#[cw_serde] +#[derive(Ord, PartialOrd, Eq)] +pub struct Operator { + pub address: HexBinary, + pub weight: Uint256, + pub signature: Option, +} + +impl Operator { + pub fn with_signature(self, sig: Signature) -> Operator { + Operator { + address: self.address, + weight: self.weight, + signature: Some(sig), + } + } +} + +#[cw_serde] +pub struct XRPLToken { + pub issuer: String, + pub currency: String, +} + +impl XRPLToken { + pub const NATIVE_CURRENCY: &str = "XRP"; +} diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs new file mode 100644 index 000000000..5af68bc0f --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -0,0 +1,857 @@ +use std::collections::BTreeSet; + +use axelar_wasm_std::nonempty; +use connection_router::state::CrossChainId; +use cosmwasm_schema::{cw_serde, serde::Serializer}; +use cosmwasm_std::{Storage, HexBinary}; +use ripemd::Ripemd160; +use sha2::{Sha512, Digest, Sha256}; + +use crate::{ + error::ContractError, + state::{Config, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS, TRANSACTION_INFO, NEXT_SEQUENCE_NUMBER, CONFIRMED_TRANSACTIONS, MESSAGE_ID_TO_TICKET, LATEST_SEQUENTIAL_TX_HASH, SIGNED_TO_UNSIGNED_TX_HASH}, + types::*, axelar_workers::{WorkerSet, AxelarSigner}, +}; + +fn itoa_serialize(x: &u64, s: S) -> Result +where + S: Serializer, +{ + s.serialize_str(&x.to_string()[..]) +} + +#[cw_serde] +pub struct XRPLTokenAmount(pub String); + +#[cw_serde] +#[serde(untagged)] +pub enum XRPLPaymentAmount { + Drops( + #[serde(serialize_with = "itoa_serialize")] + u64, + ), + Token(XRPLToken, XRPLTokenAmount), +} + +#[cw_serde] +#[serde(untagged)] +pub enum Sequence { + Plain(u32), + Ticket(u32), +} + +impl Into for Sequence { + fn into(self) -> u32 { + match self { + Sequence::Plain(sequence) => sequence, + Sequence::Ticket(ticket) => ticket, + } + } +} + +#[cw_serde] +#[serde(rename_all = "PascalCase")] +pub struct XRPLTxCommonFields { + pub account: String, // TODO: redundant here? + #[serde(serialize_with = "itoa_serialize")] + pub fee: u64, + pub sequence: Sequence, + pub signing_pub_key: String, +} + +#[cw_serde] +#[serde(rename_all = "PascalCase", tag = "SignerEntry")] +pub struct XRPLSignerEntry { + pub account: String, + pub signer_weight: u16, +} + +#[cw_serde] +#[serde(rename_all = "PascalCase")] +pub struct XRPLUnsignedTx { + #[serde(flatten)] + pub common: XRPLTxCommonFields, + #[serde(flatten)] + pub partial: XRPLPartialTx, +} + +#[cw_serde] +#[serde(tag="TransactionType")] +pub enum XRPLPartialTx { + Payment { + amount: XRPLPaymentAmount, + destination: nonempty::String, + }, + SignerListSet { + signer_quorum: u32, + signer_entries: Vec, + }, + TicketCreate { + ticket_count: u32, + }, +} + +impl XRPLUnsignedTx { + pub fn sequence_number_increment(&self, status: TransactionStatus) -> u32 { + if status == TransactionStatus::Pending || status == TransactionStatus::FailedOffChain { + return 0; + } + + match self.partial { + XRPLPartialTx::Payment { .. } | + XRPLPartialTx::SignerListSet { .. } => { + match self.common.sequence { + Sequence::Plain(_) => 1, + Sequence::Ticket(_) => 0, + } + }, + XRPLPartialTx::TicketCreate { ticket_count } => { + match status { + TransactionStatus::Succeeded => ticket_count + 1, + TransactionStatus::FailedOnChain => 1, + TransactionStatus::FailedOffChain | + TransactionStatus::Pending => unreachable!(), + } + }, + } + } +} + +#[cw_serde] +#[serde(rename_all = "PascalCase")] +pub struct XRPLSigner { + pub account: String, + pub txn_signature: HexBinary, + pub signing_pub_key: HexBinary, +} + +#[cw_serde] +#[serde(rename_all = "PascalCase")] +pub struct XRPLSignedTransaction { + #[serde(flatten)] + pub unsigned_tx: XRPLUnsignedTx, + pub signers: Vec, +} + +pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { + let last_assigned_ticket_number = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; + let available_tickets = AVAILABLE_TICKETS.load(storage)?; + + // find next largest in available, otherwise use available_tickets[0] + let ticket_number = available_tickets.iter().find(|&x| x > &last_assigned_ticket_number).unwrap_or(&available_tickets[0]); + Ok(*ticket_number) +} + +pub fn available_ticket_count(storage: &mut dyn Storage) -> Result { + let available_tickets = AVAILABLE_TICKETS.load(storage)?; + let ticket_count = 250 - (available_tickets.len() as u32); + Ok(ticket_count) +} + +fn construct_unsigned_tx( + config: &Config, + partial_unsigned_tx: XRPLPartialTx, + sequence: Sequence, +) -> XRPLUnsignedTx { + let unsigned_tx_common = XRPLTxCommonFields { + account: config.xrpl_multisig_address.to_string(), + fee: config.xrpl_fee, + sequence: sequence.clone(), + signing_pub_key: "".to_string(), + }; + + XRPLUnsignedTx { + common: unsigned_tx_common, + partial: partial_unsigned_tx, + } +} + +const PAYMENT_TYPE: u16 = 0; +const UINT16_TYPE_CODE: u8 = 1; +const UINT32_TYPE_CODE: u8 = 2; +const AMOUNT_TYPE_CODE: u8 = 6; +const BLOB_TYPE_CODE: u8 = 7; +const ACCOUNT_ID_TYPE_CODE: u8 = 8; +const OBJECT_TYPE_CODE: u8 = 14; +const ARRAY_TYPE_CODE: u8 = 15; + +// field ids and type codes from here +// https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/enums/definitions.json +pub fn field_id(type_code: u8, field_code: u8) -> Vec { + assert!(type_code < 16); + if field_code < 16 { + vec![type_code << 4 | field_code] + } else { + vec![type_code << 4, field_code] + } +} + +pub fn serialize_uint16(field_code: u8, value: u16) -> Vec { + let mut result = Vec::new(); + result.extend(field_id(UINT16_TYPE_CODE, field_code)); + result.extend(&value.to_be_bytes()); + result +} + +pub fn serialize_uint32(field_code: u8, value: u32) -> Vec { + let mut result = Vec::new(); + result.extend(field_id(UINT32_TYPE_CODE, field_code)); + result.extend(&value.to_be_bytes()); + result +} + +const POSITIVE_BIT: u64 = 0x4000000000000000; + +const MIN_MANTISSA: u128 = 1_000_000_000_000_000; +const MAX_MANTISSA: u128 = 10_000_000_000_000_000 - 1; +const MIN_EXPONENT: i32 = -96; +const MAX_EXPONENT: i32 = 80; + +// TODO: trim whitespace? +fn parse_decimal(s: &str) -> Result<(bool, u64, u64), ContractError> { + if s.is_empty() { + return Err(ContractError::InvalidAmount); + } + + let sign = s.starts_with('-'); + let trimmed = if s.starts_with('-') || s.starts_with('+') { &s[1..] } else { s }; + + let parts: Vec<_> = trimmed.split('.').collect(); + + if parts.len() > 2 { + return Err(ContractError::InvalidAmount); + } + + let integer_part = match parts[0].parse::() { + Ok(num) => num, + Err(_) => return Err(ContractError::InvalidAmount), + }; + + let fractional_part = if parts.len() == 2 { + match parts[1].parse::() { + Ok(num) => num, + Err(_) => return Err(ContractError::InvalidAmount), + } + } else { + 0 + }; + + Ok((sign, integer_part, fractional_part)) +} + +// see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 +pub fn amount_to_bytes(amount: XRPLTokenAmount) -> Result, ContractError> { + let (is_negative, integer_part, fractional_part) = parse_decimal(amount.0.trim())?; + + let mut serial: u64 = 0x8000000000000000; + if integer_part == 0 && fractional_part == 0 { + return Ok(Vec::from(serial.to_be_bytes())) + } + + let mut exponent: i32 = fractional_part.to_string().len() as i32; + let mut mantissa: u128 = (integer_part as u128) * 10u128.pow(exponent as u32) + (fractional_part as u128); + + while mantissa < MIN_MANTISSA && exponent > MIN_EXPONENT { + mantissa *= 10; + exponent -= 1; + } + + /* + TODO: Discuss with Ripple team, the below is part of the reference implementation + https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/STAmount.cpp#L795 + However it leads to least significant digits being dropped off from the mantissa */ + while mantissa > MAX_MANTISSA && exponent > MIN_EXPONENT { + if exponent > MAX_EXPONENT { + return Err(ContractError::InvalidAmount); + } + mantissa /= 10; + exponent += 1; + } + + if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA { + return Ok(Vec::from(serial.to_be_bytes())); + } + + if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA { + return Err(ContractError::InvalidAmount); + } + + if !is_negative { + serial |= 0x4000000000000000; // set positive bit + } + + serial |= ((exponent+97) as u64) << 54; // next 8 bits are exponent + + serial |= mantissa as u64; // last 54 bits are mantissa + + Ok(Vec::from(serial.to_be_bytes())) +} + + +pub fn currency_to_bytes(currency: String) -> Result<[u8; 20], ContractError> { + if currency.len() != 3 || !currency.is_ascii() { + return Err(ContractError::InvalidCurrency); + } + let mut buffer = [0u8; 20]; + buffer[12..15].copy_from_slice(currency.as_bytes()); + Ok(buffer) +} + +pub fn decode_address(address: String) -> Result<[u8; 20], ContractError> { + let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); + // .map_err(|_| ContractError::InvalidAddress)?; + println!("decoded {:?} {}", res, res.len()); + if res.len() != 25 { + return Err(ContractError::InvalidAddress); + } + let mut buffer = [0u8; 20]; + buffer.copy_from_slice(&res[1..21]); + return Ok(buffer) +} + +pub fn serialize_amount(field_code: u8, amount: XRPLPaymentAmount) -> Result, ContractError> { + let mut result = Vec::new(); + println!("amount field id {} {} {}", AMOUNT_TYPE_CODE, field_code, hex::encode(field_id(AMOUNT_TYPE_CODE, field_code))); + result.extend(field_id(AMOUNT_TYPE_CODE, field_code)); + match amount { + XRPLPaymentAmount::Drops(value) => { + // assert!(value >= 0); + assert!(value <= 10u64.pow(17)); + result.extend((value | POSITIVE_BIT).to_be_bytes()); + }, + XRPLPaymentAmount::Token(token, amount) => { + result.extend(amount_to_bytes(amount)?); + result.extend(currency_to_bytes(token.currency)?); + result.extend(decode_address(token.issuer)?); + } + } + Ok(result) +} + +// see https://github.com/XRPLF/xrpl-dev-portal/blob/master/content/_code-samples/tx-serialization/py/serialize.py#L92 +// returns None if length too big +pub fn encode_length(mut length: usize) -> Option> { + if length <= 192 { + return Some(vec![length as u8]); + } else if length <= 12480 { + length -= 193; + return Some(vec![(length >> 8) as u8, (length & 0xff) as u8]); + } else if length <= 918744 { + length -= 12481; + return Some(vec![ + 241 + (length >> 16) as u8, + ((length >> 8) & 0xff) as u8, + (length & 0xff) as u8 + ]) + } else { + return None + } +} + +pub fn serialize_blob(field_code: u8, blob: Vec) -> Option> { + let mut result: Vec = Vec::new(); + result.extend(field_id(BLOB_TYPE_CODE, field_code)); + result.extend(encode_length(blob.len())?); + result.extend(blob); + Some(result) +} + +pub fn serialize_signer(signer: &XRPLSigner) -> Result, ContractError> { + let mut results: Vec> = Vec::new(); + results.push(field_id(OBJECT_TYPE_CODE, 16)); + results.push(serialize_blob(3, signer.signing_pub_key.clone().as_slice().to_vec()).ok_or(ContractError::InvalidSigningPubKey)?); + results.push(serialize_blob(4, signer.txn_signature.clone().as_slice().to_vec()).ok_or(ContractError::InvalidSignature)?); + results.push(serialize_account_id(1, signer.account.clone())?); + results.push(field_id(OBJECT_TYPE_CODE, 1)); + println!("signer hex parts {}", results.iter().map(|x| hex::encode(x)).collect::>().join(",")); + Ok(results.concat()) +} + +pub fn serialize_signers_array(field_code: u8, signers: Vec) -> Result, ContractError> { + let mut result: Vec = Vec::new(); + result.extend(field_id(ARRAY_TYPE_CODE, field_code)); + for signer in &signers { + result.extend(serialize_signer(signer)?); + } + result.extend(field_id(ARRAY_TYPE_CODE, 1)); + Ok(result) +} + +pub fn serialize_account_id(field_code: u8, account_id: String) -> Result, ContractError> { + let mut result: Vec = Vec::new(); + result.extend(field_id(ACCOUNT_ID_TYPE_CODE, field_code)); + result.extend(vec![20]); + result.extend(decode_address(account_id)?); + Ok(result) +} + +// TODO: each worker must append his decoded address to the serialized tx before signing +pub fn serialize_unsigned_payment_tx(common: XRPLTxCommonFields, amount: XRPLPaymentAmount, destination: nonempty::String) -> Result, ContractError> { + let mut results: Vec> = Vec::new(); + // value: 0, type:uint16, type_code: 1, nth: 2, !isVLEncoded + // serialize_transaction_payment_type(); + results.push(Vec::from((0x534D5400 as u32).to_be_bytes())); // prefix for multisignature signing + results.push(serialize_uint16(2, PAYMENT_TYPE)); + results.push(serialize_uint32(2, 0)); + match common.sequence { + Sequence::Plain(seq) => { + // type: Uint32, type_code: 2, nth: 4, !isVLEncoded + // serialize_sequence(seq); + results.push(serialize_uint32(4, seq)); + }, + Sequence::Ticket(seq) => { + // type: Uint32, type_code: 2, nth: 4, !isVLEncoded + // serialize_sequence(seq) + results.push(serialize_uint32(4, 0)); + // type: Uint32, type_code: 2, nth: 41, !isVLEncoded + // serialize_ticket_sequence(seq); + results.push(serialize_uint32(41, seq)); + } + }; + // type: Amount, type_code: 6, nth: 1, !isVLEncoded + results.push(serialize_amount(1, amount)?); + // type: Amount, type_code: 6, nth: 8, !isVLEncoded + results.push(serialize_amount(8, XRPLPaymentAmount::Drops(common.fee))?); + // type: Blob, type_code: 7, nth: 3, isVLEncoded + // TODO: hex::encode to slice? + results.push(serialize_blob(3, hex::decode("").unwrap()).unwrap()); + // type: AccountId, type_code: 8, nth: 1, isVLEncoded + results.push(serialize_account_id(1, common.account)?); + // type: AccountId, type_code: 8, nth:3, isVLEncoded + results.push(serialize_account_id(3, destination.into())?); + + println!("hex parts {}", results.iter().map(|x| hex::encode(x)).collect::>().join(",")); + Ok(results.concat()) +} + +pub fn serialize_unsigned_tx(tx: XRPLUnsignedTx) -> Result, ContractError> { + match tx.partial { + XRPLPartialTx::Payment { amount, destination } => serialize_unsigned_payment_tx(tx.common, amount, destination), + _ => unimplemented!() + } +} + +// TODO: each worker must append his decoded address to the serialized tx before signing +pub fn serialize_signed_payment_tx(signers: Vec, common: XRPLTxCommonFields, amount: XRPLPaymentAmount, destination: nonempty::String) -> Result, ContractError> { + let mut results: Vec> = Vec::new(); + // value: 0, type:uint16, type_code: 1, nth: 2, !isVLEncoded + // serialize_transaction_payment_type(); + results.push(serialize_uint16(2, PAYMENT_TYPE)); + results.push(serialize_uint32(2, 0)); + match common.sequence { + Sequence::Plain(seq) => { + // type: Uint32, type_code: 2, nth: 4, !isVLEncoded + // serialize_sequence(seq); + results.push(serialize_uint32(4, seq)); + }, + Sequence::Ticket(seq) => { + // type: Uint32, type_code: 2, nth: 4, !isVLEncoded + // serialize_sequence(seq) + results.push(serialize_uint32(4, 0)); + // type: Uint32, type_code: 2, nth: 41, !isVLEncoded + // serialize_ticket_sequence(seq); + results.push(serialize_uint32(41, seq)); + } + }; + // type: Amount, type_code: 6, nth: 1, !isVLEncoded + results.push(serialize_amount(1, amount)?); + // type: Amount, type_code: 6, nth: 8, !isVLEncoded + results.push(serialize_amount(8, XRPLPaymentAmount::Drops(common.fee))?); + // type: Blob, type_code: 7, nth: 3, isVLEncoded + // TODO: hex::encode to slice? + results.push(serialize_blob(3, hex::decode("").unwrap()).unwrap()); + // type: AccountId, type_code: 8, nth: 1, isVLEncoded + results.push(serialize_account_id(1, common.account)?); + // type: AccountId, type_code: 8, nth:3, isVLEncoded + results.push(serialize_account_id(3, destination.into())?); + // type: STArray, type_code: 14, nth:3, !isVLEncoded + results.push(serialize_signers_array(3, signers)?); + + println!("hex parts {}", results.iter().map(|x| hex::encode(x)).collect::>().join(",")); + Ok(results.concat()) +} + +pub fn serialize_signed_tx(signed_tx: XRPLSignedTransaction) -> Result, ContractError> { + match signed_tx.unsigned_tx.partial { + XRPLPartialTx::Payment { amount, destination } => serialize_signed_payment_tx(signed_tx.signers, signed_tx.unsigned_tx.common, amount, destination), + _ => unimplemented!() + } +} + +// TODO: optimize +pub fn compute_unsigned_tx_hash(unsigned_tx: XRPLUnsignedTx) -> Result { + let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx)?; + + let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI, encoded_unsigned_tx.as_slice())); + let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); + Ok(tx_hash) +} + +pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result { + let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())); + let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); + Ok(tx_hash) +} + +pub const HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; +pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; + +pub fn xrpl_hash( + prefix: [u8; 4], + tx_blob: &[u8], +) -> [u8; 32] { + let mut hasher = Sha512::new_with_prefix(prefix); + hasher.update(tx_blob); + let hash: [u8; 64] = hasher.finalize().into(); + let mut half_hash: [u8; 32] = [0; 32]; + half_hash.copy_from_slice(&hash[..32]); + half_hash +} + +fn issue_tx( + storage: &mut dyn Storage, + config: &Config, + partial_unsigned_tx: XRPLPartialTx, + sequence: Sequence, + message_id: Option, +) -> Result { + let unsigned_tx = construct_unsigned_tx( + config, + partial_unsigned_tx, + sequence.clone(), + ); + + let tx_hash = compute_unsigned_tx_hash(unsigned_tx.clone())?; + + TRANSACTION_INFO.save( + storage, + tx_hash.clone(), + &TransactionInfo { + status: TransactionStatus::Pending, + unsigned_contents: unsigned_tx.clone(), + message_id, + } + )?; + + match sequence { + Sequence::Ticket(ticket_number) => { + LAST_ASSIGNED_TICKET_NUMBER.save(storage, &ticket_number)?; + }, + Sequence::Plain(_) => { + LATEST_SEQUENTIAL_TX_HASH.save(storage, &tx_hash)?; + }, + }; + + Ok(tx_hash) +} + +fn get_next_sequence_number(storage: &dyn Storage) -> Result { + let latest_sequential_tx_info = load_latest_sequential_tx_info(storage)?; + let sequence_number = if latest_sequential_tx_info.status == TransactionStatus::Pending { + latest_sequential_tx_info.unsigned_contents.common.sequence.clone().into() + } else { + NEXT_SEQUENCE_NUMBER.load(storage)? + }; + Ok(sequence_number) +} + +pub fn issue_ticket_create(storage: &mut dyn Storage, config: &Config, ticket_count: u32) -> Result { + let partial_unsigned_tx = XRPLPartialTx::TicketCreate { + ticket_count, + }; + + let sequence_number = get_next_sequence_number(storage)?; + + let tx_hash = issue_tx( + storage, + config, + partial_unsigned_tx, + Sequence::Plain(sequence_number), + None, + )?; + + Ok(tx_hash) +} + +fn load_latest_sequential_tx_info( + storage: &dyn Storage, +) -> Result { + let latest_sequential_tx_hash = LATEST_SEQUENTIAL_TX_HASH.load(storage)?; + Ok(TRANSACTION_INFO.load(storage, latest_sequential_tx_hash.clone())?) +} + +pub fn issue_payment( + storage: &mut dyn Storage, + config: &Config, + destination: nonempty::String, + amount: XRPLPaymentAmount, + message_id: CrossChainId, +) -> Result { + let partial_unsigned_tx = XRPLPartialTx::Payment { + destination, + amount, + }; + + let ticket_number = assign_ticket_number(storage, message_id.clone())?; + + issue_tx( + storage, + config, + partial_unsigned_tx, + Sequence::Ticket(ticket_number), + Some(message_id), + ) +} + +pub fn public_key_to_xrpl_address(public_key: multisig::key::PublicKey) -> String { + let public_key_hex: HexBinary = public_key.into(); + + assert!(public_key_hex.len() == 33); + + let public_key_inner_hash = Sha256::digest(public_key_hex); + let account_id = Ripemd160::digest(public_key_inner_hash); + + let address_type_prefix: &[u8] = &[0x00]; + let payload = [address_type_prefix, &account_id].concat(); + + let checksum_hash1 = Sha256::digest(payload.clone()); + let checksum_hash2 = Sha256::digest(checksum_hash1); + let checksum = &checksum_hash2[0..4]; + + bs58::encode([payload, checksum.to_vec()].concat()) + .with_alphabet(bs58::Alphabet::RIPPLE) + .into_string() +} + +pub fn make_xrpl_signer_entries(signers: BTreeSet) -> Vec { + signers + .into_iter() + .map( + |worker| { + XRPLSignerEntry { + account: public_key_to_xrpl_address(worker.pub_key), + signer_weight: worker.weight, + } + } + ).collect() +} + +pub fn issue_signer_list_set(storage: &mut dyn Storage, config: &Config, workers: WorkerSet) -> Result { + let partial_unsigned_tx = XRPLPartialTx::SignerListSet { + signer_quorum: workers.quorum, + signer_entries: make_xrpl_signer_entries(workers.signers), + }; + + let sequence_number = get_next_sequence_number(storage)?; + issue_tx( + storage, + config, + partial_unsigned_tx, + Sequence::Plain(sequence_number), + None, + ) +} + +fn mark_tickets_available(storage: &mut dyn Storage, tickets: impl Iterator) -> Result<(), ContractError> { + AVAILABLE_TICKETS.update(storage, |available_tickets| -> Result<_, ContractError> { + let mut new_available_tickets = available_tickets.clone(); + for i in tickets { + new_available_tickets.push(i); + } + + Ok(new_available_tickets) + })?; + Ok(()) +} + +fn mark_ticket_unavailable(storage: &mut dyn Storage, ticket: u32) -> Result<(), ContractError> { + AVAILABLE_TICKETS.update(storage, |available_tickets| -> Result<_, ContractError> { + Ok(available_tickets + .into_iter() + .filter(|&x| x != ticket) + .collect()) + })?; + Ok(()) +} + +pub fn update_tx_status(storage: &mut dyn Storage, tx_hash: TxHash, new_status: TransactionStatus) -> Result<(), ContractError> { + let unsigned_tx_hash = SIGNED_TO_UNSIGNED_TX_HASH.load(storage, tx_hash.clone())?; + let mut tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; + if tx_info.status != TransactionStatus::Pending { + return Err(ContractError::TransactionStatusAlreadyUpdated); + } + + tx_info.status = new_status.clone(); + + let tx_sequence_number: u32 = tx_info.unsigned_contents.common.sequence.clone().into(); + if let XRPLPartialTx::TicketCreate { ticket_count } = tx_info.unsigned_contents.partial { + if tx_info.status == TransactionStatus::Succeeded { + mark_tickets_available( + storage, + (tx_sequence_number + 1)..(tx_sequence_number + ticket_count), + )?; + } + } + + let sequence_number_increment = tx_info.unsigned_contents.sequence_number_increment(new_status.clone()); + if sequence_number_increment > 0 && tx_sequence_number == NEXT_SEQUENCE_NUMBER.load(storage)? { + NEXT_SEQUENCE_NUMBER.save(storage, &(tx_sequence_number + sequence_number_increment))?; + } + + if new_status == TransactionStatus::Succeeded || new_status == TransactionStatus::FailedOnChain { + CONFIRMED_TRANSACTIONS.save(storage, tx_sequence_number, &unsigned_tx_hash)?; + mark_ticket_unavailable(storage, tx_sequence_number)?; + } + + TRANSACTION_INFO.save(storage, unsigned_tx_hash, &tx_info)?; + Ok(()) +} + +// A message ID can be ticketed a different ticket number +// only if the previous ticket number has been consumed +// by a TX that doesn't correspond to this message. +pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: CrossChainId) -> Result { + // If this message ID has already been ticketed, + // then use the same ticket number as before, + if let Some(ticket_number) = MESSAGE_ID_TO_TICKET.may_load(storage, message_id.clone())? { + // as long as it has not already been consumed + let confirmed_tx_hash = CONFIRMED_TRANSACTIONS.may_load(storage, ticket_number)?; + if confirmed_tx_hash.is_none() { + return Ok(ticket_number) + } + + // or if it has been consumed by the same transactions. + let tx_info = TRANSACTION_INFO.load(storage, confirmed_tx_hash.unwrap())?; + if tx_info.message_id.map_or(false, |id| id == message_id) { + return Ok(ticket_number) + } + } + + // Otherwise, use the next available ticket number. + let new_ticket_number = get_next_ticket_number(storage)?; + MESSAGE_ID_TO_TICKET.save(storage, message_id, &new_ticket_number)?; + Ok(new_ticket_number) +} + +#[cfg(test)] +mod tests { + use multisig::key::PublicKey; + + use super::*; + + #[test] + fn serialize_xrpl_unsigned_token_payment_transaction() { + let unsigned_tx = XRPLUnsignedTx { + common: XRPLTxCommonFields { + account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + fee: 12, + sequence: Sequence::Plain(1), + signing_pub_key: "".to_string(), + }, + partial: XRPLPartialTx::Payment { + amount: XRPLPaymentAmount::Token( + XRPLToken { + currency: "JPY".to_string(), + issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".to_string(), + }, + XRPLTokenAmount("0.3369568318".to_string()), + ), + destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), + } + }; + let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx).unwrap(); + assert_eq!( + "534D54001200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", + hex::encode_upper(encoded_unsigned_tx) + ); + } + + #[test] + fn serialize_xrpl_unsigned_xrp_payment_transaction() { + let unsigned_tx = XRPLUnsignedTx { + common: XRPLTxCommonFields { + account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + fee: 10, + sequence: Sequence::Plain(1), + signing_pub_key: "".to_string(), + }, + partial: XRPLPartialTx::Payment { + amount: XRPLPaymentAmount::Drops(1000), + destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), + } + }; + let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx).unwrap(); + assert_eq!( + "534D5400120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", + hex::encode_upper(encoded_unsigned_tx) + ); + + let unsigned_tx = XRPLUnsignedTx { + common: XRPLTxCommonFields { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + fee: 3, + sequence: Sequence::Plain(43497363), + signing_pub_key: "".to_string(), + }, + partial: XRPLPartialTx::Payment { + amount: XRPLPaymentAmount::Drops(1000000000), + destination: nonempty::String::try_from("rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq").unwrap(), + } + }; + let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx).unwrap(); + assert_eq!( + "534D54001200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6", + hex::encode_upper(encoded_unsigned_tx) + ); + } + + #[test] + fn serialize_xrpl_signed_xrp_payment_transaction() { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx { + common: XRPLTxCommonFields { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + fee: 30, + sequence: Sequence::Plain(43497365), + signing_pub_key: "".to_string(), + }, + partial: XRPLPartialTx::Payment { + amount: XRPLPaymentAmount::Drops(1000000000), + destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), + } + }, signers: vec![ + XRPLSigner{ + account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), + txn_signature: HexBinary::from(hex::decode("00CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9").unwrap()), + }, + XRPLSigner{ + account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), + txn_signature: HexBinary::from(hex::decode("62B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE02").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), + } + ] + }; + let encoded_signed_tx = serialize_signed_tx(signed_tx).unwrap(); + assert_eq!( + "1200002200000000240297B79561400000003B9ACA0068400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689A831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9744000CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744062B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE028114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", + hex::encode_upper(encoded_signed_tx) + ); + } + + #[test] + fn ed25519_public_key_to_xrpl_address() { + assert_eq!( + public_key_to_xrpl_address(PublicKey::Ed25519(HexBinary::from(hex::decode("ED9434799226374926EDA3B54B1B461B4ABF7237962EAE18528FEA67595397FA32").unwrap()))), + "rDTXLQ7ZKZVKz33zJbHjgVShjsBnqMBhmN" + ); + } + + #[test] + fn secp256k1_public_key_to_xrpl_address() { + assert_eq!( + public_key_to_xrpl_address(PublicKey::Ecdsa(HexBinary::from(hex::decode("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D").unwrap()))), + "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H" + ); + } +} From d2ec76f3ff0f79d4e77a5c8b582f7fa42d037676 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Fri, 29 Dec 2023 18:09:31 +0200 Subject: [PATCH 06/80] xrpl serialiation for ticket create and signerlistset --- contracts/xrpl-multisig-prover/src/error.rs | 4 + contracts/xrpl-multisig-prover/src/query.rs | 2 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 431 +++++++++++------- 3 files changed, 267 insertions(+), 170 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index d9fd4ed6e..743f251b9 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -70,6 +70,10 @@ pub enum ContractError { #[error("signing session not completed")] SigningSessionNotCompleted, + + #[error("invalid blob")] + InvalidBlob, + } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 736246c24..9cffad7ba 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -57,7 +57,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & .collect(); let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers); - let tx_blob: HexBinary = HexBinary::from(xrpl_multisig::serialize_signed_tx(signed_tx)?); + let tx_blob: HexBinary = HexBinary::from(xrpl_multisig::serialize_signed_tx(&signed_tx)?); GetProofResponse::Completed { unsigned_tx_hash, tx_blob } } }; diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 5af68bc0f..4d2b502d1 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -166,7 +166,10 @@ fn construct_unsigned_tx( } } -const PAYMENT_TYPE: u16 = 0; +const PAYMENT_TX_TYPE: u16 = 0; +const TICKET_CREATE_TX_TYPE: u16 = 10; +const SIGNER_LIST_SET_TX_TYPE: u16 = 12; + const UINT16_TYPE_CODE: u8 = 1; const UINT32_TYPE_CODE: u8 = 2; const AMOUNT_TYPE_CODE: u8 = 6; @@ -186,20 +189,6 @@ pub fn field_id(type_code: u8, field_code: u8) -> Vec { } } -pub fn serialize_uint16(field_code: u8, value: u16) -> Vec { - let mut result = Vec::new(); - result.extend(field_id(UINT16_TYPE_CODE, field_code)); - result.extend(&value.to_be_bytes()); - result -} - -pub fn serialize_uint32(field_code: u8, value: u32) -> Vec { - let mut result = Vec::new(); - result.extend(field_id(UINT32_TYPE_CODE, field_code)); - result.extend(&value.to_be_bytes()); - result -} - const POSITIVE_BIT: u64 = 0x4000000000000000; const MIN_MANTISSA: u128 = 1_000_000_000_000_000; @@ -240,7 +229,7 @@ fn parse_decimal(s: &str) -> Result<(bool, u64, u64), ContractError> { } // see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 -pub fn amount_to_bytes(amount: XRPLTokenAmount) -> Result, ContractError> { +pub fn amount_to_bytes(amount: &XRPLTokenAmount) -> Result, ContractError> { let (is_negative, integer_part, fractional_part) = parse_decimal(amount.0.trim())?; let mut serial: u64 = 0x8000000000000000; @@ -288,7 +277,7 @@ pub fn amount_to_bytes(amount: XRPLTokenAmount) -> Result, ContractError } -pub fn currency_to_bytes(currency: String) -> Result<[u8; 20], ContractError> { +pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { if currency.len() != 3 || !currency.is_ascii() { return Err(ContractError::InvalidCurrency); } @@ -297,7 +286,7 @@ pub fn currency_to_bytes(currency: String) -> Result<[u8; 20], ContractError> { Ok(buffer) } -pub fn decode_address(address: String) -> Result<[u8; 20], ContractError> { +pub fn decode_address(address: &String) -> Result<[u8; 20], ContractError> { let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); // .map_err(|_| ContractError::InvalidAddress)?; println!("decoded {:?} {}", res, res.len()); @@ -309,25 +298,160 @@ pub fn decode_address(address: String) -> Result<[u8; 20], ContractError> { return Ok(buffer) } -pub fn serialize_amount(field_code: u8, amount: XRPLPaymentAmount) -> Result, ContractError> { - let mut result = Vec::new(); - println!("amount field id {} {} {}", AMOUNT_TYPE_CODE, field_code, hex::encode(field_id(AMOUNT_TYPE_CODE, field_code))); - result.extend(field_id(AMOUNT_TYPE_CODE, field_code)); - match amount { - XRPLPaymentAmount::Drops(value) => { - // assert!(value >= 0); - assert!(value <= 10u64.pow(17)); - result.extend((value | POSITIVE_BIT).to_be_bytes()); - }, - XRPLPaymentAmount::Token(token, amount) => { - result.extend(amount_to_bytes(amount)?); - result.extend(currency_to_bytes(token.currency)?); - result.extend(decode_address(token.issuer)?); +pub const HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; +pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; + +// TODO: optimize +pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result { + let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx)?; + + let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI, encoded_unsigned_tx.as_slice())); + let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); + Ok(tx_hash) +} + +pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result { + let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())); + let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); + Ok(tx_hash) +} + +pub fn serialize_signed_tx(signed_tx: &XRPLSignedTransaction) -> Result, ContractError> { + let mut obj = tx_to_xrpl_object(&signed_tx.unsigned_tx)?; + obj.add_field(3, &XRPLArray{ field_code: 16, items: signed_tx.signers.clone() })?; + let parts: Vec = obj.clone().fields.into_iter().map(|f| { + let mut res = Vec::new(); + res.extend(field_id(f.0, f.1)); + res.extend(f.2); + return hex::encode(res); + }).collect(); + println!("signed tx parts {:?}", parts); + + Ok(obj.serialize()) +} + +pub fn serialize_unsigned_tx(tx: &XRPLUnsignedTx) -> Result, ContractError> { + let obj = tx_to_xrpl_object(tx)?; + println!("{:?}", obj.fields); + + let mut result = Vec::from((0x534D5400 as u32).to_be_bytes()); // prefix for multisignature signing + result.extend(obj.serialize()); + Ok(result) +} + +pub fn tx_to_xrpl_object(tx: &XRPLUnsignedTx) -> Result { + match &tx.partial { + XRPLPartialTx::Payment { amount, destination } => make_payment_tx_object(&tx.common, &amount, &XRPLAddress(destination.to_string())), + XRPLPartialTx::TicketCreate { ticket_count } => make_ticket_create_tx_object(&tx.common, ticket_count), + XRPLPartialTx::SignerListSet { signer_quorum, signer_entries } => make_signer_list_set_tx_object(&tx.common, signer_quorum, signer_entries), + } +} + +pub fn make_payment_tx_object(common: &XRPLTxCommonFields, amount: &XRPLPaymentAmount, destination: &XRPLAddress) -> Result { + let mut obj = XRPLObject::new(); + obj.add_field(2, &PAYMENT_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags + obj.add_sequence(&common.sequence)?; + // type: Amount, type_code: 6, nth: 1, !isVLEncoded + obj.add_field(1, amount)?; + // type: Amount, type_code: 6, nth: 8, !isVLEncoded + obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; + obj.add_field(3, &HexBinary::from_hex("")?)?; + obj.add_field(1, &XRPLAddress(common.account.clone()))?; + obj.add_field(3, destination)?; + + Ok(obj) +} + +pub fn make_ticket_create_tx_object(common: &XRPLTxCommonFields, ticket_count: &u32) -> Result { + let mut obj = XRPLObject::new(); + // type_code: 1, nth: 2, !isVLEncoded + obj.add_field(2, &TICKET_CREATE_TX_TYPE)?; + obj.add_sequence(&common.sequence)?; + obj.add_field(40, ticket_count)?; + obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; + obj.add_field(1, &XRPLAddress(common.account.clone()))?; + + Ok(obj) +} + +pub fn make_signer_list_set_tx_object(common: &XRPLTxCommonFields, signer_quorum: &u32, signer_entries: &Vec) -> Result { + let mut obj = XRPLObject::new(); + + obj.add_field(2, &SIGNER_LIST_SET_TX_TYPE)?; + obj.add_sequence(&common.sequence)?; + obj.add_field(35, signer_quorum)?; + obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; + obj.add_field(1, &XRPLAddress(common.account.clone()))?; + obj.add_field(4, &XRPLArray{ field_code: 11, items: signer_entries.clone() })?; + Ok(obj) +} + +pub struct XRPLAddress(String); + +pub trait XRPLSerialize { + const TYPE_CODE: u8; + fn xrpl_serialize(&self) -> Result, ContractError>; +} + +impl XRPLSerialize for u16 { + const TYPE_CODE: u8 = UINT16_TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + Ok(self.to_be_bytes().to_vec()) + } +} + +impl XRPLSerialize for u32 { + const TYPE_CODE: u8 = UINT32_TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + Ok(self.to_be_bytes().to_vec()) + } +} + +impl XRPLSerialize for XRPLPaymentAmount { + const TYPE_CODE: u8 = AMOUNT_TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + match self { + XRPLPaymentAmount::Drops(value) => { + // assert!(value >= 0); + assert!(*value <= 10u64.pow(17)); + Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) + }, + XRPLPaymentAmount::Token(token, amount) => { + let mut result = Vec::new(); + result.extend(amount_to_bytes(amount)?); + result.extend(currency_to_bytes(&token.currency)?); + result.extend(decode_address(&token.issuer)?); + Ok(result) + } } } - Ok(result) } +struct XRPLArray { + field_code: u8, + items: Vec +} + +impl XRPLSerialize for XRPLArray { + const TYPE_CODE: u8 = ARRAY_TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + println!("Vec::xrpl_serialize"); + let mut result: Vec = Vec::new(); + for item in &self.items { + result.extend(field_id(T::TYPE_CODE, self.field_code)); + result.extend(item.xrpl_serialize()?); + } + result.extend(field_id(ARRAY_TYPE_CODE, 1)); + Ok(result) + } +} + + // see https://github.com/XRPLF/xrpl-dev-portal/blob/master/content/_code-samples/tx-serialization/py/serialize.py#L92 // returns None if length too big pub fn encode_length(mut length: usize) -> Option> { @@ -347,155 +471,122 @@ pub fn encode_length(mut length: usize) -> Option> { return None } } - -pub fn serialize_blob(field_code: u8, blob: Vec) -> Option> { - let mut result: Vec = Vec::new(); - result.extend(field_id(BLOB_TYPE_CODE, field_code)); - result.extend(encode_length(blob.len())?); - result.extend(blob); - Some(result) + +impl XRPLSerialize for HexBinary { + const TYPE_CODE: u8 = BLOB_TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + match encode_length(self.len()) { + Some(encoded_length) => { + let mut result = Vec::new(); + result.extend(encoded_length); + result.extend(self.to_vec()); + Ok(result) + } + None => Err(ContractError::InvalidBlob) + } + } } -pub fn serialize_signer(signer: &XRPLSigner) -> Result, ContractError> { - let mut results: Vec> = Vec::new(); - results.push(field_id(OBJECT_TYPE_CODE, 16)); - results.push(serialize_blob(3, signer.signing_pub_key.clone().as_slice().to_vec()).ok_or(ContractError::InvalidSigningPubKey)?); - results.push(serialize_blob(4, signer.txn_signature.clone().as_slice().to_vec()).ok_or(ContractError::InvalidSignature)?); - results.push(serialize_account_id(1, signer.account.clone())?); - results.push(field_id(OBJECT_TYPE_CODE, 1)); - println!("signer hex parts {}", results.iter().map(|x| hex::encode(x)).collect::>().join(",")); - Ok(results.concat()) + +impl XRPLSerialize for XRPLSigner { + const TYPE_CODE: u8 = OBJECT_TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut obj = XRPLObject::new(); + obj.add_field(3, &self.signing_pub_key)?; + obj.add_field(4, &self.txn_signature)?; + obj.add_field(1, &XRPLAddress(self.account.clone()))?; + let mut result = obj.serialize(); + result.extend(field_id(OBJECT_TYPE_CODE, 1)); + let parts: Vec = obj.clone().fields.into_iter().map(|f| { + let mut res = Vec::new(); + res.extend(field_id(f.0, f.1)); + res.extend(f.2); + return hex::encode(res); + }).collect(); + println!("signer parts {:?}", parts); + Ok(result) + } } -pub fn serialize_signers_array(field_code: u8, signers: Vec) -> Result, ContractError> { - let mut result: Vec = Vec::new(); - result.extend(field_id(ARRAY_TYPE_CODE, field_code)); - for signer in &signers { - result.extend(serialize_signer(signer)?); +impl XRPLSerialize for XRPLAddress { + const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut result: Vec = Vec::new(); + result.extend(vec![20]); + result.extend(decode_address(&self.0)?); + Ok(result) } - result.extend(field_id(ARRAY_TYPE_CODE, 1)); - Ok(result) } -pub fn serialize_account_id(field_code: u8, account_id: String) -> Result, ContractError> { - let mut result: Vec = Vec::new(); - result.extend(field_id(ACCOUNT_ID_TYPE_CODE, field_code)); - result.extend(vec![20]); - result.extend(decode_address(account_id)?); - Ok(result) +impl XRPLSerialize for XRPLSignerEntry { + const TYPE_CODE: u8 = OBJECT_TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + println!("XRPLSignerEntry::xrpl_serialize"); + let mut obj = XRPLObject::new(); + obj.add_field(1, &XRPLAddress(self.account.clone()))?; + obj.add_field(3, &self.signer_weight)?; + let mut result = obj.serialize(); + result.extend(field_id(OBJECT_TYPE_CODE, 1)); + + let parts: Vec = obj.clone().fields.into_iter().map(|f| { + let mut res = Vec::new(); + res.extend(field_id(f.0, f.1)); + res.extend(f.2); + return hex::encode(res); + }).collect(); + println!("signer entry parts {:?}", parts); + Ok(result) + } } -// TODO: each worker must append his decoded address to the serialized tx before signing -pub fn serialize_unsigned_payment_tx(common: XRPLTxCommonFields, amount: XRPLPaymentAmount, destination: nonempty::String) -> Result, ContractError> { - let mut results: Vec> = Vec::new(); - // value: 0, type:uint16, type_code: 1, nth: 2, !isVLEncoded - // serialize_transaction_payment_type(); - results.push(Vec::from((0x534D5400 as u32).to_be_bytes())); // prefix for multisignature signing - results.push(serialize_uint16(2, PAYMENT_TYPE)); - results.push(serialize_uint32(2, 0)); - match common.sequence { - Sequence::Plain(seq) => { - // type: Uint32, type_code: 2, nth: 4, !isVLEncoded - // serialize_sequence(seq); - results.push(serialize_uint32(4, seq)); - }, - Sequence::Ticket(seq) => { - // type: Uint32, type_code: 2, nth: 4, !isVLEncoded - // serialize_sequence(seq) - results.push(serialize_uint32(4, 0)); - // type: Uint32, type_code: 2, nth: 41, !isVLEncoded - // serialize_ticket_sequence(seq); - results.push(serialize_uint32(41, seq)); - } - }; - // type: Amount, type_code: 6, nth: 1, !isVLEncoded - results.push(serialize_amount(1, amount)?); - // type: Amount, type_code: 6, nth: 8, !isVLEncoded - results.push(serialize_amount(8, XRPLPaymentAmount::Drops(common.fee))?); - // type: Blob, type_code: 7, nth: 3, isVLEncoded - // TODO: hex::encode to slice? - results.push(serialize_blob(3, hex::decode("").unwrap()).unwrap()); - // type: AccountId, type_code: 8, nth: 1, isVLEncoded - results.push(serialize_account_id(1, common.account)?); - // type: AccountId, type_code: 8, nth:3, isVLEncoded - results.push(serialize_account_id(3, destination.into())?); - - println!("hex parts {}", results.iter().map(|x| hex::encode(x)).collect::>().join(",")); - Ok(results.concat()) -} - -pub fn serialize_unsigned_tx(tx: XRPLUnsignedTx) -> Result, ContractError> { - match tx.partial { - XRPLPartialTx::Payment { amount, destination } => serialize_unsigned_payment_tx(tx.common, amount, destination), - _ => unimplemented!() - } -} - -// TODO: each worker must append his decoded address to the serialized tx before signing -pub fn serialize_signed_payment_tx(signers: Vec, common: XRPLTxCommonFields, amount: XRPLPaymentAmount, destination: nonempty::String) -> Result, ContractError> { - let mut results: Vec> = Vec::new(); - // value: 0, type:uint16, type_code: 1, nth: 2, !isVLEncoded - // serialize_transaction_payment_type(); - results.push(serialize_uint16(2, PAYMENT_TYPE)); - results.push(serialize_uint32(2, 0)); - match common.sequence { - Sequence::Plain(seq) => { - // type: Uint32, type_code: 2, nth: 4, !isVLEncoded - // serialize_sequence(seq); - results.push(serialize_uint32(4, seq)); - }, - Sequence::Ticket(seq) => { - // type: Uint32, type_code: 2, nth: 4, !isVLEncoded - // serialize_sequence(seq) - results.push(serialize_uint32(4, 0)); - // type: Uint32, type_code: 2, nth: 41, !isVLEncoded - // serialize_ticket_sequence(seq); - results.push(serialize_uint32(41, seq)); - } - }; - // type: Amount, type_code: 6, nth: 1, !isVLEncoded - results.push(serialize_amount(1, amount)?); - // type: Amount, type_code: 6, nth: 8, !isVLEncoded - results.push(serialize_amount(8, XRPLPaymentAmount::Drops(common.fee))?); - // type: Blob, type_code: 7, nth: 3, isVLEncoded - // TODO: hex::encode to slice? - results.push(serialize_blob(3, hex::decode("").unwrap()).unwrap()); - // type: AccountId, type_code: 8, nth: 1, isVLEncoded - results.push(serialize_account_id(1, common.account)?); - // type: AccountId, type_code: 8, nth:3, isVLEncoded - results.push(serialize_account_id(3, destination.into())?); - // type: STArray, type_code: 14, nth:3, !isVLEncoded - results.push(serialize_signers_array(3, signers)?); - - println!("hex parts {}", results.iter().map(|x| hex::encode(x)).collect::>().join(",")); - Ok(results.concat()) +#[derive(Debug, Clone)] +pub struct XRPLObject { + fields: Vec<(u8, u8, Vec)> } -pub fn serialize_signed_tx(signed_tx: XRPLSignedTransaction) -> Result, ContractError> { - match signed_tx.unsigned_tx.partial { - XRPLPartialTx::Payment { amount, destination } => serialize_signed_payment_tx(signed_tx.signers, signed_tx.unsigned_tx.common, amount, destination), - _ => unimplemented!() +impl XRPLObject { + pub fn new() -> XRPLObject { + Self { + fields: Vec::new() + } } -} -// TODO: optimize -pub fn compute_unsigned_tx_hash(unsigned_tx: XRPLUnsignedTx) -> Result { - let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx)?; + pub fn add_field(&mut self, field_code: u8, value: &T) -> Result<(), ContractError> { + self.fields.push((T::TYPE_CODE, field_code, value.xrpl_serialize()?)); + Ok(()) + } - let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI, encoded_unsigned_tx.as_slice())); - let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); - Ok(tx_hash) -} + pub fn add_sequence(&mut self, sequence: &Sequence) -> Result<(), ContractError> { + match sequence { + Sequence::Plain(seq) => { + self.add_field(4, seq) + }, + Sequence::Ticket(seq) => { + self.add_field(4, &0u32)?; + self.add_field(41, seq) + } + } + } -pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result { - let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())); - let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); - Ok(tx_hash) + pub fn serialize(&self) -> Vec { + let mut tmp: Vec<(u8, u8, Vec)> = self.fields.clone(); + tmp.sort_by(|a, b| { (a.0, a.1).cmp(&(b.0, b.1)) }); + tmp.into_iter() + .map(|f| { + let mut res = Vec::new(); + res.extend(field_id(f.0, f.1)); + res.extend(f.2); + return res; + }) + .collect::>>() + .concat() + } } -pub const HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; -pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; - pub fn xrpl_hash( prefix: [u8; 4], tx_blob: &[u8], @@ -521,7 +612,7 @@ fn issue_tx( sequence.clone(), ); - let tx_hash = compute_unsigned_tx_hash(unsigned_tx.clone())?; + let tx_hash = compute_unsigned_tx_hash(&unsigned_tx)?; TRANSACTION_INFO.save( storage, @@ -739,6 +830,7 @@ mod tests { use super::*; + /* #[test] fn serialize_xrpl_unsigned_token_payment_transaction() { let unsigned_tx = XRPLUnsignedTx { @@ -765,6 +857,7 @@ mod tests { hex::encode_upper(encoded_unsigned_tx) ); } + */ #[test] fn serialize_xrpl_unsigned_xrp_payment_transaction() { @@ -780,7 +873,7 @@ mod tests { destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), } }; - let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx).unwrap(); + let encoded_unsigned_tx = serialize_unsigned_tx(&unsigned_tx).unwrap(); assert_eq!( "534D5400120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", hex::encode_upper(encoded_unsigned_tx) @@ -798,7 +891,7 @@ mod tests { destination: nonempty::String::try_from("rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq").unwrap(), } }; - let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx).unwrap(); + let encoded_unsigned_tx = serialize_unsigned_tx(&unsigned_tx).unwrap(); assert_eq!( "534D54001200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6", hex::encode_upper(encoded_unsigned_tx) @@ -832,7 +925,7 @@ mod tests { } ] }; - let encoded_signed_tx = serialize_signed_tx(signed_tx).unwrap(); + let encoded_signed_tx = serialize_signed_tx(&signed_tx).unwrap(); assert_eq!( "1200002200000000240297B79561400000003B9ACA0068400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689A831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9744000CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744062B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE028114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", hex::encode_upper(encoded_signed_tx) From 772b243b2dd0906a748d0e25e9e15cdb5be8445e Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 2 Jan 2024 15:36:25 +0200 Subject: [PATCH 07/80] xrpl serialization all tests pass --- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 94 +++++++++++++++++-- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 4d2b502d1..f995788d9 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -367,9 +367,11 @@ pub fn make_ticket_create_tx_object(common: &XRPLTxCommonFields, ticket_count: & let mut obj = XRPLObject::new(); // type_code: 1, nth: 2, !isVLEncoded obj.add_field(2, &TICKET_CREATE_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags obj.add_sequence(&common.sequence)?; - obj.add_field(40, ticket_count)?; - obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; + obj.add_field(40, ticket_count)?; // 202800000000a + obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; // 68400000000000001e + obj.add_field(3, &HexBinary::from_hex("")?)?; obj.add_field(1, &XRPLAddress(common.account.clone()))?; Ok(obj) @@ -379,10 +381,12 @@ pub fn make_signer_list_set_tx_object(common: &XRPLTxCommonFields, signer_quorum let mut obj = XRPLObject::new(); obj.add_field(2, &SIGNER_LIST_SET_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags obj.add_sequence(&common.sequence)?; obj.add_field(35, signer_quorum)?; obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; obj.add_field(1, &XRPLAddress(common.account.clone()))?; + obj.add_field(3, &HexBinary::from_hex("")?)?; obj.add_field(4, &XRPLArray{ field_code: 11, items: signer_entries.clone() })?; Ok(obj) } @@ -515,7 +519,7 @@ impl XRPLSerialize for XRPLAddress { fn xrpl_serialize(&self) -> Result, ContractError> { let mut result: Vec = Vec::new(); - result.extend(vec![20]); + result.extend(vec![20]); // 0x14, length-encoding result.extend(decode_address(&self.0)?); Ok(result) } @@ -915,23 +919,101 @@ mod tests { }, signers: vec![ XRPLSigner{ account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), - txn_signature: HexBinary::from(hex::decode("00CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B").unwrap()), + txn_signature: HexBinary::from(hex::decode("3C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F551403").unwrap()), signing_pub_key: HexBinary::from(hex::decode("EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9").unwrap()), }, XRPLSigner{ account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), - txn_signature: HexBinary::from(hex::decode("62B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE02").unwrap()), + txn_signature: HexBinary::from(hex::decode("5EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC04").unwrap()), signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), } ] }; let encoded_signed_tx = serialize_signed_tx(&signed_tx).unwrap(); assert_eq!( - "1200002200000000240297B79561400000003B9ACA0068400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689A831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9744000CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744062B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE028114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", + "12000A2200000000240297B79720280000000A68400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689AF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F974403C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F5514038114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B43574405EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC048114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", hex::encode_upper(encoded_signed_tx) ); } + #[test] + fn serialize_xrpl_signed_xrp_ticket_create_transaction() { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx { + common: XRPLTxCommonFields { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + fee: 30, + sequence: Sequence::Plain(43497367), + signing_pub_key: "".to_string(), + }, + partial: XRPLPartialTx::TicketCreate { + ticket_count: 10 + } + }, signers: vec![ + XRPLSigner{ + account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), + txn_signature: HexBinary::from(hex::decode("3C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F551403").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9").unwrap()), + }, + XRPLSigner{ + account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), + txn_signature: HexBinary::from(hex::decode("5EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC04").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), + }, + ] + }; + let encoded_signed_tx = serialize_signed_tx(&signed_tx).unwrap(); + assert_eq!( + "12000A2200000000240297B79720280000000A68400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689AF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F974403C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F5514038114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B43574405EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC048114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", + hex::encode_upper(encoded_signed_tx) + ); + } + + + #[test] + fn serialize_xrpl_signed_signer_list_set_transaction() { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx { + common: XRPLTxCommonFields { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + fee: 30, + sequence: Sequence::Plain(43497378), + signing_pub_key: "".to_string(), + }, + partial: XRPLPartialTx::SignerListSet { + signer_quorum: 3, + signer_entries: vec![ + XRPLSignerEntry{ + account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), + signer_weight: 2 + }, + XRPLSignerEntry{ + account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), + signer_weight: 1 + } + ] + } + }, signers: vec![ + XRPLSigner{ + account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), + txn_signature: HexBinary::from(hex::decode("F4EE6E7AE1359360C16FF774EB907C908AE5E717A4DAAA8BF0C5A754A544B9690118E18EB9ABF2CE41A4853F346321F4624089AE803EB869B49F3ED506139A0E").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9").unwrap()), + }, + XRPLSigner{ + account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), + txn_signature: HexBinary::from(hex::decode("86B8AF804C7F4881E125F4F876C9EC292EEF811D572D4D4BA7C6CD533B13FB1B9A31ADB4A71DD54405135BEFCDEF3A98564479B681242250D42154A93EB1FE04").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), + }, + ] + }; + let encoded_signed_tx = serialize_signed_tx(&signed_tx).unwrap(); + assert_eq!( + "12000C2200000000240297B7A220230000000368400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689AF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F97440F4EE6E7AE1359360C16FF774EB907C908AE5E717A4DAAA8BF0C5A754A544B9690118E18EB9ABF2CE41A4853F346321F4624089AE803EB869B49F3ED506139A0E8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744086B8AF804C7F4881E125F4F876C9EC292EEF811D572D4D4BA7C6CD533B13FB1B9A31ADB4A71DD54405135BEFCDEF3A98564479B681242250D42154A93EB1FE048114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1F4EB1300028114DCE722505E32B29932618C5C9819AAEA03754AA5E1EB1300018114310A592CA22E8B35B819464F8A581A36C91DE857E1F1", + hex::encode_upper(encoded_signed_tx) + ); + } + + #[test] fn ed25519_public_key_to_xrpl_address() { assert_eq!( From e07f8a0d356e973f1656e5325b6240d32f429627 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 2 Jan 2024 16:27:33 +0200 Subject: [PATCH 08/80] xrpl fix accidentally broken signed payment tx test --- contracts/xrpl-multisig-prover/src/xrpl_multisig.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index f995788d9..d4509ce2d 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -919,19 +919,19 @@ mod tests { }, signers: vec![ XRPLSigner{ account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), - txn_signature: HexBinary::from(hex::decode("3C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F551403").unwrap()), + txn_signature: HexBinary::from(hex::decode("00CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B").unwrap()), signing_pub_key: HexBinary::from(hex::decode("EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9").unwrap()), }, XRPLSigner{ account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), - txn_signature: HexBinary::from(hex::decode("5EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC04").unwrap()), + txn_signature: HexBinary::from(hex::decode("62B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE02").unwrap()), signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), } ] }; let encoded_signed_tx = serialize_signed_tx(&signed_tx).unwrap(); assert_eq!( - "12000A2200000000240297B79720280000000A68400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689AF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F974403C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F5514038114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B43574405EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC048114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", + "1200002200000000240297B79561400000003B9ACA0068400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689A831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9744000CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744062B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE028114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", hex::encode_upper(encoded_signed_tx) ); } From 7eff148be652d3bed09a0addd5ed83d0f4439bcb Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 3 Jan 2024 12:57:00 +0200 Subject: [PATCH 09/80] xrpl-{multisig-prover,voting-verifier}: add integration tests & make necessary fixes --- Cargo.lock | 7 + contracts/xrpl-multisig-prover/Cargo.toml | 34 +- .../src/axelar_workers.rs | 12 +- .../xrpl-multisig-prover/src/contract.rs | 129 ++++-- contracts/xrpl-multisig-prover/src/error.rs | 8 + contracts/xrpl-multisig-prover/src/query.rs | 50 ++- contracts/xrpl-multisig-prover/src/state.rs | 2 +- contracts/xrpl-multisig-prover/src/types.rs | 27 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 78 ++-- contracts/xrpl-voting-verifier/src/execute.rs | 5 +- contracts/xrpl-voting-verifier/tests/tests.rs | 4 +- integration-tests/Cargo.toml | 2 + integration-tests/tests/message_routing.rs | 216 +++++++++- integration-tests/tests/test_utils/mod.rs | 397 +++++++++++++++++- 14 files changed, 855 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e44a21457..8f91d14bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3918,6 +3918,8 @@ dependencies = [ "thiserror", "tofn", "voting-verifier", + "xrpl-multisig-prover", + "xrpl-voting-verifier", ] [[package]] @@ -9293,17 +9295,22 @@ dependencies = [ name = "xrpl-multisig-prover" version = "0.1.0" dependencies = [ + "anyhow", "axelar-wasm-std", "axelar-wasm-std-derive", "bs58 0.5.0", "connection-router", "cosmwasm-schema", "cosmwasm-std", + "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", + "elliptic-curve", "error-stack", "gateway", + "generic-array", "hex", + "k256", "multisig", "report", "ripemd", diff --git a/contracts/xrpl-multisig-prover/Cargo.toml b/contracts/xrpl-multisig-prover/Cargo.toml index 6f3e6ee60..575397c8a 100644 --- a/contracts/xrpl-multisig-prover/Cargo.toml +++ b/contracts/xrpl-multisig-prover/Cargo.toml @@ -2,14 +2,33 @@ name = "xrpl-multisig-prover" version = "0.1.0" edition = "2021" +description = "XRPL multisig prover contract" +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.6 +""" [dependencies] -cosmwasm-std = { workspace = true, version = "1.5.0" } +cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } axelar-wasm-std = { workspace = true } axelar-wasm-std-derive = { workspace = true } @@ -23,9 +42,16 @@ error-stack = { workspace = true } report = { workspace = true } serde_json = "1.0.89" sha2 = { version = "0.10.7" } -sha3 = { version = "0.10", default-features = false, features = [] } +sha3 = { workspace = true } cw-utils = "1.0.1" bs58 = "0.5.0" ripemd = "0.1.3" -hex = "0.4.3" +hex = { version = "0.4.3", default-features = false, features = [] } xrpl-voting-verifier = { workspace = true, features = ["library"] } +k256 = { version = "0.13.1", features = ["ecdsa"] } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.15.1" +elliptic-curve = "0.13.5" +generic-array = "0.14.7" diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index e004902af..fd8a16c67 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -7,9 +7,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, Uint256, Addr, Fraction}; use multisig::key::KeyType; use service_registry::state::Worker; -use sha2::Digest; use multisig::key::PublicKey; -use sha3::Keccak256; use crate::querier::Querier; use crate::error::ContractError; @@ -108,7 +106,7 @@ impl WorkerSet { .collect() } - pub fn hash(&self) -> HexBinary { + /*pub fn hash(&self) -> HexBinary { Keccak256::digest(serde_json::to_vec(&self).expect("couldn't serialize worker set")) .as_slice() .into() @@ -116,7 +114,7 @@ impl WorkerSet { pub fn id(&self) -> String { self.hash().to_hex() - } + }*/ } fn convert_uint256_to_u16_unsafely(value: Uint256) -> u16 { @@ -176,7 +174,7 @@ pub fn get_active_worker_set( }); } - let sum_of_weights: u16 = weights.iter().sum(); + let sum_of_weights: u32 = weights.iter().map(|w| u32::from(*w)).sum(); let quorum = (sum_of_weights as u64) .checked_mul(signing_threshold.numerator().into()) @@ -209,6 +207,10 @@ mod tests { #[test] fn test_convert_or_scale_weights() { + let weights = vec![Uint256::from(1u128), Uint256::from(1u128)]; + let scaled_weights = convert_or_scale_weights(weights); + assert_eq!(scaled_weights, vec![65535, 65535]); + let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128)]; let scaled_weights = convert_or_scale_weights(weights); assert_eq!(scaled_weights, vec![21845, 43690, 65535]); diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index ea0bc8f1f..61c21df57 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -3,35 +3,38 @@ use std::str::FromStr; #[cfg(not(feature = "library"))] use axelar_wasm_std::Threshold; use connection_router::state::{CrossChainId, ChainName}; -use cosmwasm_schema::cw_serde; +use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{ entry_point, Storage, wasm_execute, SubMsg, Reply, - DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_binary, Deps, StdResult, Binary, Addr, + DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_binary, Deps, StdResult, Binary, Addr, HexBinary, }; use xrpl_voting_verifier::execute::MessageStatus; use crate::{ error::ContractError, - state::{Config, CONFIG, REPLY_TX_HASH, TOKENS, CURRENT_WORKER_SET, NEXT_WORKER_SET, SIGNED_TO_UNSIGNED_TX_HASH}, + state::{Config, CONFIG, REPLY_TX_HASH, TOKENS, CURRENT_WORKER_SET, NEXT_WORKER_SET, SIGNED_TO_UNSIGNED_TX_HASH, NEXT_SEQUENCE_NUMBER, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS}, reply, types::*, - xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query::{get_proof, GetProofResponse, QueryMsg, self}, + xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query::{get_proof, self}, }; pub const START_MULTISIG_REPLY_ID: u64 = 1; #[cw_serde] pub struct InstantiateMsg { - axelar_multisig_address: String, - gateway_address: String, - signing_threshold: Threshold, - xrpl_multisig_address: String, - voting_verifier_address: String, - service_registry_address: String, - service_name: String, - worker_set_diff_threshold: u32, - xrpl_fee: u64, - ticket_count_threshold: u32, + pub axelar_multisig_address: String, + pub gateway_address: String, + pub signing_threshold: Threshold, + pub xrpl_multisig_address: String, + pub voting_verifier_address: String, + pub service_registry_address: String, + pub service_name: String, + pub worker_set_diff_threshold: u32, + pub xrpl_fee: u64, + pub ticket_count_threshold: u32, + pub available_tickets: Vec, + pub next_sequence_number: u32, + pub last_assigned_ticket_number: u32, } #[cfg_attr(not(feature = "library"), entry_point)] @@ -43,7 +46,6 @@ pub fn instantiate( ) -> Result { let axelar_multisig_address = deps.api.addr_validate(&msg.axelar_multisig_address)?; let gateway_address = deps.api.addr_validate(&msg.gateway_address)?; - let xrpl_multisig_address = deps.api.addr_validate(&msg.xrpl_multisig_address)?; let voting_verifier_address = deps.api.addr_validate(&msg.voting_verifier_address)?; let service_registry_address = deps.api.addr_validate(&msg.service_registry_address)?; @@ -54,7 +56,7 @@ pub fn instantiate( let config = Config { axelar_multisig_address, gateway_address, - xrpl_multisig_address, + xrpl_multisig_address: msg.xrpl_multisig_address, signing_threshold: msg.signing_threshold, voting_verifier_address, service_registry_address, @@ -66,27 +68,44 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; + NEXT_SEQUENCE_NUMBER.save(deps.storage, &msg.next_sequence_number)?; + LAST_ASSIGNED_TICKET_NUMBER.save(deps.storage, &msg.last_assigned_ticket_number)?; + AVAILABLE_TICKETS.save(deps.storage, &msg.available_tickets)?; + let querier = Querier::new(deps.querier, config.clone()); let new_worker_set = axelar_workers::get_active_worker_set(querier, msg.signing_threshold, env.block.height)?; CURRENT_WORKER_SET.save(deps.storage, &new_worker_set)?; - Ok(Response::new().add_message(wasm_execute( + let msg = wasm_execute( config.axelar_multisig_address, &multisig::msg::ExecuteMsg::RegisterWorkerSet { worker_set: new_worker_set.into(), }, vec![], - )?)) + )?; + + Ok(Response::new().add_message(msg)) } #[cw_serde] pub enum ExecuteMsg { - ConstructProof(CrossChainId), - FinalizeProof(Uint64), - UpdateTxStatus(CrossChainId, MessageStatus), - UpdateWorkerSet(), - TicketCreate(), + RegisterToken { denom: String, token: XRPLToken }, + // TODO: fix inconsistency between message_id & cc_id + ConstructProof { message_id: CrossChainId }, + FinalizeProof { multisig_session_id: Uint64 }, + UpdateTxStatus { cc_id: CrossChainId, message_status: MessageStatus }, + UpdateWorkerSet, + TicketCreate, +} + +fn register_token( + storage: &mut dyn Storage, + denom: String, + token: &XRPLToken, +) -> Result { + TOKENS.save(storage, denom, token)?; + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -100,19 +119,23 @@ pub fn execute( let querier = Querier::new(deps.querier, config.clone()); let res = match msg { - ExecuteMsg::ConstructProof(message_id) => { + // TODO: should be admin-only + ExecuteMsg::RegisterToken { denom, token } => { + register_token(deps.storage, denom, &token) + }, + ExecuteMsg::ConstructProof { message_id } => { construct_payment_proof(deps.storage, querier, info, env.contract.address, &config, message_id) }, - ExecuteMsg::FinalizeProof(session_id) => { - finalize_proof(deps.storage, querier, &session_id) - } - ExecuteMsg::UpdateWorkerSet() => { + ExecuteMsg::FinalizeProof { multisig_session_id } => { + finalize_proof(deps.storage, querier, &multisig_session_id) + }, + ExecuteMsg::UpdateWorkerSet {} => { construct_signer_list_set_proof(deps.storage, querier, env, &config) }, - ExecuteMsg::UpdateTxStatus(cc_id, status) => { - update_tx_status(deps.storage, querier, cc_id, &status) + ExecuteMsg::UpdateTxStatus { cc_id, message_status } => { + update_tx_status(deps.storage, querier, cc_id, &message_status) }, - ExecuteMsg::TicketCreate() => { + ExecuteMsg::TicketCreate {} => { construct_ticket_create_proof(deps.storage, env.contract.address, &config) }, }?; @@ -120,6 +143,11 @@ pub fn execute( Ok(res) } +// TODO: proof submitted on XRPL may be +// different than the one submitted here +// (i.e., the signed hash is different because more signatures +// were gathered after the TX was broadcasted to XRPL +// but before finalize_proof was called) fn finalize_proof( storage: &mut dyn Storage, querier: Querier, @@ -132,7 +160,7 @@ fn finalize_proof( GetProofResponse::Completed { unsigned_tx_hash, tx_blob } => { let tx_hash = xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?; SIGNED_TO_UNSIGNED_TX_HASH.save(storage, tx_hash, &unsigned_tx_hash)?; - Ok(Response::default()) + Ok(Response::default()) } } } @@ -153,7 +181,9 @@ fn construct_payment_proof( let coin = funds.remove(0); let xrpl_token = TOKENS.load(storage, coin.denom.clone())?; let message = querier.get_message(message_id.clone())?; - let drops = u64::try_from(coin.amount.u128() / 10u128.pow(12)).map_err(|_| ContractError::InvalidAmount)?; + // TODO: handle coin.amount precision loss + let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount)?; + // TODO: find better way to distinguish XRP from the rest let xrpl_payment_amount = if xrpl_token.currency == XRPLToken::NATIVE_CURRENCY { XRPLPaymentAmount::Drops(drops) } else { @@ -191,12 +221,14 @@ pub fn start_signing_session( self_address: Addr, ) -> Result { REPLY_TX_HASH.save(storage, &tx_hash)?; - let cur_worker_set = CURRENT_WORKER_SET.load(storage)?; + let cur_worker_set: multisig::worker_set::WorkerSet = CURRENT_WORKER_SET.load(storage)?.into(); let start_sig_msg: multisig::msg::ExecuteMsg = multisig::msg::ExecuteMsg::StartSigningSession { worker_set_id: cur_worker_set.id(), chain_name: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), msg: tx_hash.into(), - sig_verifier: Some(self_address.into()) + // TODO: implement sig_verifier + //sig_verifier: Some(self_address.into()) + sig_verifier: None, }; let wasm_msg = wasm_execute(config.axelar_multisig_address.clone(), &start_sig_msg, vec![])?; @@ -297,6 +329,27 @@ pub fn reply( .map_err(axelar_wasm_std::ContractError::from) } +// TODO: move to msg.rs +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(GetProofResponse)] + GetProof { multisig_session_id: Uint64 }, + #[returns(GetMessageToSignResponse)] + GetMessageToSign { multisig_session_id: Uint64, signer_xrpl_address: String }, +} + +#[cw_serde] +#[serde(tag = "status")] +pub enum GetProofResponse { + Completed { unsigned_tx_hash: TxHash, tx_blob: HexBinary}, + Pending { unsigned_tx_hash: TxHash }, +} + +#[cw_serde] +pub struct GetMessageToSignResponse { + pub tx_hash: HexBinary, +} #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { @@ -306,5 +359,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetProof { multisig_session_id, } => to_binary(&query::get_proof(deps.storage, querier, &multisig_session_id)?), + QueryMsg::GetMessageToSign { + multisig_session_id, + signer_xrpl_address, + } => to_binary(&query::get_message_to_sign(deps.storage, &multisig_session_id, &signer_xrpl_address)?), } -} \ No newline at end of file +} diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 743f251b9..70c820883 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -47,6 +47,9 @@ pub enum ContractError { #[error("transaction has not been confirmed")] TransactionStatusNotConfirmed, + #[error("transaction status is not pending")] + TransactionStatusNotPending, + #[error("invalid payment amount")] InvalidPaymentAmount, @@ -74,6 +77,11 @@ pub enum ContractError { #[error("invalid blob")] InvalidBlob, + #[error("invalid message ID {0}")] + InvalidMessageID(String), + + #[error("failed to encode signature")] + FailedToEncodeSignature, } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 9cffad7ba..fed7a5d72 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -1,27 +1,15 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{StdResult, Uint64, HexBinary, Storage}; use multisig::types::MultisigState; +// TODO: remove dependency? +use k256::{ecdsa, schnorr::signature::SignatureEncoding}; + use crate::{ - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO}, types::TxHash, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self}, querier::Querier, + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self}, querier::Querier, contract::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, }; -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(GetProofResponse)] - GetProof { multisig_session_id: Uint64 }, -} - -#[cw_serde] -#[serde(tag = "status")] -pub enum GetProofResponse { - Completed { unsigned_tx_hash: TxHash, tx_blob: HexBinary}, - Pending { unsigned_tx_hash: TxHash }, -} - -pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> XRPLSignedTransaction { +pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { let xrpl_signers: Vec = axelar_signers .iter() .map(|(axelar_signer, signature)| { @@ -29,15 +17,37 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul XRPLSigner { account: xrpl_address, signing_pub_key: axelar_signer.pub_key.clone().into(), - txn_signature: HexBinary::from(signature.clone().as_ref()) + // TODO: should work with Ed25519 signatures too + txn_signature: HexBinary::from(ecdsa::Signature::to_der( + &ecdsa::Signature::try_from(signature.clone().as_ref()) + .map_err(|_| ContractError::FailedToEncodeSignature).unwrap() // TODO: FIX - SHOULD NOT UNWRAP + ).to_vec()), } }) .collect::>(); - XRPLSignedTransaction { + Ok(XRPLSignedTransaction { unsigned_tx, signers: xrpl_signers, + }) +} + +pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &String) -> StdResult { + let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; + + let tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; + if tx_info.status != TransactionStatus::Pending { + return Err(ContractError::TransactionStatusNotPending.into()); } + + let serialized_unsigned_tx = xrpl_multisig::serialize_unsigned_tx(&tx_info.unsigned_contents)?; + let serialized_signer_xrpl_address = xrpl_multisig::decode_address(signer_xrpl_address)?; + + let serialized_tx = &[serialized_unsigned_tx, serialized_signer_xrpl_address.to_vec()].concat(); + + Ok(GetMessageToSignResponse { + tx_hash: xrpl_multisig::xrpl_hash(None, serialized_tx).into() + }) } pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: &Uint64) -> StdResult { @@ -56,7 +66,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & .map(|(signer, signature)| (signer.clone(), signature.clone().unwrap())) .collect(); - let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers); + let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; let tx_blob: HexBinary = HexBinary::from(xrpl_multisig::serialize_signed_tx(&signed_tx)?); GetProofResponse::Completed { unsigned_tx_hash, tx_blob } } diff --git a/contracts/xrpl-multisig-prover/src/state.rs b/contracts/xrpl-multisig-prover/src/state.rs index cbef70824..8da8261fc 100644 --- a/contracts/xrpl-multisig-prover/src/state.rs +++ b/contracts/xrpl-multisig-prover/src/state.rs @@ -10,7 +10,7 @@ pub struct Config { pub axelar_multisig_address: Addr, pub gateway_address: Addr, pub signing_threshold: Threshold, - pub xrpl_multisig_address: Addr, + pub xrpl_multisig_address: String, pub voting_verifier_address: Addr, pub service_registry_address: Addr, pub service_name: String, diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index d733f7185..af9eb4df7 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -1,5 +1,4 @@ -use std::ops::Deref; - +use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256}; @@ -17,6 +16,25 @@ pub enum TransactionStatus { FailedOffChain, } +// TODO: import from verifier +pub fn parse_message_id( + message_id: &nonempty::String, +) -> Result<(nonempty::String, u64), ContractError> { + // expected format: : + let components = message_id.split(":").collect::>(); + + if components.len() != 2 { + return Err(ContractError::InvalidMessageID(message_id.to_string())); + } + + Ok(( + components[0].try_into()?, + components[1] + .parse::() + .map_err(|_| ContractError::InvalidMessageID(message_id.to_string()))?, + )) +} + #[cw_serde] pub struct TxHash(pub HexBinary); @@ -24,7 +42,8 @@ impl TryFrom for TxHash { type Error = ContractError; fn try_from(cc_id: CrossChainId) -> Result { // TODO check this is correct - Ok(Self(HexBinary::from_hex(cc_id.id.deref().as_str())?)) + let (tx_id, _event_index) = parse_message_id(&cc_id.id)?; + Ok(Self(HexBinary::from_hex(tx_id.to_ascii_lowercase().as_str())?)) } } @@ -109,5 +128,5 @@ pub struct XRPLToken { } impl XRPLToken { - pub const NATIVE_CURRENCY: &str = "XRP"; + pub const NATIVE_CURRENCY: &'static str = "XRP"; } diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index d4509ce2d..44364ff61 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; -use cosmwasm_schema::{cw_serde, serde::Serializer}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Storage, HexBinary}; use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; @@ -13,28 +13,28 @@ use crate::{ types::*, axelar_workers::{WorkerSet, AxelarSigner}, }; -fn itoa_serialize(x: &u64, s: S) -> Result +/*fn itoa_serialize(x: &u64, s: S) -> Result where S: Serializer, { s.serialize_str(&x.to_string()[..]) -} +}*/ #[cw_serde] pub struct XRPLTokenAmount(pub String); #[cw_serde] -#[serde(untagged)] +// #[serde(untagged)] pub enum XRPLPaymentAmount { Drops( - #[serde(serialize_with = "itoa_serialize")] + // #[serde(serialize_with = "itoa_serialize")] u64, ), Token(XRPLToken, XRPLTokenAmount), } #[cw_serde] -#[serde(untagged)] +// #[serde(untagged)] pub enum Sequence { Plain(u32), Ticket(u32), @@ -50,33 +50,33 @@ impl Into for Sequence { } #[cw_serde] -#[serde(rename_all = "PascalCase")] +//#[serde(rename_all = "PascalCase")] pub struct XRPLTxCommonFields { pub account: String, // TODO: redundant here? - #[serde(serialize_with = "itoa_serialize")] + //#[serde(serialize_with = "itoa_serialize")] pub fee: u64, pub sequence: Sequence, pub signing_pub_key: String, } #[cw_serde] -#[serde(rename_all = "PascalCase", tag = "SignerEntry")] +// #[serde(rename_all = "PascalCase", tag = "SignerEntry")] pub struct XRPLSignerEntry { pub account: String, pub signer_weight: u16, } #[cw_serde] -#[serde(rename_all = "PascalCase")] +//#[serde(rename_all = "PascalCase")] pub struct XRPLUnsignedTx { - #[serde(flatten)] + //#[serde(flatten)] pub common: XRPLTxCommonFields, - #[serde(flatten)] + //#[serde(flatten)] pub partial: XRPLPartialTx, } #[cw_serde] -#[serde(tag="TransactionType")] +//#[serde(tag="TransactionType")] pub enum XRPLPartialTx { Payment { amount: XRPLPaymentAmount, @@ -118,7 +118,7 @@ impl XRPLUnsignedTx { } #[cw_serde] -#[serde(rename_all = "PascalCase")] +// #[serde(rename_all = "PascalCase")] pub struct XRPLSigner { pub account: String, pub txn_signature: HexBinary, @@ -126,18 +126,20 @@ pub struct XRPLSigner { } #[cw_serde] -#[serde(rename_all = "PascalCase")] +// #[serde(rename_all = "PascalCase")] pub struct XRPLSignedTransaction { - #[serde(flatten)] + // #[serde(flatten)] pub unsigned_tx: XRPLUnsignedTx, pub signers: Vec, } pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { - let last_assigned_ticket_number = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; + let last_assigned_ticket_number: u32 = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; + // TODO: handle no available tickets let available_tickets = AVAILABLE_TICKETS.load(storage)?; // find next largest in available, otherwise use available_tickets[0] + // TODO: handle IndexOutOfBounds error on available_tickets[0] let ticket_number = available_tickets.iter().find(|&x| x > &last_assigned_ticket_number).unwrap_or(&available_tickets[0]); Ok(*ticket_number) } @@ -155,6 +157,7 @@ fn construct_unsigned_tx( ) -> XRPLUnsignedTx { let unsigned_tx_common = XRPLTxCommonFields { account: config.xrpl_multisig_address.to_string(), + // TODO: should be at least (n+1)*num_of_signatures (which we don't know) fee: config.xrpl_fee, sequence: sequence.clone(), signing_pub_key: "".to_string(), @@ -305,13 +308,13 @@ pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result { let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx)?; - let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI, encoded_unsigned_tx.as_slice())); + let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(Some(HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI), encoded_unsigned_tx.as_slice())); let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); Ok(tx_hash) } pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result { - let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())); + let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(Some(HASH_PREFIX_SIGNED_TRANSACTION), encoded_signed_tx.as_slice())); let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); Ok(tx_hash) } @@ -319,7 +322,7 @@ pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result Result, ContractError> { let mut obj = tx_to_xrpl_object(&signed_tx.unsigned_tx)?; obj.add_field(3, &XRPLArray{ field_code: 16, items: signed_tx.signers.clone() })?; - let parts: Vec = obj.clone().fields.into_iter().map(|f| { + let parts: Vec = obj.clone().fields.into_iter().map(|f| { let mut res = Vec::new(); res.extend(field_id(f.0, f.1)); res.extend(f.2); @@ -455,7 +458,7 @@ impl XRPLSerialize for XRPLArray { } } - + // see https://github.com/XRPLF/xrpl-dev-portal/blob/master/content/_code-samples/tx-serialization/py/serialize.py#L92 // returns None if length too big pub fn encode_length(mut length: usize) -> Option> { @@ -475,7 +478,7 @@ pub fn encode_length(mut length: usize) -> Option> { return None } } - + impl XRPLSerialize for HexBinary { const TYPE_CODE: u8 = BLOB_TYPE_CODE; @@ -492,7 +495,7 @@ impl XRPLSerialize for HexBinary { } } - + impl XRPLSerialize for XRPLSigner { const TYPE_CODE: u8 = OBJECT_TYPE_CODE; @@ -503,7 +506,7 @@ impl XRPLSerialize for XRPLSigner { obj.add_field(1, &XRPLAddress(self.account.clone()))?; let mut result = obj.serialize(); result.extend(field_id(OBJECT_TYPE_CODE, 1)); - let parts: Vec = obj.clone().fields.into_iter().map(|f| { + let parts: Vec = obj.clone().fields.into_iter().map(|f| { let mut res = Vec::new(); res.extend(field_id(f.0, f.1)); res.extend(f.2); @@ -536,7 +539,7 @@ impl XRPLSerialize for XRPLSignerEntry { let mut result = obj.serialize(); result.extend(field_id(OBJECT_TYPE_CODE, 1)); - let parts: Vec = obj.clone().fields.into_iter().map(|f| { + let parts: Vec = obj.clone().fields.into_iter().map(|f| { let mut res = Vec::new(); res.extend(field_id(f.0, f.1)); res.extend(f.2); @@ -591,11 +594,15 @@ impl XRPLObject { } } +// TODO: fix to not take prefix as param pub fn xrpl_hash( - prefix: [u8; 4], + prefix: Option<[u8; 4]>, tx_blob: &[u8], ) -> [u8; 32] { - let mut hasher = Sha512::new_with_prefix(prefix); + let mut hasher = match prefix { + Some(prefix) => Sha512::new_with_prefix(prefix), + None => Sha512::new(), + }; hasher.update(tx_blob); let hash: [u8; 64] = hasher.finalize().into(); let mut half_hash: [u8; 32] = [0; 32]; @@ -642,8 +649,9 @@ fn issue_tx( fn get_next_sequence_number(storage: &dyn Storage) -> Result { let latest_sequential_tx_info = load_latest_sequential_tx_info(storage)?; - let sequence_number = if latest_sequential_tx_info.status == TransactionStatus::Pending { - latest_sequential_tx_info.unsigned_contents.common.sequence.clone().into() + // TODO: fix + let sequence_number = if latest_sequential_tx_info.is_some() && latest_sequential_tx_info.clone().unwrap().status == TransactionStatus::Pending { + latest_sequential_tx_info.unwrap().unsigned_contents.common.sequence.clone().into() } else { NEXT_SEQUENCE_NUMBER.load(storage)? }; @@ -670,9 +678,13 @@ pub fn issue_ticket_create(storage: &mut dyn Storage, config: &Config, ticket_co fn load_latest_sequential_tx_info( storage: &dyn Storage, -) -> Result { - let latest_sequential_tx_hash = LATEST_SEQUENTIAL_TX_HASH.load(storage)?; - Ok(TRANSACTION_INFO.load(storage, latest_sequential_tx_hash.clone())?) +) -> Result, ContractError> { + let latest_sequential_tx_hash = LATEST_SEQUENTIAL_TX_HASH.may_load(storage)?; + if latest_sequential_tx_hash.is_none() { + return Ok(None) + } + + Ok(TRANSACTION_INFO.may_load(storage, latest_sequential_tx_hash.unwrap())?) } pub fn issue_payment( @@ -815,7 +827,7 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: CrossChainId) return Ok(ticket_number) } - // or if it has been consumed by the same transactions. + // or if it has been consumed by the same message. let tx_info = TRANSACTION_INFO.load(storage, confirmed_tx_hash.unwrap())?; if tx_info.message_id.map_or(false, |id| id == message_id) { return Ok(ticket_number) diff --git a/contracts/xrpl-voting-verifier/src/execute.rs b/contracts/xrpl-voting-verifier/src/execute.rs index 4c8e8ea1e..95339050e 100644 --- a/contracts/xrpl-voting-verifier/src/execute.rs +++ b/contracts/xrpl-voting-verifier/src/execute.rs @@ -149,6 +149,7 @@ pub fn verify_messages( #[cw_serde] pub enum MessageStatus { Succeeded, + // TODO: FAILED OFF CHAIN NOT APPLICABLE FailedOffChain, FailedOnChain } @@ -174,12 +175,12 @@ pub fn verify_message_statuses( let source_chain = CONFIG.load(deps.storage)?.source_chain; - if message_statuses + /*if message_statuses .iter() .any(|(cc_id, _)| cc_id.chain.ne(&source_chain)) { return Err(ContractError::SourceChainMismatch(source_chain))?; - } + }*/ let config = CONFIG.load(deps.storage)?; diff --git a/contracts/xrpl-voting-verifier/tests/tests.rs b/contracts/xrpl-voting-verifier/tests/tests.rs index 9988e841a..2af422c22 100644 --- a/contracts/xrpl-voting-verifier/tests/tests.rs +++ b/contracts/xrpl-voting-verifier/tests/tests.rs @@ -7,8 +7,8 @@ use axelar_wasm_std::{nonempty, Threshold}; use connection_router::state::{ChainName, CrossChainId, Message, ID_SEPARATOR}; use mock::make_mock_rewards; use service_registry::state::Worker; -use voting_verifier::events::TxEventConfirmation; -use voting_verifier::{contract, error::ContractError, msg}; +use xrpl_voting_verifier::events::TxEventConfirmation; +use xrpl_voting_verifier::{contract, error::ContractError, msg}; use crate::mock::make_mock_service_registry; diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index a80c16d89..b2899c208 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -44,6 +44,7 @@ k256 = { version = "0.13.1", features = ["ecdsa"] } mockall = "0.11.3" multisig = { workspace = true } multisig-prover = { workspace = true } +xrpl-multisig-prover = { workspace = true } report = { workspace = true } rewards = { workspace = true } schemars = "0.8.10" @@ -53,3 +54,4 @@ service-registry = { workspace = true } thiserror = { workspace = true } tofn = { git = "https://github.com/axelarnetwork/tofn.git", branch = "update-deps" } voting-verifier = { workspace = true } +xrpl-voting-verifier = { workspace = true } diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index 9aa924ffb..5a52c69c6 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -1,9 +1,6 @@ use connection_router::state::{CrossChainId, Message}; -use cosmwasm_std::{Addr, HexBinary, Uint128}; +use cosmwasm_std::{HexBinary, Uint128}; -use crate::test_utils::AXL_DENOMINATION; -use cw_multi_test::Executor; -use test_utils::{Chain, Protocol, Worker}; mod test_utils; /// Tests that a single message can be routed fully through the protocol. Submits a message to the /// gateway, votes on the poll, routes the message to the outgoing gateway, triggers signing at the prover @@ -47,7 +44,7 @@ fn single_message_can_be_verified_and_routed_and_proven_and_rewards_are_distribu test_utils::vote_success_for_all_messages( &mut protocol.app, &chain1.voting_verifier_address, - &msgs, + msgs.len(), &workers, poll_id, ); @@ -117,3 +114,212 @@ fn single_message_can_be_verified_and_routed_and_proven_and_rewards_are_distribu assert_eq!(balance.amount, expected_rewards); } } + +#[test] +fn single_message_towards_xrpl_can_be_verified_and_routed_and_proven() { + let (mut protocol, source_chain, xrpl, workers, _) = test_utils::setup_xrpl_destination_test_case(); + + /* Create tickets */ + let session_id = test_utils::construct_xrpl_ticket_create_proof_and_sign( + &mut protocol.app, + &xrpl.multisig_prover_address, + &protocol.multisig_address, + &workers, + ); + + let proof = test_utils::get_xrpl_proof( + &mut protocol.app, + &xrpl.multisig_prover_address, + &session_id, + ); + println!("TicketCreate proof: {:?}", proof); + + test_utils::finalize_xrpl_proof( + &mut protocol.app, + &xrpl.multisig_prover_address, + &session_id, + ); + + let proof_msg_id = CrossChainId { + chain: xrpl.chain_name.clone(), + id: "DEACDB4FE9D11296BC705D6241E9D95892BDD6CFD12240A086373A7625122B63:0" + .to_string() + .try_into() + .unwrap(), + }; + + // TODO: verify_message_statuses should be called through gateway, like verify_messages + let (poll_id, expiry) = test_utils::xrpl_verify_message_statuses( + &mut protocol.app, + &xrpl.voting_verifier_address, + vec![(proof_msg_id.clone(), xrpl_voting_verifier::execute::MessageStatus::Succeeded)].to_vec(), + ); + test_utils::vote_success_for_all_messages( + &mut protocol.app, + &xrpl.voting_verifier_address, + 1, + &workers, + poll_id, + ); + test_utils::advance_at_least_to_height(&mut protocol.app, expiry); + test_utils::end_poll(&mut protocol.app, &xrpl.voting_verifier_address, poll_id); + + test_utils::xrpl_update_tx_status( + &mut protocol.app, + &xrpl.multisig_prover_address, + proof_msg_id, + xrpl_voting_verifier::execute::MessageStatus::Succeeded + ); + + assert!(matches!( + proof, + xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } + )); + + let msg = Message { + cc_id: CrossChainId { + chain: source_chain.chain_name.clone(), + id: "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924:3" + .to_string() + .try_into() + .unwrap(), + }, + // TODO: should be 0x address + source_address: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj" + .to_string() + .try_into() + .unwrap(), + destination_address: "r4ueUc4mo1X7yvmg78s5gV9uLhgLnPkJxz" + .to_string() + .try_into() + .unwrap(), + destination_chain: xrpl.chain_name.clone(), + // TODO: payload_hash? + payload_hash: HexBinary::from_hex( + "3e50a012285f8e7ec59b558179cd546c55c477ebe16202aac7d7747e25be03be", + ) + .unwrap() + .as_slice() + .try_into() + .unwrap(), + }; + let msg_id: CrossChainId = msg.cc_id.clone(); + let msgs = vec![msg.clone()]; + let msg_ids = vec![msg_id.clone()]; + + // start the flow by submitting the message to the gateway + let (poll_id, expiry) = + test_utils::verify_messages(&mut protocol.app, &source_chain.gateway_address, &msgs); + + // do voting + test_utils::vote_success_for_all_messages( + &mut protocol.app, + &source_chain.voting_verifier_address, + msgs.len(), + &workers, + poll_id, + ); + + test_utils::advance_at_least_to_height(&mut protocol.app, expiry); + + test_utils::end_poll(&mut protocol.app, &source_chain.voting_verifier_address, poll_id); + + // should be verified, now route + test_utils::route_messages(&mut protocol.app, &source_chain.gateway_address, &msgs); + + // check that the message can be found at the outgoing gateway + let found_msgs = + test_utils::get_messages_from_gateway(&mut protocol.app, &xrpl.gateway_address, &msg_ids); + assert_eq!(found_msgs, msgs); + + // trigger signing and submit all necessary signatures + let session_id = test_utils::construct_xrpl_payment_proof_and_sign( + &mut protocol.app, + &xrpl.multisig_prover_address, + &protocol.multisig_address, + msg, + &workers, + ); + + let proof = test_utils::get_xrpl_proof( + &mut protocol.app, + &xrpl.multisig_prover_address, + &session_id, + ); + println!("proof={:?}", proof); + assert!(matches!( + //proof.status, + proof, + xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } + )); + + test_utils::finalize_xrpl_proof( + &mut protocol.app, + &xrpl.multisig_prover_address, + &session_id, + ); + + let proof_msg_id = CrossChainId { + chain: xrpl.chain_name.clone(), + id: "2B779BF106E36B5FA21518E279DC6A02E26DF63115010EB40CA7B0FCD73E28C9:0" + .to_string() + .try_into() + .unwrap(), + }; + + // TODO: verify_message_statuses should be called through gateway, like verify_messages? + let (poll_id, expiry) = test_utils::xrpl_verify_message_statuses( + &mut protocol.app, + &xrpl.voting_verifier_address, + vec![(proof_msg_id.clone(), xrpl_voting_verifier::execute::MessageStatus::Succeeded)].to_vec(), + ); + test_utils::vote_success_for_all_messages( + &mut protocol.app, + &xrpl.voting_verifier_address, + 1, + &workers, + poll_id, + ); + test_utils::advance_at_least_to_height(&mut protocol.app, expiry); + test_utils::end_poll(&mut protocol.app, &xrpl.voting_verifier_address, poll_id); + + test_utils::xrpl_update_tx_status( + &mut protocol.app, + &xrpl.multisig_prover_address, + proof_msg_id, + xrpl_voting_verifier::execute::MessageStatus::Succeeded + ); + + // TODO: FIX REWARDS + /* + // Advance the height to be able to distribute rewards + test_utils::advance_height( + &mut protocol.app, + u64::from(protocol.rewards_params.epoch_duration) * 2, + ); + + test_utils::distribute_rewards( + &mut protocol.app, + &protocol.rewards_address, + &xrpl.voting_verifier_address, + ); + test_utils::distribute_rewards( + &mut protocol.app, + &protocol.rewards_address, + &protocol.multisig_address, + ); + + // rewards split evenly amongst all workers, but there are two contracts that rewards should have been distributed for + let expected_rewards = Uint128::from(protocol.rewards_params.rewards_per_epoch) + / Uint128::from(workers.len() as u64) + * Uint128::from(2u64); + + for worker in workers { + let balance = protocol + .app + .wrap() + .query_balance(worker.addr, test_utils::AXL_DENOMINATION) + .unwrap(); + assert_eq!(balance.amount, expected_rewards); + }*/ +} diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index d66254ae7..cdf7f3b8c 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use axelar_wasm_std::{ nonempty, voting::{PollId, Vote}, @@ -6,7 +8,7 @@ use axelar_wasm_std::{ use connection_router::state::{ChainName, CrossChainId, Message}; use cosmwasm_std::{ coins, Addr, Attribute, Binary, BlockInfo, Deps, Env, Event, HexBinary, StdResult, Uint128, - Uint256, Uint64, + Uint256, Uint64, Coin }; use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; @@ -17,8 +19,10 @@ use multisig::{ }; use multisig_prover::encoding::{make_operators, Encoder}; use tofn::ecdsa::KeyPair; +use xrpl_multisig_prover::types::XRPLToken; pub const AXL_DENOMINATION: &str = "uaxl"; +pub const XRP_DENOMINATION: &str = "uxrp"; fn get_event_attribute<'a>( events: &'a [Event], @@ -58,6 +62,31 @@ pub fn verify_messages( (poll_id, expiry) } +pub fn xrpl_verify_message_statuses( + app: &mut App, + voting_verifier_address: &Addr, + message_statuses: Vec<(CrossChainId, xrpl_voting_verifier::execute::MessageStatus)>, +) -> (PollId, PollExpiryBlock) { + let response = app.execute_contract( + Addr::unchecked("relayer"), + voting_verifier_address.clone(), + &xrpl_voting_verifier::msg::ExecuteMsg::VerifyMessageStatuses { + message_statuses + }, + &[], + ); + assert!(response.is_ok()); + let response = response.unwrap(); + + let poll_id = get_event_attribute(&response.events, "wasm-message_statuses_poll_started", "poll_id") + .map(|attr| serde_json::from_str(&attr.value).unwrap()) + .expect("couldn't get poll_id"); + let expiry = get_event_attribute(&response.events, "wasm-message_statuses_poll_started", "expires_at") + .map(|attr| attr.value.as_str().parse().unwrap()) + .expect("couldn't get poll expiry"); + (poll_id, expiry) +} + pub fn route_messages(app: &mut App, gateway_address: &Addr, msgs: &[Message]) { let response = app.execute_contract( Addr::unchecked("relayer"), @@ -72,7 +101,7 @@ pub fn route_messages(app: &mut App, gateway_address: &Addr, msgs: &[Message]) { pub fn vote_success_for_all_messages( app: &mut App, voting_verifier_address: &Addr, - messages: &Vec, + msg_count: usize, workers: &Vec, poll_id: PollId, ) { @@ -82,7 +111,7 @@ pub fn vote_success_for_all_messages( voting_verifier_address.clone(), &voting_verifier::msg::ExecuteMsg::Vote { poll_id, - votes: vec![Vote::SucceededOnChain; messages.len()], + votes: vec![Vote::SucceededOnChain; msg_count], }, &[], ); @@ -210,6 +239,103 @@ pub fn register_service( assert!(response.is_ok()); } +pub fn construct_xrpl_ticket_create_proof_and_sign( + app: &mut App, + multisig_prover_address: &Addr, + multisig_address: &Addr, + workers: &Vec, +) -> Uint64 { + let response = app.execute_contract( + Addr::unchecked("relayer"), + multisig_prover_address.clone(), + &xrpl_multisig_prover::contract::ExecuteMsg::TicketCreate, + &[], + ); + assert!(response.is_ok()); + let response = response.unwrap(); + + sign_xrpl_proof(app, multisig_prover_address, multisig_address, workers, response) +} + +pub fn construct_xrpl_payment_proof_and_sign( + app: &mut App, + multisig_prover_address: &Addr, + multisig_address: &Addr, + message: Message, + workers: &Vec, +) -> Uint64 { + let response = app.execute_contract( + Addr::unchecked("relayer"), + multisig_prover_address.clone(), + &xrpl_multisig_prover::contract::ExecuteMsg::ConstructProof { + message_id: message.cc_id.clone(), + }, + &[Coin { + denom: XRP_DENOMINATION.to_string(), + // amount: Uint128::MAX, + // amount: Uint128::from(10u128.pow(29)), // scaled down to 10^17 drops = max XRP + amount: Uint128::from(100000000u128) + }], + ); + assert!(response.is_ok()); + let response = response.unwrap(); + + sign_xrpl_proof(app, multisig_prover_address, multisig_address, workers, response) +} + +pub fn sign_xrpl_proof( + app: &mut App, + multisig_prover_address: &Addr, + multisig_address: &Addr, + workers: &Vec, + response: AppResponse, +) -> Uint64 { + let session_id: Uint64 = + get_event_attribute(&response.events, "wasm-signing_started", "session_id") + .map(|attr| attr.value.as_str().try_into().unwrap()) + .expect("couldn't get session_id"); + + for worker in workers { + let xrpl_signer_address = xrpl_multisig_prover::xrpl_multisig::public_key_to_xrpl_address( + multisig::key::PublicKey::Ecdsa(worker.key_pair.encoded_verifying_key().into()) + ); + + let msg_to_sign = match get_xrpl_message_to_sign( + app, + multisig_prover_address, + &session_id, + &xrpl_signer_address + ) { + xrpl_multisig_prover::contract::GetMessageToSignResponse { tx_hash } => tx_hash, + }; + + let signature = tofn::ecdsa::sign( + worker.key_pair.signing_key(), + &msg_to_sign + .as_slice() + .try_into() + .unwrap(), + ) + .unwrap(); + + // TODO: why from_der and then back to_der? + let sig = ecdsa::Signature::from_der(&signature).unwrap(); + + let response = app.execute_contract( + worker.addr.clone(), + multisig_address.clone(), + &multisig::msg::ExecuteMsg::SubmitSignature { + session_id, + signature: HexBinary::from(sig.to_vec()), + }, + &[], + ); + assert!(response.is_ok()); + } + + session_id +} + pub fn get_messages_from_gateway( app: &mut App, gateway_address: &Addr, @@ -252,6 +378,73 @@ pub fn get_worker_set( query_response.unwrap() } +pub fn get_xrpl_proof( + app: &mut App, + multisig_prover_address: &Addr, + multisig_session_id: &Uint64, +) -> xrpl_multisig_prover::contract::GetProofResponse { + let query_response = app.wrap().query_wasm_smart( + multisig_prover_address, + &xrpl_multisig_prover::contract::QueryMsg::GetProof { + multisig_session_id: *multisig_session_id, + }, + ); + assert!(query_response.is_ok()); + query_response.unwrap() +} + +pub fn get_xrpl_message_to_sign( + app: &mut App, + multisig_prover_address: &Addr, + multisig_session_id: &Uint64, + xrpl_signer_address: &String, +) -> xrpl_multisig_prover::contract::GetMessageToSignResponse { + let query_response = app.wrap().query_wasm_smart( + multisig_prover_address, + &xrpl_multisig_prover::contract::QueryMsg::GetMessageToSign { + multisig_session_id: *multisig_session_id, + signer_xrpl_address: xrpl_signer_address.clone(), + }, + ); + assert!(query_response.is_ok()); + query_response.unwrap() +} + +pub fn finalize_xrpl_proof( + app: &mut App, + multisig_prover_address: &Addr, + multisig_session_id: &Uint64, +) { + let response = app.execute_contract( + Addr::unchecked("relayer"), + multisig_prover_address.clone(), + &xrpl_multisig_prover::contract::ExecuteMsg::FinalizeProof { + multisig_session_id: *multisig_session_id, + }, + &[], + ); + + assert!(response.is_ok()); +} + +pub fn xrpl_update_tx_status( + app: &mut App, + multisig_prover_address: &Addr, + cc_id: CrossChainId, + message_status: xrpl_voting_verifier::execute::MessageStatus, +) { + let response = app.execute_contract( + Addr::unchecked("relayer"), + multisig_prover_address.clone(), + &xrpl_multisig_prover::contract::ExecuteMsg::UpdateTxStatus { + cc_id, + message_status, + }, + &[], + ); + assert!(response.is_ok()); +} + pub fn advance_height(app: &mut App, increment: u64) { let cur_block = app.block_info(); app.set_block(BlockInfo { @@ -298,11 +491,15 @@ pub struct Protocol { pub fn setup_protocol(service_name: nonempty::String) -> Protocol { let genesis = Addr::unchecked("genesis"); + // TODO: return relayer + let relayer = Addr::unchecked("relayer"); let mut app = App::new(|router, _, storage| { router .bank .init_balance(storage, &genesis, coins(u128::MAX, AXL_DENOMINATION)) - .unwrap() + .unwrap(); + // TODO: relocate + router.bank.init_balance(storage, &relayer, coins(u128::MAX, XRP_DENOMINATION)).unwrap() }); let router_admin_address = Addr::unchecked("admin"); let governance_address = Addr::unchecked("governance"); @@ -378,6 +575,7 @@ pub fn generate_key(seed: u32) -> KeyPair { tofn::ecdsa::keygen(&secret_recovery_key, b"tofn nonce").unwrap() } +#[derive(Debug)] pub struct Worker { pub addr: Addr, pub supported_chains: Vec, @@ -667,6 +865,105 @@ pub fn setup_chain(protocol: &mut Protocol, chain_name: ChainName) -> Chain { } } +pub fn setup_xrpl(protocol: &mut Protocol, chain_name: ChainName) -> Chain { + let voting_verifier_address = instantiate_xrpl_voting_verifier( + &mut protocol.app, + xrpl_voting_verifier::msg::InstantiateMsg { + service_registry_address: protocol + .service_registry_address + .to_string() + .try_into() + .unwrap(), + service_name: protocol.service_name.clone(), + source_gateway_address: "doesn't matter".to_string().try_into().unwrap(), + voting_threshold: (9, 10).try_into().unwrap(), + block_expiry: 10, + confirmation_height: 5, + source_chain: ChainName::from_str("XRPL").unwrap(), + rewards_address: protocol.rewards_address.to_string(), + }, + ); + let gateway_address = instantiate_gateway( + &mut protocol.app, + gateway::msg::InstantiateMsg { + router_address: protocol.router_address.to_string(), + verifier_address: voting_verifier_address.to_string(), + }, + ); + let multisig_prover_address = instantiate_xrpl_multisig_prover( + &mut protocol.app, + xrpl_multisig_prover::contract::InstantiateMsg { + axelar_multisig_address: protocol.multisig_address.to_string(), + gateway_address: gateway_address.to_string(), + service_registry_address: protocol.service_registry_address.to_string(), + voting_verifier_address: voting_verifier_address.to_string(), + signing_threshold: (2, 3).try_into().unwrap(), + service_name: protocol.service_name.to_string(), + worker_set_diff_threshold: 1, + xrpl_fee: 30, + xrpl_multisig_address: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), + ticket_count_threshold: 1, + next_sequence_number: 44218439, + last_assigned_ticket_number: 44218188, + available_tickets: vec![ + vec![], + (44218189..44218438).collect::>() + ].concat(), + }, + ); + + let response = protocol.app.execute_contract( + Addr::unchecked("doesn't matter"), // TODO: should fail unless admin + multisig_prover_address.clone(), + &xrpl_multisig_prover::contract::ExecuteMsg::RegisterToken { + denom: XRP_DENOMINATION.to_string(), + token: XRPLToken { + issuer: "".to_string(), + currency: XRPLToken::NATIVE_CURRENCY.to_string(), + }, + }, + &[], + ); + assert!(response.is_ok()); + let response = protocol.app.execute_contract( + protocol.governance_address.clone(), + protocol.multisig_address.clone(), + &multisig::msg::ExecuteMsg::AuthorizeCaller { + contract_address: multisig_prover_address.clone(), + }, + &[], + ); + assert!(response.is_ok()); + + let response = protocol.app.execute_contract( + protocol.governance_address.clone(), + protocol.router_address.clone(), + &connection_router::msg::ExecuteMsg::RegisterChain { + chain: chain_name.clone(), + gateway_address: gateway_address.to_string(), + }, + &[], + ); + assert!(response.is_ok()); + + let response = protocol.app.execute_contract( + protocol.genesis_address.clone(), + protocol.rewards_address.clone(), + &rewards::msg::ExecuteMsg::AddRewards { + contract_address: voting_verifier_address.to_string(), + }, + &coins(1000, AXL_DENOMINATION), + ); + assert!(response.is_ok()); + + Chain { + gateway_address, + voting_verifier_address, + multisig_prover_address, + chain_name, + } +} + pub fn instantiate_connection_router( app: &mut App, instantiate_msg: connection_router::msg::InstantiateMsg, @@ -757,6 +1054,30 @@ pub fn instantiate_voting_verifier( contract_addr.unwrap() } +pub fn instantiate_xrpl_voting_verifier( + app: &mut App, + instantiate_msg: xrpl_voting_verifier::msg::InstantiateMsg, +) -> Addr { + let code = ContractWrapper::new( + xrpl_voting_verifier::contract::execute, + xrpl_voting_verifier::contract::instantiate, + xrpl_voting_verifier::contract::query, + ); + let code_id = app.store_code(Box::new(code)); + + let contract_addr = app.instantiate_contract( + code_id, + Addr::unchecked("anyone"), + &instantiate_msg, + &[], + "xrpl_voting_verifier", + None, + ); + + assert!(contract_addr.is_ok()); + contract_addr.unwrap() +} + pub fn instantiate_gateway(app: &mut App, instantiate_msg: gateway::msg::InstantiateMsg) -> Addr { let code = ContractWrapper::new( gateway::contract::execute, @@ -869,3 +1190,71 @@ pub fn setup_test_case() -> (Protocol, Chain, Chain, Vec, Uint128) { let chain2 = setup_chain(&mut protocol, chains.get(1).unwrap().clone()); (protocol, chain1, chain2, workers, min_worker_bond) } + +pub fn setup_xrpl_destination_test_case() -> (Protocol, Chain, Chain, Vec, Uint128) { + let mut protocol = setup_protocol("validators".to_string().try_into().unwrap()); + let chains = vec![ + "Ethereum".to_string().try_into().unwrap(), + "XRPL".to_string().try_into().unwrap(), + ]; + let workers = vec![ + Worker { + addr: Addr::unchecked("worker1"), + supported_chains: chains.clone(), + key_pair: generate_key(0), + }, + Worker { + addr: Addr::unchecked("worker2"), + supported_chains: chains.clone(), + key_pair: generate_key(1), + }, + ]; + let min_worker_bond = Uint128::new(100); + register_service( + &mut protocol.app, + protocol.service_registry_address.clone(), + protocol.governance_address.clone(), + protocol.service_name.clone(), + min_worker_bond.clone(), + ); + + register_workers( + &mut protocol.app, + protocol.service_registry_address.clone(), + protocol.multisig_address.clone(), + protocol.governance_address.clone(), + protocol.genesis_address.clone(), + &workers, + protocol.service_name.clone(), + min_worker_bond, + ); + let source_chain = setup_chain(&mut protocol, chains.get(0).unwrap().clone()); + let xrpl = setup_xrpl(&mut protocol, chains.get(1).unwrap().clone()); + (protocol, source_chain, xrpl, workers, min_worker_bond) +} + +pub fn instantiate_xrpl_multisig_prover( + app: &mut App, + // TODO: should be msg::InstantiateMsg + instantiate_msg: xrpl_multisig_prover::contract::InstantiateMsg, +) -> Addr { + let code = ContractWrapper::new( + xrpl_multisig_prover::contract::execute, + xrpl_multisig_prover::contract::instantiate, + xrpl_multisig_prover::contract::query, + ) + .with_reply(xrpl_multisig_prover::contract::reply); + let code_id = app.store_code(Box::new(code)); + + let contract_addr = app.instantiate_contract( + code_id, + Addr::unchecked("anyone"), + &instantiate_msg, + &[], + "xrpl_multisig_prover", + None, + ); + + assert!(contract_addr.is_ok()); + contract_addr.unwrap() +} From decab212eceb68d9ba8f5c5544cf920f4b2e9029 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 3 Jan 2024 16:55:44 +0200 Subject: [PATCH 10/80] xrpl: cleanup xrpl_multisig module, register next worker set --- .../xrpl-multisig-prover/src/contract.rs | 13 +- contracts/xrpl-multisig-prover/src/query.rs | 6 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 700 +++++++++--------- 3 files changed, 367 insertions(+), 352 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 61c21df57..6150e6805 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -133,7 +133,7 @@ pub fn execute( construct_signer_list_set_proof(deps.storage, querier, env, &config) }, ExecuteMsg::UpdateTxStatus { cc_id, message_status } => { - update_tx_status(deps.storage, querier, cc_id, &message_status) + update_tx_status(deps.storage, querier, cc_id, &message_status, config.axelar_multisig_address) }, ExecuteMsg::TicketCreate {} => { construct_ticket_create_proof(deps.storage, env.contract.address, &config) @@ -218,7 +218,7 @@ pub fn start_signing_session( storage: &mut dyn Storage, config: &Config, tx_hash: TxHash, - self_address: Addr, + _self_address: Addr, ) -> Result { REPLY_TX_HASH.save(storage, &tx_hash)?; let cur_worker_set: multisig::worker_set::WorkerSet = CURRENT_WORKER_SET.load(storage)?.into(); @@ -304,7 +304,8 @@ fn update_tx_status( storage: &mut dyn Storage, querier: Querier, cc_id: CrossChainId, - status: &MessageStatus + status: &MessageStatus, + axelar_multisig_address: impl Into, ) -> Result { if !querier.get_message_confirmation(cc_id.clone(), status)? { return Err(ContractError::InvalidMessageStatus) @@ -312,8 +313,10 @@ fn update_tx_status( let tx_hash: TxHash = TxHash::try_from(cc_id)?; - xrpl_multisig::update_tx_status(storage, tx_hash, status.clone().into())?; - Ok(Response::default()) + match xrpl_multisig::update_tx_status(storage, axelar_multisig_address, tx_hash, status.clone().into())? { + None => Ok(Response::default()), + Some(msg) => Ok(Response::new().add_message(msg)) + } } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index fed7a5d72..ce0c809d7 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -6,7 +6,7 @@ use multisig::types::MultisigState; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use crate::{ - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self}, querier::Querier, contract::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, contract::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, }; pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { @@ -40,7 +40,7 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, return Err(ContractError::TransactionStatusNotPending.into()); } - let serialized_unsigned_tx = xrpl_multisig::serialize_unsigned_tx(&tx_info.unsigned_contents)?; + let serialized_unsigned_tx = tx_info.unsigned_contents.xrpl_serialize()?; let serialized_signer_xrpl_address = xrpl_multisig::decode_address(signer_xrpl_address)?; let serialized_tx = &[serialized_unsigned_tx, serialized_signer_xrpl_address.to_vec()].concat(); @@ -67,7 +67,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & .collect(); let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; - let tx_blob: HexBinary = HexBinary::from(xrpl_multisig::serialize_signed_tx(&signed_tx)?); + let tx_blob: HexBinary = HexBinary::from(signed_tx.xrpl_serialize()?); GetProofResponse::Completed { unsigned_tx_hash, tx_blob } } }; diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 44364ff61..5b8834b6d 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -3,38 +3,28 @@ use std::collections::BTreeSet; use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Storage, HexBinary}; +use cosmwasm_std::{Storage, HexBinary, wasm_execute, WasmMsg}; use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; use crate::{ error::ContractError, - state::{Config, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS, TRANSACTION_INFO, NEXT_SEQUENCE_NUMBER, CONFIRMED_TRANSACTIONS, MESSAGE_ID_TO_TICKET, LATEST_SEQUENTIAL_TX_HASH, SIGNED_TO_UNSIGNED_TX_HASH}, + state::{Config, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS, TRANSACTION_INFO, NEXT_SEQUENCE_NUMBER, CONFIRMED_TRANSACTIONS, MESSAGE_ID_TO_TICKET, LATEST_SEQUENTIAL_TX_HASH, SIGNED_TO_UNSIGNED_TX_HASH, NEXT_WORKER_SET, CURRENT_WORKER_SET}, types::*, axelar_workers::{WorkerSet, AxelarSigner}, }; -/*fn itoa_serialize(x: &u64, s: S) -> Result -where - S: Serializer, -{ - s.serialize_str(&x.to_string()[..]) -}*/ - #[cw_serde] pub struct XRPLTokenAmount(pub String); #[cw_serde] -// #[serde(untagged)] pub enum XRPLPaymentAmount { Drops( - // #[serde(serialize_with = "itoa_serialize")] u64, ), Token(XRPLToken, XRPLTokenAmount), } #[cw_serde] -// #[serde(untagged)] pub enum Sequence { Plain(u32), Ticket(u32), @@ -50,64 +40,53 @@ impl Into for Sequence { } #[cw_serde] -//#[serde(rename_all = "PascalCase")] -pub struct XRPLTxCommonFields { - pub account: String, // TODO: redundant here? - //#[serde(serialize_with = "itoa_serialize")] - pub fee: u64, - pub sequence: Sequence, - pub signing_pub_key: String, -} - -#[cw_serde] -// #[serde(rename_all = "PascalCase", tag = "SignerEntry")] pub struct XRPLSignerEntry { pub account: String, pub signer_weight: u16, } #[cw_serde] -//#[serde(rename_all = "PascalCase")] -pub struct XRPLUnsignedTx { - //#[serde(flatten)] - pub common: XRPLTxCommonFields, - //#[serde(flatten)] - pub partial: XRPLPartialTx, -} - -#[cw_serde] -//#[serde(tag="TransactionType")] -pub enum XRPLPartialTx { - Payment { - amount: XRPLPaymentAmount, - destination: nonempty::String, - }, - SignerListSet { - signer_quorum: u32, - signer_entries: Vec, - }, - TicketCreate { - ticket_count: u32, - }, +pub enum XRPLUnsignedTx { + Payment(XRPLPaymentTx), + SignerListSet(XRPLSignerListSetTx), + TicketCreate(XRPLTicketCreateTx), } impl XRPLUnsignedTx { + pub fn sequence(&self) -> Sequence { + match self { + XRPLUnsignedTx::Payment(tx) => { + tx.sequence.clone() + }, + XRPLUnsignedTx::TicketCreate(tx) => { + tx.sequence.clone() + }, + XRPLUnsignedTx::SignerListSet(tx) => { + tx.sequence.clone() + } + } + } pub fn sequence_number_increment(&self, status: TransactionStatus) -> u32 { if status == TransactionStatus::Pending || status == TransactionStatus::FailedOffChain { return 0; } - match self.partial { - XRPLPartialTx::Payment { .. } | - XRPLPartialTx::SignerListSet { .. } => { - match self.common.sequence { + match self { + XRPLUnsignedTx::Payment(tx ) => { + match tx.sequence { + Sequence::Plain(_) => 1, + Sequence::Ticket(_) => 0, + } + } + XRPLUnsignedTx::SignerListSet(tx) => { + match tx.sequence { Sequence::Plain(_) => 1, Sequence::Ticket(_) => 0, } }, - XRPLPartialTx::TicketCreate { ticket_count } => { + XRPLUnsignedTx::TicketCreate(tx) => { match status { - TransactionStatus::Succeeded => ticket_count + 1, + TransactionStatus::Succeeded => tx.ticket_count + 1, TransactionStatus::FailedOnChain => 1, TransactionStatus::FailedOffChain | TransactionStatus::Pending => unreachable!(), @@ -117,8 +96,102 @@ impl XRPLUnsignedTx { } } +impl TryFrom<&XRPLUnsignedTx> for XRPLObject { + type Error = ContractError; + + fn try_from(tx: &XRPLUnsignedTx) -> Result { + match tx { + XRPLUnsignedTx::Payment(tx) => tx.try_into(), + XRPLUnsignedTx::TicketCreate(tx) => tx.try_into(), + XRPLUnsignedTx::SignerListSet(tx) => tx.try_into() + } + } +} + +#[cw_serde] +pub struct XRPLPaymentTx { + pub account: String, + pub fee: u64, + pub sequence: Sequence, + pub amount: XRPLPaymentAmount, + pub destination: nonempty::String, +} + +impl TryFrom<&XRPLPaymentTx> for XRPLObject { + type Error = ContractError; + + fn try_from(tx: &XRPLPaymentTx) -> Result { + let mut obj = XRPLObject::new(); + obj.add_field(2, &PAYMENT_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags + obj.add_sequence(&tx.sequence)?; + // type: Amount, type_code: 6, nth: 1, !isVLEncoded + obj.add_field(1, &tx.amount)?; + // type: Amount, type_code: 6, nth: 8, !isVLEncoded + obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; + obj.add_field(3, &HexBinary::from_hex("")?)?; + obj.add_field(1, &XRPLAddress(tx.account.clone()))?; + obj.add_field(3, &XRPLAddress(tx.destination.to_string()))?; + + Ok(obj) + } +} + +#[cw_serde] +pub struct XRPLSignerListSetTx { + pub account: String, + pub fee: u64, + pub sequence: Sequence, + pub signer_quorum: u32, + pub signer_entries: Vec +} + +impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { + type Error = ContractError; + + fn try_from(tx: &XRPLSignerListSetTx) -> Result { + let mut obj = XRPLObject::new(); + + obj.add_field(2, &SIGNER_LIST_SET_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags + obj.add_sequence(&tx.sequence)?; + obj.add_field(35, &tx.signer_quorum)?; + obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; + obj.add_field(1, &XRPLAddress(tx.account.clone()))?; + obj.add_field(3, &HexBinary::from_hex("")?)?; + obj.add_field(4, &XRPLArray{ field_code: 11, items: tx.signer_entries.clone() })?; + Ok(obj) + } +} + + +#[cw_serde] +pub struct XRPLTicketCreateTx { + pub account: String, + pub fee: u64, + pub sequence: Sequence, + pub ticket_count: u32 +} + +impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { + type Error = ContractError; + + fn try_from(tx: &XRPLTicketCreateTx) -> Result { + let mut obj = XRPLObject::new(); + // type_code: 1, nth: 2, !isVLEncoded + obj.add_field(2, &TICKET_CREATE_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags + obj.add_sequence(&tx.sequence)?; + obj.add_field(40, &tx.ticket_count)?; // 202800000000a + obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; // 68400000000000001e + obj.add_field(3, &HexBinary::from_hex("")?)?; + obj.add_field(1, &XRPLAddress(tx.account.clone()))?; + + Ok(obj) + } +} + #[cw_serde] -// #[serde(rename_all = "PascalCase")] pub struct XRPLSigner { pub account: String, pub txn_signature: HexBinary, @@ -126,9 +199,7 @@ pub struct XRPLSigner { } #[cw_serde] -// #[serde(rename_all = "PascalCase")] pub struct XRPLSignedTransaction { - // #[serde(flatten)] pub unsigned_tx: XRPLUnsignedTx, pub signers: Vec, } @@ -150,25 +221,6 @@ pub fn available_ticket_count(storage: &mut dyn Storage) -> Result XRPLUnsignedTx { - let unsigned_tx_common = XRPLTxCommonFields { - account: config.xrpl_multisig_address.to_string(), - // TODO: should be at least (n+1)*num_of_signatures (which we don't know) - fee: config.xrpl_fee, - sequence: sequence.clone(), - signing_pub_key: "".to_string(), - }; - - XRPLUnsignedTx { - common: unsigned_tx_common, - partial: partial_unsigned_tx, - } -} - const PAYMENT_TX_TYPE: u16 = 0; const TICKET_CREATE_TX_TYPE: u16 = 10; const SIGNER_LIST_SET_TX_TYPE: u16 = 12; @@ -306,7 +358,7 @@ pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; // TODO: optimize pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result { - let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx)?; + let encoded_unsigned_tx = unsigned_tx.xrpl_serialize()?; let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(Some(HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI), encoded_unsigned_tx.as_slice())); let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); @@ -319,107 +371,39 @@ pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result Result, ContractError> { - let mut obj = tx_to_xrpl_object(&signed_tx.unsigned_tx)?; - obj.add_field(3, &XRPLArray{ field_code: 16, items: signed_tx.signers.clone() })?; - let parts: Vec = obj.clone().fields.into_iter().map(|f| { - let mut res = Vec::new(); - res.extend(field_id(f.0, f.1)); - res.extend(f.2); - return hex::encode(res); - }).collect(); - println!("signed tx parts {:?}", parts); - - Ok(obj.serialize()) -} - -pub fn serialize_unsigned_tx(tx: &XRPLUnsignedTx) -> Result, ContractError> { - let obj = tx_to_xrpl_object(tx)?; - println!("{:?}", obj.fields); - - let mut result = Vec::from((0x534D5400 as u32).to_be_bytes()); // prefix for multisignature signing - result.extend(obj.serialize()); - Ok(result) -} - -pub fn tx_to_xrpl_object(tx: &XRPLUnsignedTx) -> Result { - match &tx.partial { - XRPLPartialTx::Payment { amount, destination } => make_payment_tx_object(&tx.common, &amount, &XRPLAddress(destination.to_string())), - XRPLPartialTx::TicketCreate { ticket_count } => make_ticket_create_tx_object(&tx.common, ticket_count), - XRPLPartialTx::SignerListSet { signer_quorum, signer_entries } => make_signer_list_set_tx_object(&tx.common, signer_quorum, signer_entries), - } -} - -pub fn make_payment_tx_object(common: &XRPLTxCommonFields, amount: &XRPLPaymentAmount, destination: &XRPLAddress) -> Result { - let mut obj = XRPLObject::new(); - obj.add_field(2, &PAYMENT_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags - obj.add_sequence(&common.sequence)?; - // type: Amount, type_code: 6, nth: 1, !isVLEncoded - obj.add_field(1, amount)?; - // type: Amount, type_code: 6, nth: 8, !isVLEncoded - obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; - obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(1, &XRPLAddress(common.account.clone()))?; - obj.add_field(3, destination)?; - - Ok(obj) -} - -pub fn make_ticket_create_tx_object(common: &XRPLTxCommonFields, ticket_count: &u32) -> Result { - let mut obj = XRPLObject::new(); - // type_code: 1, nth: 2, !isVLEncoded - obj.add_field(2, &TICKET_CREATE_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags - obj.add_sequence(&common.sequence)?; - obj.add_field(40, ticket_count)?; // 202800000000a - obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; // 68400000000000001e - obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(1, &XRPLAddress(common.account.clone()))?; - - Ok(obj) -} -pub fn make_signer_list_set_tx_object(common: &XRPLTxCommonFields, signer_quorum: &u32, signer_entries: &Vec) -> Result { - let mut obj = XRPLObject::new(); - - obj.add_field(2, &SIGNER_LIST_SET_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags - obj.add_sequence(&common.sequence)?; - obj.add_field(35, signer_quorum)?; - obj.add_field(8, &XRPLPaymentAmount::Drops(common.fee))?; - obj.add_field(1, &XRPLAddress(common.account.clone()))?; - obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(4, &XRPLArray{ field_code: 11, items: signer_entries.clone() })?; - Ok(obj) -} pub struct XRPLAddress(String); pub trait XRPLSerialize { - const TYPE_CODE: u8; fn xrpl_serialize(&self) -> Result, ContractError>; } -impl XRPLSerialize for u16 { - const TYPE_CODE: u8 = UINT16_TYPE_CODE; +pub trait XRPLTypedSerialize: XRPLSerialize { + const TYPE_CODE: u8; +} +impl XRPLSerialize for u16 { fn xrpl_serialize(&self) -> Result, ContractError> { Ok(self.to_be_bytes().to_vec()) } } -impl XRPLSerialize for u32 { - const TYPE_CODE: u8 = UINT32_TYPE_CODE; +impl XRPLTypedSerialize for u16 { + const TYPE_CODE: u8 = UINT16_TYPE_CODE; +} +impl XRPLSerialize for u32 { fn xrpl_serialize(&self) -> Result, ContractError> { Ok(self.to_be_bytes().to_vec()) } } -impl XRPLSerialize for XRPLPaymentAmount { - const TYPE_CODE: u8 = AMOUNT_TYPE_CODE; +impl XRPLTypedSerialize for u32 { + const TYPE_CODE: u8 = UINT32_TYPE_CODE; +} +impl XRPLSerialize for XRPLPaymentAmount { fn xrpl_serialize(&self) -> Result, ContractError> { match self { XRPLPaymentAmount::Drops(value) => { @@ -438,14 +422,43 @@ impl XRPLSerialize for XRPLPaymentAmount { } } +impl XRPLTypedSerialize for XRPLPaymentAmount { + const TYPE_CODE: u8 = AMOUNT_TYPE_CODE; +} + +impl XRPLSerialize for XRPLSignedTransaction { + fn xrpl_serialize(self: &XRPLSignedTransaction) -> Result, ContractError> { + let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; + obj.add_field(3, &XRPLArray{ field_code: 16, items: self.signers.clone() })?; + let parts: Vec = obj.clone().fields.into_iter().map(|f| { + let mut res = Vec::new(); + res.extend(field_id(f.0, f.1)); + res.extend(f.2); + return hex::encode(res); + }).collect(); + println!("signed tx parts {:?}", parts); + + obj.xrpl_serialize() + } +} + +impl XRPLSerialize for XRPLUnsignedTx { + fn xrpl_serialize(self: &XRPLUnsignedTx) -> Result, ContractError> { + let obj = XRPLObject::try_from(self)?; + println!("{:?}", obj.fields); + + let mut result = Vec::from((0x534D5400 as u32).to_be_bytes()); // prefix for multisignature signing + result.extend(obj.xrpl_serialize()?); + Ok(result) + } +} + struct XRPLArray { field_code: u8, items: Vec } -impl XRPLSerialize for XRPLArray { - const TYPE_CODE: u8 = ARRAY_TYPE_CODE; - +impl XRPLSerialize for XRPLArray { fn xrpl_serialize(&self) -> Result, ContractError> { println!("Vec::xrpl_serialize"); let mut result: Vec = Vec::new(); @@ -458,6 +471,10 @@ impl XRPLSerialize for XRPLArray { } } +impl XRPLTypedSerialize for XRPLArray { + const TYPE_CODE: u8 = ARRAY_TYPE_CODE; +} + // see https://github.com/XRPLF/xrpl-dev-portal/blob/master/content/_code-samples/tx-serialization/py/serialize.py#L92 // returns None if length too big @@ -480,8 +497,6 @@ pub fn encode_length(mut length: usize) -> Option> { } impl XRPLSerialize for HexBinary { - const TYPE_CODE: u8 = BLOB_TYPE_CODE; - fn xrpl_serialize(&self) -> Result, ContractError> { match encode_length(self.len()) { Some(encoded_length) => { @@ -495,16 +510,17 @@ impl XRPLSerialize for HexBinary { } } +impl XRPLTypedSerialize for HexBinary { + const TYPE_CODE: u8 = BLOB_TYPE_CODE; +} impl XRPLSerialize for XRPLSigner { - const TYPE_CODE: u8 = OBJECT_TYPE_CODE; - fn xrpl_serialize(&self) -> Result, ContractError> { let mut obj = XRPLObject::new(); obj.add_field(3, &self.signing_pub_key)?; obj.add_field(4, &self.txn_signature)?; obj.add_field(1, &XRPLAddress(self.account.clone()))?; - let mut result = obj.serialize(); + let mut result = obj.xrpl_serialize()?; result.extend(field_id(OBJECT_TYPE_CODE, 1)); let parts: Vec = obj.clone().fields.into_iter().map(|f| { let mut res = Vec::new(); @@ -517,9 +533,11 @@ impl XRPLSerialize for XRPLSigner { } } -impl XRPLSerialize for XRPLAddress { - const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; +impl XRPLTypedSerialize for XRPLSigner { + const TYPE_CODE: u8 = OBJECT_TYPE_CODE; +} +impl XRPLSerialize for XRPLAddress { fn xrpl_serialize(&self) -> Result, ContractError> { let mut result: Vec = Vec::new(); result.extend(vec![20]); // 0x14, length-encoding @@ -528,28 +546,28 @@ impl XRPLSerialize for XRPLAddress { } } -impl XRPLSerialize for XRPLSignerEntry { - const TYPE_CODE: u8 = OBJECT_TYPE_CODE; +impl XRPLTypedSerialize for XRPLAddress { + const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; +} + +impl XRPLSerialize for XRPLSignerEntry { fn xrpl_serialize(&self) -> Result, ContractError> { println!("XRPLSignerEntry::xrpl_serialize"); let mut obj = XRPLObject::new(); obj.add_field(1, &XRPLAddress(self.account.clone()))?; obj.add_field(3, &self.signer_weight)?; - let mut result = obj.serialize(); + let mut result = obj.xrpl_serialize()?; result.extend(field_id(OBJECT_TYPE_CODE, 1)); - - let parts: Vec = obj.clone().fields.into_iter().map(|f| { - let mut res = Vec::new(); - res.extend(field_id(f.0, f.1)); - res.extend(f.2); - return hex::encode(res); - }).collect(); - println!("signer entry parts {:?}", parts); Ok(result) } } +impl XRPLTypedSerialize for XRPLSignerEntry { + const TYPE_CODE: u8 = OBJECT_TYPE_CODE; +} + + #[derive(Debug, Clone)] pub struct XRPLObject { fields: Vec<(u8, u8, Vec)> @@ -562,7 +580,7 @@ impl XRPLObject { } } - pub fn add_field(&mut self, field_code: u8, value: &T) -> Result<(), ContractError> { + pub fn add_field(&mut self, field_code: u8, value: &T) -> Result<(), ContractError> { self.fields.push((T::TYPE_CODE, field_code, value.xrpl_serialize()?)); Ok(()) } @@ -578,11 +596,13 @@ impl XRPLObject { } } } +} - pub fn serialize(&self) -> Vec { +impl XRPLSerialize for XRPLObject { + fn xrpl_serialize(&self) -> Result, ContractError> { let mut tmp: Vec<(u8, u8, Vec)> = self.fields.clone(); tmp.sort_by(|a, b| { (a.0, a.1).cmp(&(b.0, b.1)) }); - tmp.into_iter() + let res = tmp.into_iter() .map(|f| { let mut res = Vec::new(); res.extend(field_id(f.0, f.1)); @@ -590,10 +610,13 @@ impl XRPLObject { return res; }) .collect::>>() - .concat() + .concat(); + Ok(res) } } +// TODO: impl XRPLSerialize for all types implementing Into + // TODO: fix to not take prefix as param pub fn xrpl_hash( prefix: Option<[u8; 4]>, @@ -612,30 +635,22 @@ pub fn xrpl_hash( fn issue_tx( storage: &mut dyn Storage, - config: &Config, - partial_unsigned_tx: XRPLPartialTx, - sequence: Sequence, + tx: XRPLUnsignedTx, message_id: Option, ) -> Result { - let unsigned_tx = construct_unsigned_tx( - config, - partial_unsigned_tx, - sequence.clone(), - ); - - let tx_hash = compute_unsigned_tx_hash(&unsigned_tx)?; + let tx_hash = compute_unsigned_tx_hash(&tx)?; TRANSACTION_INFO.save( storage, tx_hash.clone(), &TransactionInfo { status: TransactionStatus::Pending, - unsigned_contents: unsigned_tx.clone(), + unsigned_contents: tx.clone(), message_id, } )?; - match sequence { + match tx.sequence() { Sequence::Ticket(ticket_number) => { LAST_ASSIGNED_TICKET_NUMBER.save(storage, &ticket_number)?; }, @@ -647,69 +662,78 @@ fn issue_tx( Ok(tx_hash) } -fn get_next_sequence_number(storage: &dyn Storage) -> Result { - let latest_sequential_tx_info = load_latest_sequential_tx_info(storage)?; - // TODO: fix - let sequence_number = if latest_sequential_tx_info.is_some() && latest_sequential_tx_info.clone().unwrap().status == TransactionStatus::Pending { - latest_sequential_tx_info.unwrap().unsigned_contents.common.sequence.clone().into() - } else { - NEXT_SEQUENCE_NUMBER.load(storage)? +pub fn issue_payment( + storage: &mut dyn Storage, + config: &Config, + destination: nonempty::String, + amount: XRPLPaymentAmount, + message_id: CrossChainId, +) -> Result { + let ticket_number = assign_ticket_number(storage, message_id.clone())?; + + let tx = XRPLPaymentTx { + account: config.xrpl_multisig_address.to_string(), + fee: config.xrpl_fee, + sequence: Sequence::Ticket(ticket_number), + amount, + destination }; - Ok(sequence_number) + + issue_tx( + storage, + XRPLUnsignedTx::Payment(tx), + Some(message_id), + ) } pub fn issue_ticket_create(storage: &mut dyn Storage, config: &Config, ticket_count: u32) -> Result { - let partial_unsigned_tx = XRPLPartialTx::TicketCreate { + let sequence_number = get_next_sequence_number(storage)?; + + let tx = XRPLTicketCreateTx { + account: config.xrpl_multisig_address.to_string(), + fee: config.xrpl_fee, + sequence: Sequence::Plain(sequence_number.clone()), ticket_count, }; - let sequence_number = get_next_sequence_number(storage)?; - - let tx_hash = issue_tx( + issue_tx( storage, - config, - partial_unsigned_tx, - Sequence::Plain(sequence_number), + XRPLUnsignedTx::TicketCreate(tx), None, - )?; - - Ok(tx_hash) + ) } -fn load_latest_sequential_tx_info( - storage: &dyn Storage, -) -> Result, ContractError> { - let latest_sequential_tx_hash = LATEST_SEQUENTIAL_TX_HASH.may_load(storage)?; - if latest_sequential_tx_hash.is_none() { - return Ok(None) - } - - Ok(TRANSACTION_INFO.may_load(storage, latest_sequential_tx_hash.unwrap())?) -} +pub fn issue_signer_list_set(storage: &mut dyn Storage, config: &Config, workers: WorkerSet) -> Result { + let sequence_number = get_next_sequence_number(storage)?; -pub fn issue_payment( - storage: &mut dyn Storage, - config: &Config, - destination: nonempty::String, - amount: XRPLPaymentAmount, - message_id: CrossChainId, -) -> Result { - let partial_unsigned_tx = XRPLPartialTx::Payment { - destination, - amount, + let tx = XRPLSignerListSetTx { + account: config.xrpl_multisig_address.to_string(), + fee: config.xrpl_fee, + sequence: Sequence::Plain(sequence_number.clone()), + signer_quorum: workers.quorum, + signer_entries: make_xrpl_signer_entries(workers.signers), }; - let ticket_number = assign_ticket_number(storage, message_id.clone())?; - issue_tx( storage, - config, - partial_unsigned_tx, - Sequence::Ticket(ticket_number), - Some(message_id), + XRPLUnsignedTx::SignerListSet(tx), + None, ) } +fn make_xrpl_signer_entries(signers: BTreeSet) -> Vec { + signers + .into_iter() + .map( + |worker| { + XRPLSignerEntry { + account: public_key_to_xrpl_address(worker.pub_key), + signer_weight: worker.weight, + } + } + ).collect() +} + pub fn public_key_to_xrpl_address(public_key: multisig::key::PublicKey) -> String { let public_key_hex: HexBinary = public_key.into(); @@ -730,33 +754,25 @@ pub fn public_key_to_xrpl_address(public_key: multisig::key::PublicKey) -> Strin .into_string() } -pub fn make_xrpl_signer_entries(signers: BTreeSet) -> Vec { - signers - .into_iter() - .map( - |worker| { - XRPLSignerEntry { - account: public_key_to_xrpl_address(worker.pub_key), - signer_weight: worker.weight, - } - } - ).collect() + +fn get_next_sequence_number(storage: &dyn Storage) -> Result { + match load_latest_sequential_tx_info(storage)? { + Some(latest_sequential_tx_info) if latest_sequential_tx_info.status == TransactionStatus::Pending => { + Ok(latest_sequential_tx_info.unsigned_contents.sequence().into()) + }, + _ => NEXT_SEQUENCE_NUMBER.load(storage).map_err(|e| e.into()) + } } -pub fn issue_signer_list_set(storage: &mut dyn Storage, config: &Config, workers: WorkerSet) -> Result { - let partial_unsigned_tx = XRPLPartialTx::SignerListSet { - signer_quorum: workers.quorum, - signer_entries: make_xrpl_signer_entries(workers.signers), - }; +fn load_latest_sequential_tx_info( + storage: &dyn Storage, +) -> Result, ContractError> { + let latest_sequential_tx_hash = LATEST_SEQUENTIAL_TX_HASH.may_load(storage)?; + if latest_sequential_tx_hash.is_none() { + return Ok(None) + } - let sequence_number = get_next_sequence_number(storage)?; - issue_tx( - storage, - config, - partial_unsigned_tx, - Sequence::Plain(sequence_number), - None, - ) + Ok(TRANSACTION_INFO.may_load(storage, latest_sequential_tx_hash.unwrap())?) } fn mark_tickets_available(storage: &mut dyn Storage, tickets: impl Iterator) -> Result<(), ContractError> { @@ -781,7 +797,7 @@ fn mark_ticket_unavailable(storage: &mut dyn Storage, ticket: u32) -> Result<(), Ok(()) } -pub fn update_tx_status(storage: &mut dyn Storage, tx_hash: TxHash, new_status: TransactionStatus) -> Result<(), ContractError> { +pub fn update_tx_status(storage: &mut dyn Storage, axelar_multisig_address: impl Into, tx_hash: TxHash, new_status: TransactionStatus) -> Result, ContractError> { let unsigned_tx_hash = SIGNED_TO_UNSIGNED_TX_HASH.load(storage, tx_hash.clone())?; let mut tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; if tx_info.status != TransactionStatus::Pending { @@ -790,15 +806,7 @@ pub fn update_tx_status(storage: &mut dyn Storage, tx_hash: TxHash, new_status: tx_info.status = new_status.clone(); - let tx_sequence_number: u32 = tx_info.unsigned_contents.common.sequence.clone().into(); - if let XRPLPartialTx::TicketCreate { ticket_count } = tx_info.unsigned_contents.partial { - if tx_info.status == TransactionStatus::Succeeded { - mark_tickets_available( - storage, - (tx_sequence_number + 1)..(tx_sequence_number + ticket_count), - )?; - } - } + let tx_sequence_number: u32 = tx_info.unsigned_contents.sequence().clone().into(); let sequence_number_increment = tx_info.unsigned_contents.sequence_number_increment(new_status.clone()); if sequence_number_increment > 0 && tx_sequence_number == NEXT_SEQUENCE_NUMBER.load(storage)? { @@ -810,8 +818,38 @@ pub fn update_tx_status(storage: &mut dyn Storage, tx_hash: TxHash, new_status: mark_ticket_unavailable(storage, tx_sequence_number)?; } - TRANSACTION_INFO.save(storage, unsigned_tx_hash, &tx_info)?; - Ok(()) + TRANSACTION_INFO.save(storage, unsigned_tx_hash.clone(), &tx_info)?; + + if tx_info.status != TransactionStatus::Succeeded { + return Ok(None); + } + + let res = match tx_info.unsigned_contents.clone() { + XRPLUnsignedTx::TicketCreate(tx) => { + mark_tickets_available( + storage, + (tx_sequence_number + 1)..(tx_sequence_number + tx.ticket_count), + )?; + None + }, + XRPLUnsignedTx::SignerListSet(_tx) => { + let next_worker_set = NEXT_WORKER_SET.load(storage, unsigned_tx_hash.clone())?; + CURRENT_WORKER_SET.save(storage, &next_worker_set)?; + NEXT_WORKER_SET.remove(storage, unsigned_tx_hash); + + let msg = wasm_execute( + axelar_multisig_address, + &multisig::msg::ExecuteMsg::RegisterWorkerSet { + worker_set: next_worker_set.into(), + }, + vec![], + )?; + Some(msg) + }, + XRPLUnsignedTx::Payment(_) => None + }; + + Ok(res) } // A message ID can be ticketed a different ticket number @@ -853,8 +891,7 @@ mod tests { common: XRPLTxCommonFields { account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), fee: 12, - sequence: Sequence::Plain(1), - signing_pub_key: "".to_string(), + sequence: Sequence::Plain(1) }, partial: XRPLPartialTx::Payment { amount: XRPLPaymentAmount::Token( @@ -877,37 +914,27 @@ mod tests { #[test] fn serialize_xrpl_unsigned_xrp_payment_transaction() { - let unsigned_tx = XRPLUnsignedTx { - common: XRPLTxCommonFields { - account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - fee: 10, - sequence: Sequence::Plain(1), - signing_pub_key: "".to_string(), - }, - partial: XRPLPartialTx::Payment { - amount: XRPLPaymentAmount::Drops(1000), - destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), - } + let tx = XRPLPaymentTx { + account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + fee: 10, + sequence: Sequence::Plain(1), + amount: XRPLPaymentAmount::Drops(1000), + destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), }; - let encoded_unsigned_tx = serialize_unsigned_tx(&unsigned_tx).unwrap(); + let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); assert_eq!( "534D5400120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", hex::encode_upper(encoded_unsigned_tx) ); - let unsigned_tx = XRPLUnsignedTx { - common: XRPLTxCommonFields { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), - fee: 3, - sequence: Sequence::Plain(43497363), - signing_pub_key: "".to_string(), - }, - partial: XRPLPartialTx::Payment { - amount: XRPLPaymentAmount::Drops(1000000000), - destination: nonempty::String::try_from("rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq").unwrap(), - } + let tx = XRPLPaymentTx { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + fee: 3, + sequence: Sequence::Plain(43497363), + amount: XRPLPaymentAmount::Drops(1000000000), + destination: nonempty::String::try_from("rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq").unwrap(), }; - let encoded_unsigned_tx = serialize_unsigned_tx(&unsigned_tx).unwrap(); + let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); assert_eq!( "534D54001200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6", hex::encode_upper(encoded_unsigned_tx) @@ -917,18 +944,13 @@ mod tests { #[test] fn serialize_xrpl_signed_xrp_payment_transaction() { let signed_tx = XRPLSignedTransaction { - unsigned_tx: XRPLUnsignedTx { - common: XRPLTxCommonFields { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), - fee: 30, - sequence: Sequence::Plain(43497365), - signing_pub_key: "".to_string(), - }, - partial: XRPLPartialTx::Payment { - amount: XRPLPaymentAmount::Drops(1000000000), - destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), - } - }, signers: vec![ + unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + fee: 30, + sequence: Sequence::Plain(43497365), + amount: XRPLPaymentAmount::Drops(1000000000), + destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), + }), signers: vec![ XRPLSigner{ account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), txn_signature: HexBinary::from(hex::decode("00CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B").unwrap()), @@ -941,7 +963,7 @@ mod tests { } ] }; - let encoded_signed_tx = serialize_signed_tx(&signed_tx).unwrap(); + let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); assert_eq!( "1200002200000000240297B79561400000003B9ACA0068400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689A831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9744000CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744062B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE028114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", hex::encode_upper(encoded_signed_tx) @@ -951,17 +973,12 @@ mod tests { #[test] fn serialize_xrpl_signed_xrp_ticket_create_transaction() { let signed_tx = XRPLSignedTransaction { - unsigned_tx: XRPLUnsignedTx { - common: XRPLTxCommonFields { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), - fee: 30, - sequence: Sequence::Plain(43497367), - signing_pub_key: "".to_string(), - }, - partial: XRPLPartialTx::TicketCreate { - ticket_count: 10 - } - }, signers: vec![ + unsigned_tx: XRPLUnsignedTx::TicketCreate(XRPLTicketCreateTx { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + fee: 30, + sequence: Sequence::Plain(43497367), + ticket_count: 10 + }), signers: vec![ XRPLSigner{ account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), txn_signature: HexBinary::from(hex::decode("3C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F551403").unwrap()), @@ -974,7 +991,7 @@ mod tests { }, ] }; - let encoded_signed_tx = serialize_signed_tx(&signed_tx).unwrap(); + let encoded_signed_tx = signed_tx.xrpl_serialize().unwrap(); assert_eq!( "12000A2200000000240297B79720280000000A68400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689AF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F974403C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F5514038114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B43574405EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC048114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", hex::encode_upper(encoded_signed_tx) @@ -985,27 +1002,22 @@ mod tests { #[test] fn serialize_xrpl_signed_signer_list_set_transaction() { let signed_tx = XRPLSignedTransaction { - unsigned_tx: XRPLUnsignedTx { - common: XRPLTxCommonFields { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), - fee: 30, - sequence: Sequence::Plain(43497378), - signing_pub_key: "".to_string(), - }, - partial: XRPLPartialTx::SignerListSet { - signer_quorum: 3, - signer_entries: vec![ - XRPLSignerEntry{ - account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), - signer_weight: 2 - }, - XRPLSignerEntry{ - account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), - signer_weight: 1 - } - ] - } - }, signers: vec![ + unsigned_tx: XRPLUnsignedTx::SignerListSet(XRPLSignerListSetTx { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + fee: 30, + sequence: Sequence::Plain(43497378), + signer_quorum: 3, + signer_entries: vec![ + XRPLSignerEntry{ + account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), + signer_weight: 2 + }, + XRPLSignerEntry{ + account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), + signer_weight: 1 + } + ] + }), signers: vec![ XRPLSigner{ account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), txn_signature: HexBinary::from(hex::decode("F4EE6E7AE1359360C16FF774EB907C908AE5E717A4DAAA8BF0C5A754A544B9690118E18EB9ABF2CE41A4853F346321F4624089AE803EB869B49F3ED506139A0E").unwrap()), @@ -1018,7 +1030,7 @@ mod tests { }, ] }; - let encoded_signed_tx = serialize_signed_tx(&signed_tx).unwrap(); + let encoded_signed_tx = signed_tx.xrpl_serialize().unwrap(); assert_eq!( "12000C2200000000240297B7A220230000000368400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689AF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F97440F4EE6E7AE1359360C16FF774EB907C908AE5E717A4DAAA8BF0C5A754A544B9690118E18EB9ABF2CE41A4853F346321F4624089AE803EB869B49F3ED506139A0E8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744086B8AF804C7F4881E125F4F876C9EC292EEF811D572D4D4BA7C6CD533B13FB1B9A31ADB4A71DD54405135BEFCDEF3A98564479B681242250D42154A93EB1FE048114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1F4EB1300028114DCE722505E32B29932618C5C9819AAEA03754AA5E1EB1300018114310A592CA22E8B35B819464F8A581A36C91DE857E1F1", hex::encode_upper(encoded_signed_tx) From 4dd3cca774bd0ba07baa3de6041467d6afc7e8af Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 3 Jan 2024 17:23:43 +0200 Subject: [PATCH 11/80] xrpl-multisig-prover: Add GetWorkerSet query & write UpdateWorkerSet integration test --- .../xrpl-multisig-prover/src/contract.rs | 5 + contracts/xrpl-multisig-prover/src/query.rs | 6 +- integration-tests/tests/message_routing.rs | 3 +- integration-tests/tests/test_utils/mod.rs | 69 ++++++++++- integration-tests/tests/update_worker_set.rs | 117 +++++++++++++++++- 5 files changed, 194 insertions(+), 6 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 6150e6805..c5767f65e 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -338,8 +338,12 @@ pub fn reply( pub enum QueryMsg { #[returns(GetProofResponse)] GetProof { multisig_session_id: Uint64 }, + #[returns(GetMessageToSignResponse)] GetMessageToSign { multisig_session_id: Uint64, signer_xrpl_address: String }, + + #[returns(multisig::worker_set::WorkerSet)] + GetWorkerSet, } #[cw_serde] @@ -366,5 +370,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { multisig_session_id, signer_xrpl_address, } => to_binary(&query::get_message_to_sign(deps.storage, &multisig_session_id, &signer_xrpl_address)?), + QueryMsg::GetWorkerSet {} => to_binary(&query::get_worker_set(deps.storage)?), } } diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index ce0c809d7..3a6e41d82 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -6,7 +6,7 @@ use multisig::types::MultisigState; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use crate::{ - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, contract::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, contract::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, axelar_workers::WorkerSet, }; pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { @@ -74,3 +74,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & Ok(response) } + +pub fn get_worker_set(storage: &dyn Storage) -> StdResult { + Ok(CURRENT_WORKER_SET.load(storage)?.into()) +} diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index 5a52c69c6..cac9aeacf 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -148,7 +148,6 @@ fn single_message_towards_xrpl_can_be_verified_and_routed_and_proven() { .unwrap(), }; - // TODO: verify_message_statuses should be called through gateway, like verify_messages let (poll_id, expiry) = test_utils::xrpl_verify_message_statuses( &mut protocol.app, &xrpl.voting_verifier_address, @@ -246,7 +245,7 @@ fn single_message_towards_xrpl_can_be_verified_and_routed_and_proven() { &xrpl.multisig_prover_address, &session_id, ); - println!("proof={:?}", proof); + println!("Payment proof: {:?}", proof); assert!(matches!( //proof.status, proof, diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index cdf7f3b8c..cfb6a8d40 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{str::FromStr, ops::Add}; use axelar_wasm_std::{ nonempty, @@ -67,6 +67,7 @@ pub fn xrpl_verify_message_statuses( voting_verifier_address: &Addr, message_statuses: Vec<(CrossChainId, xrpl_voting_verifier::execute::MessageStatus)>, ) -> (PollId, PollExpiryBlock) { + // TODO: should be verified through the gateway let response = app.execute_contract( Addr::unchecked("relayer"), voting_verifier_address.clone(), @@ -283,6 +284,23 @@ pub fn construct_xrpl_payment_proof_and_sign( sign_xrpl_proof(app, multisig_prover_address, multisig_address, workers, response) } +pub fn construct_xrpl_signer_list_set_proof_and_sign( + app: &mut App, + multisig_prover_address: &Addr, + multisig_address: &Addr, + workers: &Vec, +) -> Uint64 { + let response = app.execute_contract( + Addr::unchecked("relayer"), + multisig_prover_address.clone(), + &xrpl_multisig_prover::contract::ExecuteMsg::UpdateWorkerSet, + &[], + ); + let response = response.unwrap(); + + sign_xrpl_proof(app, multisig_prover_address, multisig_address, workers, response) +} + pub fn sign_xrpl_proof( app: &mut App, multisig_prover_address: &Addr, @@ -378,6 +396,18 @@ pub fn get_worker_set( query_response.unwrap() } +pub fn get_xrpl_worker_set( + app: &mut App, + multisig_prover_address: &Addr, +) -> multisig::worker_set::WorkerSet { + let query_response = app.wrap().query_wasm_smart( + multisig_prover_address, + &xrpl_multisig_prover::contract::QueryMsg::GetWorkerSet, + ); + assert!(query_response.is_ok()); + query_response.unwrap() +} + pub fn get_xrpl_proof( app: &mut App, multisig_prover_address: &Addr, @@ -769,6 +799,41 @@ pub fn workers_to_worker_set(protocol: &mut Protocol, workers: &Vec) -> ) } +pub fn xrpl_workers_to_worker_set(protocol: &mut Protocol, workers: &Vec) -> WorkerSet { + // get public keys + let mut pub_keys = vec![]; + for worker in workers { + let encoded_verifying_key = + HexBinary::from(worker.key_pair.encoded_verifying_key().to_vec()); + let pub_key = PublicKey::try_from((KeyType::Ecdsa, encoded_verifying_key)).unwrap(); + pub_keys.push(pub_key); + } + + // turn into participants + let participants: Vec = workers + .iter() + .map(|worker| Participant { + address: worker.addr.clone(), + weight: Uint256::from(65535u128).try_into().unwrap(), + }) + .collect(); + + let total_weight = participants + .iter() + .fold( + Uint256::zero(), + |acc, p| acc.add(Uint256::from(p.weight)) + ); + + let pubkeys_by_participant = participants.into_iter().zip(pub_keys).collect(); + + WorkerSet::new( + pubkeys_by_participant, + total_weight.mul_ceil((2u64, 3u64)).into(), + protocol.app.block_info().height, + ) +} + #[derive(Clone)] pub struct Chain { pub gateway_address: Addr, @@ -903,7 +968,7 @@ pub fn setup_xrpl(protocol: &mut Protocol, chain_name: ChainName) -> Chain { xrpl_fee: 30, xrpl_multisig_address: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), ticket_count_threshold: 1, - next_sequence_number: 44218439, + next_sequence_number: 44218441, last_assigned_ticket_number: 44218188, available_tickets: vec![ vec![], diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index 5ac278db8..fdc5e7b5f 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -1,4 +1,4 @@ -use connection_router::Message; +use connection_router::{Message, state::CrossChainId}; use cosmwasm_std::Addr; use test_utils::Worker; @@ -118,6 +118,121 @@ fn worker_set_can_be_initialized_and_then_manually_updated() { assert_eq!(new_worker_set, expected_new_worker_set); } +#[test] +fn xrpl_worker_set_can_be_initialized_and_then_manually_updated() { + let chains: Vec = vec![ + "Ethereum".to_string().try_into().unwrap(), + "XRPL".to_string().try_into().unwrap(), + ]; + let (mut protocol, _, xrpl, initial_workers, min_worker_bond) = + test_utils::setup_xrpl_destination_test_case(); + + let simulated_worker_set = test_utils::xrpl_workers_to_worker_set(&mut protocol, &initial_workers); + + let worker_set = + test_utils::get_xrpl_worker_set(&mut protocol.app, &xrpl.multisig_prover_address); + + assert_eq!(worker_set, simulated_worker_set); + + // add third and fourth worker + let mut new_workers = Vec::new(); + let new_worker = Worker { + addr: Addr::unchecked("worker3"), + supported_chains: chains.clone(), + key_pair: test_utils::generate_key(2), + }; + new_workers.push(new_worker); + let new_worker = Worker { + addr: Addr::unchecked("worker4"), + supported_chains: chains.clone(), + key_pair: test_utils::generate_key(3), + }; + new_workers.push(new_worker); + + let expected_new_worker_set = test_utils::xrpl_workers_to_worker_set(&mut protocol, &new_workers); + + test_utils::register_workers( + &mut protocol.app, + protocol.service_registry_address.clone(), + protocol.multisig_address.clone(), + protocol.governance_address.clone(), + protocol.genesis_address.clone(), + &new_workers, + protocol.service_name.clone(), + min_worker_bond, + ); + + // remove old workers + test_utils::deregister_workers( + &mut protocol.app, + protocol.service_registry_address.clone(), + protocol.governance_address.clone(), + &initial_workers, + protocol.service_name.clone(), + ); + + let session_id = test_utils::construct_xrpl_signer_list_set_proof_and_sign( + &mut protocol.app, + &xrpl.multisig_prover_address, + &protocol.multisig_address, + &initial_workers, + ); + + let proof = test_utils::get_xrpl_proof( + &mut protocol.app, + &xrpl.multisig_prover_address, + &session_id, + ); + println!("SignerListSet proof: {:?}", proof); + + test_utils::finalize_xrpl_proof( + &mut protocol.app, + &xrpl.multisig_prover_address, + &session_id, + ); + + let proof_msg_id = CrossChainId { + chain: xrpl.chain_name.clone(), + id: "3387D15000A9191024FBA1789A2D23BCDDF93C7D268129EA7023E6115F4DF07B:0" + .to_string() + .try_into() + .unwrap(), + }; + + // TODO: verify_message_statuses should be called through gateway, like verify_messages + let (poll_id, expiry) = test_utils::xrpl_verify_message_statuses( + &mut protocol.app, + &xrpl.voting_verifier_address, + vec![(proof_msg_id.clone(), xrpl_voting_verifier::execute::MessageStatus::Succeeded)].to_vec(), + ); + test_utils::vote_success_for_all_messages( + &mut protocol.app, + &xrpl.voting_verifier_address, + 1, + &new_workers, + poll_id, + ); + test_utils::advance_at_least_to_height(&mut protocol.app, expiry); + test_utils::end_poll(&mut protocol.app, &xrpl.voting_verifier_address, poll_id); + + test_utils::xrpl_update_tx_status( + &mut protocol.app, + &xrpl.multisig_prover_address, + proof_msg_id, + xrpl_voting_verifier::execute::MessageStatus::Succeeded + ); + + assert!(matches!( + proof, + xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } + )); + + let new_worker_set = + test_utils::get_worker_set(&mut protocol.app, &xrpl.multisig_prover_address); + + assert_eq!(new_worker_set, expected_new_worker_set); +} + #[test] fn worker_set_can_be_initialized_and_then_automatically_updated_during_proof_construction() { let chains = vec![ From 0925066d93e1e947032061128744f584a52102d3 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 3 Jan 2024 17:34:15 +0200 Subject: [PATCH 12/80] xrpl-multisig-prover: separate TicketCreate & Payment integration tests --- integration-tests/tests/message_routing.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index cac9aeacf..3dd53ce34 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -116,8 +116,8 @@ fn single_message_can_be_verified_and_routed_and_proven_and_rewards_are_distribu } #[test] -fn single_message_towards_xrpl_can_be_verified_and_routed_and_proven() { - let (mut protocol, source_chain, xrpl, workers, _) = test_utils::setup_xrpl_destination_test_case(); +fn xrpl_ticket_create_can_be_proven() { + let (mut protocol, _, xrpl, workers, _) = test_utils::setup_xrpl_destination_test_case(); /* Create tickets */ let session_id = test_utils::construct_xrpl_ticket_create_proof_and_sign( @@ -132,6 +132,10 @@ fn single_message_towards_xrpl_can_be_verified_and_routed_and_proven() { &xrpl.multisig_prover_address, &session_id, ); + assert!(matches!( + proof, + xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } + )); println!("TicketCreate proof: {:?}", proof); test_utils::finalize_xrpl_proof( @@ -142,7 +146,7 @@ fn single_message_towards_xrpl_can_be_verified_and_routed_and_proven() { let proof_msg_id = CrossChainId { chain: xrpl.chain_name.clone(), - id: "DEACDB4FE9D11296BC705D6241E9D95892BDD6CFD12240A086373A7625122B63:0" + id: "B0660BA5DC6C96B20D4E4DC19E2BF421B0F7D9E62FDC3B3F2AFCB322A6E3582D:0" .to_string() .try_into() .unwrap(), @@ -169,11 +173,11 @@ fn single_message_towards_xrpl_can_be_verified_and_routed_and_proven() { proof_msg_id, xrpl_voting_verifier::execute::MessageStatus::Succeeded ); +} - assert!(matches!( - proof, - xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } - )); +#[test] +fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { + let (mut protocol, source_chain, xrpl, workers, _) = test_utils::setup_xrpl_destination_test_case(); let msg = Message { cc_id: CrossChainId { From 66434b208023484305f07351282c732dd9a6d8fd Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 3 Jan 2024 17:35:15 +0200 Subject: [PATCH 13/80] xrpl-multisig-prover: relocate UpdateWorkerSet integration test assertion --- integration-tests/tests/update_worker_set.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index fdc5e7b5f..5f2947fae 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -183,6 +183,10 @@ fn xrpl_worker_set_can_be_initialized_and_then_manually_updated() { &xrpl.multisig_prover_address, &session_id, ); + assert!(matches!( + proof, + xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } + )); println!("SignerListSet proof: {:?}", proof); test_utils::finalize_xrpl_proof( @@ -222,11 +226,6 @@ fn xrpl_worker_set_can_be_initialized_and_then_manually_updated() { xrpl_voting_verifier::execute::MessageStatus::Succeeded ); - assert!(matches!( - proof, - xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } - )); - let new_worker_set = test_utils::get_worker_set(&mut protocol.app, &xrpl.multisig_prover_address); From d8aa0c0e627c1f6ad7e607a4dd22ae196eb99691 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 3 Jan 2024 17:48:59 +0200 Subject: [PATCH 14/80] xrpl: fix serializing token payments and enable tests --- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 5b8834b6d..f1eda760e 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -292,8 +292,8 @@ pub fn amount_to_bytes(amount: &XRPLTokenAmount) -> Result, ContractErro return Ok(Vec::from(serial.to_be_bytes())) } - let mut exponent: i32 = fractional_part.to_string().len() as i32; - let mut mantissa: u128 = (integer_part as u128) * 10u128.pow(exponent as u32) + (fractional_part as u128); + let mut exponent: i32 = -1 * fractional_part.to_string().len() as i32; + let mut mantissa: u128 = (integer_part as u128) * 10u128.pow((-1 * exponent) as u32) + (fractional_part as u128); while mantissa < MIN_MANTISSA && exponent > MIN_EXPONENT { mantissa *= 10; @@ -344,7 +344,6 @@ pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { pub fn decode_address(address: &String) -> Result<[u8; 20], ContractError> { let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); // .map_err(|_| ContractError::InvalidAddress)?; - println!("decoded {:?} {}", res, res.len()); if res.len() != 25 { return Err(ContractError::InvalidAddress); } @@ -436,7 +435,6 @@ impl XRPLSerialize for XRPLSignedTransaction { res.extend(f.2); return hex::encode(res); }).collect(); - println!("signed tx parts {:?}", parts); obj.xrpl_serialize() } @@ -445,7 +443,6 @@ impl XRPLSerialize for XRPLSignedTransaction { impl XRPLSerialize for XRPLUnsignedTx { fn xrpl_serialize(self: &XRPLUnsignedTx) -> Result, ContractError> { let obj = XRPLObject::try_from(self)?; - println!("{:?}", obj.fields); let mut result = Vec::from((0x534D5400 as u32).to_be_bytes()); // prefix for multisignature signing result.extend(obj.xrpl_serialize()?); @@ -460,7 +457,6 @@ struct XRPLArray { impl XRPLSerialize for XRPLArray { fn xrpl_serialize(&self) -> Result, ContractError> { - println!("Vec::xrpl_serialize"); let mut result: Vec = Vec::new(); for item in &self.items { result.extend(field_id(T::TYPE_CODE, self.field_code)); @@ -528,7 +524,6 @@ impl XRPLSerialize for XRPLSigner { res.extend(f.2); return hex::encode(res); }).collect(); - println!("signer parts {:?}", parts); Ok(result) } } @@ -553,7 +548,6 @@ impl XRPLTypedSerialize for XRPLAddress { impl XRPLSerialize for XRPLSignerEntry { fn xrpl_serialize(&self) -> Result, ContractError> { - println!("XRPLSignerEntry::xrpl_serialize"); let mut obj = XRPLObject::new(); obj.add_field(1, &XRPLAddress(self.account.clone()))?; obj.add_field(3, &self.signer_weight)?; @@ -884,33 +878,27 @@ mod tests { use super::*; - /* #[test] fn serialize_xrpl_unsigned_token_payment_transaction() { - let unsigned_tx = XRPLUnsignedTx { - common: XRPLTxCommonFields { - account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - fee: 12, - sequence: Sequence::Plain(1) - }, - partial: XRPLPartialTx::Payment { - amount: XRPLPaymentAmount::Token( - XRPLToken { - currency: "JPY".to_string(), - issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".to_string(), - }, - XRPLTokenAmount("0.3369568318".to_string()), - ), - destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), - } + let unsigned_tx = XRPLPaymentTx { + account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + fee: 12, + sequence: Sequence::Plain(1), + amount: XRPLPaymentAmount::Token( + XRPLToken { + currency: "JPY".to_string(), + issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".to_string(), + }, + XRPLTokenAmount("0.3369568318".to_string()), + ), + destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), }; - let encoded_unsigned_tx = serialize_unsigned_tx(unsigned_tx).unwrap(); + let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize().unwrap(); assert_eq!( "534D54001200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", hex::encode_upper(encoded_unsigned_tx) ); } - */ #[test] fn serialize_xrpl_unsigned_xrp_payment_transaction() { From fa8111dfd8cfe90aa959cf7725a2e8f33874181a Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 3 Jan 2024 19:00:00 +0200 Subject: [PATCH 15/80] xrpl: remove commented out code --- .../src/axelar_workers.rs | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index fd8a16c67..3fe534077 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -52,46 +52,7 @@ impl Into for WorkerSet { } } -/* -fn convert_u32_to_nonempty_uint256(value: u32) -> nonempty::Uint256 { - nonempty::Uint256::try_from(Uint256::from(value as u128)).unwrap() -} - -impl Into for WorkerSet { - fn into(self) -> Snapshot { - Snapshot { - quorum: convert_u32_to_nonempty_uint256(self.quorum), - participants: self.signers - .into_iter() - .map(|signer| (signer.address.to_string(), signer.into())) - .collect(), - } - } -} -*/ - impl WorkerSet { - /*pub fn new( - participants: Vec<(Participant, PublicKey)>, - threshold: u32, - block_height: u64, - ) -> Self { - let signers = participants - .into_iter() - .map(|(participant, pub_key)| Signer { - address: participant.address.clone(), - weight: participant.weight.into(), - pub_key, - }) - .collect(); - - WorkerSet { - signers, - threshold, - created_at: block_height, - } - }*/ - pub fn pub_keys_by_address(&self) -> HashMap { self .signers @@ -105,16 +66,6 @@ impl WorkerSet { }) .collect() } - - /*pub fn hash(&self) -> HexBinary { - Keccak256::digest(serde_json::to_vec(&self).expect("couldn't serialize worker set")) - .as_slice() - .into() - } - - pub fn id(&self) -> String { - self.hash().to_hex() - }*/ } fn convert_uint256_to_u16_unsafely(value: Uint256) -> u16 { From 1033d2fd516694839b9ec7c06ac43c6b5809cd65 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 3 Jan 2024 19:26:15 +0200 Subject: [PATCH 16/80] xrpl: minor fix remove unwrap function call --- contracts/xrpl-multisig-prover/src/query.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 3a6e41d82..672d79240 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -12,19 +12,19 @@ use crate::{ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { let xrpl_signers: Vec = axelar_signers .iter() - .map(|(axelar_signer, signature)| { + .map(|(axelar_signer, signature)| -> Result { let xrpl_address = xrpl_multisig::public_key_to_xrpl_address(axelar_signer.pub_key.clone()); - XRPLSigner { + Ok(XRPLSigner { account: xrpl_address, signing_pub_key: axelar_signer.pub_key.clone().into(), // TODO: should work with Ed25519 signatures too txn_signature: HexBinary::from(ecdsa::Signature::to_der( &ecdsa::Signature::try_from(signature.clone().as_ref()) - .map_err(|_| ContractError::FailedToEncodeSignature).unwrap() // TODO: FIX - SHOULD NOT UNWRAP + .map_err(|_| ContractError::FailedToEncodeSignature)? ).to_vec()), - } + }) }) - .collect::>(); + .collect::, ContractError>>()?; Ok(XRPLSignedTransaction { unsigned_tx, From fd63575c0a0c84e3e0ac1a9fc6b4a7cf00533265 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Fri, 12 Jan 2024 15:59:35 +0200 Subject: [PATCH 17/80] wip: xrpl memo --- contracts/xrpl-multisig-prover/src/error.rs | 5 ++ .../xrpl-multisig-prover/src/xrpl_multisig.rs | 54 ++++++++++++------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 70c820883..944594490 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -74,6 +74,8 @@ pub enum ContractError { #[error("signing session not completed")] SigningSessionNotCompleted, + // TODO: do not expose low level errors + #[error("invalid blob")] InvalidBlob, @@ -82,6 +84,9 @@ pub enum ContractError { #[error("failed to encode signature")] FailedToEncodeSignature, + + #[error("failed to serialize")] + FailedToSerialize, } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index f1eda760e..aa301145f 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -108,6 +108,28 @@ impl TryFrom<&XRPLUnsignedTx> for XRPLObject { } } +pub struct XRPLMemo(HexBinary); + +impl TryFrom<&XRPLMemo> for XRPLObject { + type Error = ContractError; + + fn try_from(memo: &XRPLMemo) -> Result { + let mut obj = XRPLObject::new(); + obj.add_field(13, &memo.0)?; + Ok(obj) + } +} + +impl XRPLSerialize for XRPLMemo { + fn xrpl_serialize(&self) -> Result, ContractError> { + XRPLObject::try_from(self)?.xrpl_serialize() + } +} + +impl XRPLTypedSerialize for XRPLMemo { + const TYPE_CODE: u8 = ARRAY_TYPE_CODE; +} + #[cw_serde] pub struct XRPLPaymentTx { pub account: String, @@ -133,6 +155,9 @@ impl TryFrom<&XRPLPaymentTx> for XRPLObject { obj.add_field(1, &XRPLAddress(tx.account.clone()))?; obj.add_field(3, &XRPLAddress(tx.destination.to_string()))?; + let tx_hash = compute_unsigned_tx_hash(&XRPLUnsignedTx::Payment(tx.clone()))?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(tx_hash.into())]})?; + Ok(obj) } } @@ -159,7 +184,12 @@ impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; obj.add_field(1, &XRPLAddress(tx.account.clone()))?; obj.add_field(3, &HexBinary::from_hex("")?)?; + + let tx_hash = compute_unsigned_tx_hash(&XRPLUnsignedTx::SignerListSet(tx.clone()))?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(tx_hash.into())]})?; + obj.add_field(4, &XRPLArray{ field_code: 11, items: tx.signer_entries.clone() })?; + Ok(obj) } } @@ -187,6 +217,9 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { obj.add_field(3, &HexBinary::from_hex("")?)?; obj.add_field(1, &XRPLAddress(tx.account.clone()))?; + let tx_hash = compute_unsigned_tx_hash(&XRPLUnsignedTx::TicketCreate(tx.clone()))?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(tx_hash.into())]})?; + Ok(obj) } } @@ -352,14 +385,13 @@ pub fn decode_address(address: &String) -> Result<[u8; 20], ContractError> { return Ok(buffer) } -pub const HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; -// TODO: optimize pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result { - let encoded_unsigned_tx = unsigned_tx.xrpl_serialize()?; + let encoded_unsigned_tx = serde_json::to_vec(unsigned_tx).map_err(|_| ContractError::FailedToSerialize)?; - let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(Some(HASH_PREFIX_UNSIGNED_TRANSACTION_MULTI), encoded_unsigned_tx.as_slice())); + let d = Sha256::digest(encoded_unsigned_tx); + let tx_hash_hex: HexBinary = HexBinary::from(d.to_vec()); let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); Ok(tx_hash) } @@ -370,8 +402,6 @@ pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result Result, ContractError> { let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; obj.add_field(3, &XRPLArray{ field_code: 16, items: self.signers.clone() })?; - let parts: Vec = obj.clone().fields.into_iter().map(|f| { - let mut res = Vec::new(); - res.extend(field_id(f.0, f.1)); - res.extend(f.2); - return hex::encode(res); - }).collect(); obj.xrpl_serialize() } @@ -518,12 +542,6 @@ impl XRPLSerialize for XRPLSigner { obj.add_field(1, &XRPLAddress(self.account.clone()))?; let mut result = obj.xrpl_serialize()?; result.extend(field_id(OBJECT_TYPE_CODE, 1)); - let parts: Vec = obj.clone().fields.into_iter().map(|f| { - let mut res = Vec::new(); - res.extend(field_id(f.0, f.1)); - res.extend(f.2); - return hex::encode(res); - }).collect(); Ok(result) } } From 25908e44c5ff71cedaf24464301064eb5149f371 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 23 Jan 2024 20:33:12 +0200 Subject: [PATCH 18/80] xrpl multisig in memo --- contracts/xrpl-multisig-prover/src/query.rs | 5 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 58 ++++++++++--------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 672d79240..701afac04 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -9,7 +9,7 @@ use crate::{ state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, contract::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, axelar_workers::WorkerSet, }; -pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { +pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>, multisig_session_id: &Uint64) -> Result { let xrpl_signers: Vec = axelar_signers .iter() .map(|(axelar_signer, signature)| -> Result { @@ -29,6 +29,7 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul Ok(XRPLSignedTransaction { unsigned_tx, signers: xrpl_signers, + multisig_session_id: *multisig_session_id }) } @@ -66,7 +67,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & .map(|(signer, signature)| (signer.clone(), signature.clone().unwrap())) .collect(); - let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; + let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers, multisig_session_id)?; let tx_blob: HexBinary = HexBinary::from(signed_tx.xrpl_serialize()?); GetProofResponse::Completed { unsigned_tx_hash, tx_blob } } diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index aa301145f..e1dc2fdaf 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,9 +1,10 @@ use std::collections::BTreeSet; + use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Storage, HexBinary, wasm_execute, WasmMsg}; +use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; @@ -114,6 +115,7 @@ impl TryFrom<&XRPLMemo> for XRPLObject { type Error = ContractError; fn try_from(memo: &XRPLMemo) -> Result { + println!("memo contents hex {}", hex::encode(memo.0.clone())); let mut obj = XRPLObject::new(); obj.add_field(13, &memo.0)?; Ok(obj) @@ -122,12 +124,17 @@ impl TryFrom<&XRPLMemo> for XRPLObject { impl XRPLSerialize for XRPLMemo { fn xrpl_serialize(&self) -> Result, ContractError> { - XRPLObject::try_from(self)?.xrpl_serialize() + let mut obj = XRPLObject::new(); + obj.add_field(13, &self.0)?; + let mut result = obj.xrpl_serialize()?; + result.extend(field_id(OBJECT_TYPE_CODE, 1)); + Ok(result) + } } impl XRPLTypedSerialize for XRPLMemo { - const TYPE_CODE: u8 = ARRAY_TYPE_CODE; + const TYPE_CODE: u8 = OBJECT_TYPE_CODE; } #[cw_serde] @@ -155,9 +162,6 @@ impl TryFrom<&XRPLPaymentTx> for XRPLObject { obj.add_field(1, &XRPLAddress(tx.account.clone()))?; obj.add_field(3, &XRPLAddress(tx.destination.to_string()))?; - let tx_hash = compute_unsigned_tx_hash(&XRPLUnsignedTx::Payment(tx.clone()))?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(tx_hash.into())]})?; - Ok(obj) } } @@ -185,9 +189,6 @@ impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { obj.add_field(1, &XRPLAddress(tx.account.clone()))?; obj.add_field(3, &HexBinary::from_hex("")?)?; - let tx_hash = compute_unsigned_tx_hash(&XRPLUnsignedTx::SignerListSet(tx.clone()))?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(tx_hash.into())]})?; - obj.add_field(4, &XRPLArray{ field_code: 11, items: tx.signer_entries.clone() })?; Ok(obj) @@ -217,9 +218,6 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { obj.add_field(3, &HexBinary::from_hex("")?)?; obj.add_field(1, &XRPLAddress(tx.account.clone()))?; - let tx_hash = compute_unsigned_tx_hash(&XRPLUnsignedTx::TicketCreate(tx.clone()))?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(tx_hash.into())]})?; - Ok(obj) } } @@ -235,6 +233,7 @@ pub struct XRPLSigner { pub struct XRPLSignedTransaction { pub unsigned_tx: XRPLUnsignedTx, pub signers: Vec, + pub multisig_session_id: Uint64 } pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { @@ -459,6 +458,10 @@ impl XRPLSerialize for XRPLSignedTransaction { fn xrpl_serialize(self: &XRPLSignedTransaction) -> Result, ContractError> { let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; obj.add_field(3, &XRPLArray{ field_code: 16, items: self.signers.clone() })?; + let memo_data: Vec = self.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + //obj.add_field(9, &XRPLMemo(memo))?; obj.xrpl_serialize() } @@ -949,29 +952,30 @@ mod tests { #[test] fn serialize_xrpl_signed_xrp_payment_transaction() { - let signed_tx = XRPLSignedTransaction { + let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), fee: 30, - sequence: Sequence::Plain(43497365), - amount: XRPLPaymentAmount::Drops(1000000000), + sequence: Sequence::Ticket(44218193), + amount: XRPLPaymentAmount::Drops(100000000), destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), }), signers: vec![ XRPLSigner{ - account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), - txn_signature: HexBinary::from(hex::decode("00CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9").unwrap()), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + txn_signature: HexBinary::from(hex::decode("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()), }, XRPLSigner{ - account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), - txn_signature: HexBinary::from(hex::decode("62B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE02").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + txn_signature: HexBinary::from(hex::decode("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()), } - ] + ], + multisig_session_id: Uint64::from(5461264u64), }; let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); assert_eq!( - "1200002200000000240297B79561400000003B9ACA0068400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689A831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9744000CBEDBDD84D5B17EC0D24EDEA49AE78D33908E69D2885895BC0243458228E8FD5CEF5ABCA558C3518D97B0BBA1C4051BBB31AAD6E7808673562FA73FFB5F50B8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744062B63EFF8ED37ACFA453A61EC98B13761EFE608E36EB437ABE42DC86B73C3114B2ED5E6C3E9428E82DC4AAB9E4A093C00F041F6F32A5392FDAEF858142F0CE028114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", + "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); } @@ -995,7 +999,8 @@ mod tests { txn_signature: HexBinary::from(hex::decode("5EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC04").unwrap()), signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), }, - ] + ], + multisig_session_id: Uint64::from(0u64) }; let encoded_signed_tx = signed_tx.xrpl_serialize().unwrap(); assert_eq!( @@ -1034,7 +1039,8 @@ mod tests { txn_signature: HexBinary::from(hex::decode("86B8AF804C7F4881E125F4F876C9EC292EEF811D572D4D4BA7C6CD533B13FB1B9A31ADB4A71DD54405135BEFCDEF3A98564479B681242250D42154A93EB1FE04").unwrap()), signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), }, - ] + ], + multisig_session_id: Uint64::from(0u64) }; let encoded_signed_tx = signed_tx.xrpl_serialize().unwrap(); assert_eq!( From 257da098432159a84319f3130d5288ca9e382b56 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 30 Jan 2024 15:26:26 +0200 Subject: [PATCH 19/80] xrpl serialization tests --- Cargo.lock | 24 + contracts/xrpl-multisig-prover/Cargo.toml | 2 + .../xrpl-multisig-prover/src/contract.rs | 2 +- contracts/xrpl-multisig-prover/src/error.rs | 4 +- contracts/xrpl-multisig-prover/src/query.rs | 1 - .../xrpl-multisig-prover/src/xrpl_multisig.rs | 428 +++++++++++++----- 6 files changed, 354 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f91d14bf..5420422e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,6 +521,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +dependencies = [ + "serde", +] + [[package]] name = "ascii-canvas" version = "3.0.0" @@ -843,6 +852,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bigdecimal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06619be423ea5bb86c95f087d5707942791a08a85530df0db2209a3ecfb8bc9" +dependencies = [ + "autocfg", + "libm", + "num-bigint 0.4.4", + "num-integer", + "num-traits", +] + [[package]] name = "bincode" version = "1.3.3" @@ -9296,8 +9318,10 @@ name = "xrpl-multisig-prover" version = "0.1.0" dependencies = [ "anyhow", + "ascii", "axelar-wasm-std", "axelar-wasm-std-derive", + "bigdecimal", "bs58 0.5.0", "connection-router", "cosmwasm-schema", diff --git a/contracts/xrpl-multisig-prover/Cargo.toml b/contracts/xrpl-multisig-prover/Cargo.toml index 575397c8a..3737096aa 100644 --- a/contracts/xrpl-multisig-prover/Cargo.toml +++ b/contracts/xrpl-multisig-prover/Cargo.toml @@ -49,6 +49,8 @@ ripemd = "0.1.3" hex = { version = "0.4.3", default-features = false, features = [] } xrpl-voting-verifier = { workspace = true, features = ["library"] } k256 = { version = "0.13.1", features = ["ecdsa"] } +bigdecimal = "0.4.2" +ascii = { version = "1.1.0", features = ["serde"] } [dev-dependencies] anyhow = "1.0" diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index c5767f65e..4059da5d7 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -182,7 +182,7 @@ fn construct_payment_proof( let xrpl_token = TOKENS.load(storage, coin.denom.clone())?; let message = querier.get_message(message_id.clone())?; // TODO: handle coin.amount precision loss - let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount)?; + let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { amount: coin.amount.to_string(), reason: "overflow".to_string() })?; // TODO: find better way to distinguish XRP from the rest let xrpl_payment_amount = if xrpl_token.currency == XRPLToken::NATIVE_CURRENCY { XRPLPaymentAmount::Drops(drops) diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 944594490..957b6a992 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -8,8 +8,8 @@ pub enum ContractError { #[error(transparent)] Std(#[from] StdError), - #[error("invalid amount")] - InvalidAmount, + #[error("invalid amount {amount}: {reason}")] + InvalidAmount { reason: String, amount: String }, #[error("serialization failed")] SerializationFailed, diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 701afac04..60018999c 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -29,7 +29,6 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul Ok(XRPLSignedTransaction { unsigned_tx, signers: xrpl_signers, - multisig_session_id: *multisig_session_id }) } diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index e1dc2fdaf..c02ec8474 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,12 +1,14 @@ use std::collections::BTreeSet; -use axelar_wasm_std::nonempty; +use axelar_wasm_std::{nonempty, FnExt}; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; +use bigdecimal::{BigDecimal, Signed, ToPrimitive, Zero}; +use std::str::FromStr; use crate::{ error::ContractError, @@ -144,6 +146,7 @@ pub struct XRPLPaymentTx { pub sequence: Sequence, pub amount: XRPLPaymentAmount, pub destination: nonempty::String, + pub multisig_session_id: Option } impl TryFrom<&XRPLPaymentTx> for XRPLObject { @@ -153,7 +156,7 @@ impl TryFrom<&XRPLPaymentTx> for XRPLObject { let mut obj = XRPLObject::new(); obj.add_field(2, &PAYMENT_TX_TYPE)?; obj.add_field(2, &0u32)?; // flags - obj.add_sequence(&tx.sequence)?; + add_sequence(&mut obj, &tx.sequence)?; // type: Amount, type_code: 6, nth: 1, !isVLEncoded obj.add_field(1, &tx.amount)?; // type: Amount, type_code: 6, nth: 8, !isVLEncoded @@ -162,6 +165,12 @@ impl TryFrom<&XRPLPaymentTx> for XRPLObject { obj.add_field(1, &XRPLAddress(tx.account.clone()))?; obj.add_field(3, &XRPLAddress(tx.destination.to_string()))?; + if let Some(multisig_session_id) = tx.multisig_session_id { + let memo_data: Vec = multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + } + Ok(obj) } } @@ -172,7 +181,8 @@ pub struct XRPLSignerListSetTx { pub fee: u64, pub sequence: Sequence, pub signer_quorum: u32, - pub signer_entries: Vec + pub signer_entries: Vec, + pub multisig_session_id: Option } impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { @@ -183,7 +193,7 @@ impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { obj.add_field(2, &SIGNER_LIST_SET_TX_TYPE)?; obj.add_field(2, &0u32)?; // flags - obj.add_sequence(&tx.sequence)?; + add_sequence(&mut obj, &tx.sequence)?; obj.add_field(35, &tx.signer_quorum)?; obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; obj.add_field(1, &XRPLAddress(tx.account.clone()))?; @@ -191,6 +201,12 @@ impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { obj.add_field(4, &XRPLArray{ field_code: 11, items: tx.signer_entries.clone() })?; + if let Some(multisig_session_id) = tx.multisig_session_id { + let memo_data: Vec = multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + } + Ok(obj) } } @@ -201,7 +217,8 @@ pub struct XRPLTicketCreateTx { pub account: String, pub fee: u64, pub sequence: Sequence, - pub ticket_count: u32 + pub ticket_count: u32, + pub multisig_session_id: Option } impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { @@ -212,12 +229,18 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { // type_code: 1, nth: 2, !isVLEncoded obj.add_field(2, &TICKET_CREATE_TX_TYPE)?; obj.add_field(2, &0u32)?; // flags - obj.add_sequence(&tx.sequence)?; + add_sequence(&mut obj, &tx.sequence)?; obj.add_field(40, &tx.ticket_count)?; // 202800000000a obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; // 68400000000000001e obj.add_field(3, &HexBinary::from_hex("")?)?; obj.add_field(1, &XRPLAddress(tx.account.clone()))?; + if let Some(multisig_session_id) = tx.multisig_session_id { + let memo_data: Vec = multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + } + Ok(obj) } } @@ -232,8 +255,7 @@ pub struct XRPLSigner { #[cw_serde] pub struct XRPLSignedTransaction { pub unsigned_tx: XRPLUnsignedTx, - pub signers: Vec, - pub multisig_session_id: Uint64 + pub signers: Vec } pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { @@ -278,78 +300,44 @@ pub fn field_id(type_code: u8, field_code: u8) -> Vec { const POSITIVE_BIT: u64 = 0x4000000000000000; -const MIN_MANTISSA: u128 = 1_000_000_000_000_000; -const MAX_MANTISSA: u128 = 10_000_000_000_000_000 - 1; -const MIN_EXPONENT: i32 = -96; -const MAX_EXPONENT: i32 = 80; - -// TODO: trim whitespace? -fn parse_decimal(s: &str) -> Result<(bool, u64, u64), ContractError> { - if s.is_empty() { - return Err(ContractError::InvalidAmount); - } - - let sign = s.starts_with('-'); - let trimmed = if s.starts_with('-') || s.starts_with('+') { &s[1..] } else { s }; - - let parts: Vec<_> = trimmed.split('.').collect(); +const MIN_MANTISSA: u64 = 1_000_000_000_000_000; +const MAX_MANTISSA: u64 = 10_000_000_000_000_000 - 1; +const MIN_EXPONENT: i64 = -96; +const MAX_EXPONENT: i64 = 80; - if parts.len() > 2 { - return Err(ContractError::InvalidAmount); - } - - let integer_part = match parts[0].parse::() { - Ok(num) => num, - Err(_) => return Err(ContractError::InvalidAmount), - }; - - let fractional_part = if parts.len() == 2 { - match parts[1].parse::() { - Ok(num) => num, - Err(_) => return Err(ContractError::InvalidAmount), - } - } else { - 0 - }; - - Ok((sign, integer_part, fractional_part)) -} // see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 pub fn amount_to_bytes(amount: &XRPLTokenAmount) -> Result, ContractError> { - let (is_negative, integer_part, fractional_part) = parse_decimal(amount.0.trim())?; + let decimal = BigDecimal::from_str(amount.0.trim()).map_err(|e| { ContractError::InvalidAmount { amount: amount.clone().0, reason: e.to_string() } })?; + + let is_negative = decimal.is_negative(); let mut serial: u64 = 0x8000000000000000; - if integer_part == 0 && fractional_part == 0 { + if decimal.is_zero() { return Ok(Vec::from(serial.to_be_bytes())) } - let mut exponent: i32 = -1 * fractional_part.to_string().len() as i32; - let mut mantissa: u128 = (integer_part as u128) * 10u128.pow((-1 * exponent) as u32) + (fractional_part as u128); + let (mut mantissa, mut exponent) = decimal.into_bigint_and_exponent().then(|(m, e)| (m.abs().to_biguint().unwrap(), e * -1)); - while mantissa < MIN_MANTISSA && exponent > MIN_EXPONENT { - mantissa *= 10; + while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { + mantissa *= 10u8; exponent -= 1; } - /* - TODO: Discuss with Ripple team, the below is part of the reference implementation - https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/STAmount.cpp#L795 - However it leads to least significant digits being dropped off from the mantissa */ - while mantissa > MAX_MANTISSA && exponent > MIN_EXPONENT { + while mantissa > MAX_MANTISSA.into() && exponent > MIN_EXPONENT { if exponent > MAX_EXPONENT { - return Err(ContractError::InvalidAmount); + return Err(ContractError::InvalidAmount { amount: amount.clone().0, reason: "overflow 1".to_string() }); } - mantissa /= 10; + mantissa /= 10u8; exponent += 1; } - if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA { + if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA.into() { return Ok(Vec::from(serial.to_be_bytes())); } - if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA { - return Err(ContractError::InvalidAmount); + if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA.into() { + return Err(ContractError::InvalidAmount { amount: amount.clone().0, reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); } if !is_negative { @@ -358,14 +346,14 @@ pub fn amount_to_bytes(amount: &XRPLTokenAmount) -> Result, ContractErro serial |= ((exponent+97) as u64) << 54; // next 8 bits are exponent - serial |= mantissa as u64; // last 54 bits are mantissa + serial |= mantissa.to_u64().unwrap(); // last 54 bits are mantissa Ok(Vec::from(serial.to_be_bytes())) } pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { - if currency.len() != 3 || !currency.is_ascii() { + if currency.len() != 3 || !currency.is_ascii() || currency == "XRP" { return Err(ContractError::InvalidCurrency); } let mut buffer = [0u8; 20]; @@ -435,9 +423,11 @@ impl XRPLSerialize for XRPLPaymentAmount { fn xrpl_serialize(&self) -> Result, ContractError> { match self { XRPLPaymentAmount::Drops(value) => { - // assert!(value >= 0); - assert!(*value <= 10u64.pow(17)); - Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) + if *value <= 10u64.pow(17) { + Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) + } else { + Err(ContractError::InvalidAmount { reason: "more than maximum amount of drops".to_string(), amount: value.to_string() }) + } }, XRPLPaymentAmount::Token(token, amount) => { let mut result = Vec::new(); @@ -458,9 +448,6 @@ impl XRPLSerialize for XRPLSignedTransaction { fn xrpl_serialize(self: &XRPLSignedTransaction) -> Result, ContractError> { let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; obj.add_field(3, &XRPLArray{ field_code: 16, items: self.signers.clone() })?; - let memo_data: Vec = self.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; //obj.add_field(9, &XRPLMemo(memo))?; obj.xrpl_serialize() @@ -506,7 +493,7 @@ pub fn encode_length(mut length: usize) -> Option> { return Some(vec![length as u8]); } else if length <= 12480 { length -= 193; - return Some(vec![(length >> 8) as u8, (length & 0xff) as u8]); + return Some(vec![193 + (length >> 8) as u8, (length & 0xff) as u8]); } else if length <= 918744 { length -= 12481; return Some(vec![ @@ -599,16 +586,16 @@ impl XRPLObject { self.fields.push((T::TYPE_CODE, field_code, value.xrpl_serialize()?)); Ok(()) } +} - pub fn add_sequence(&mut self, sequence: &Sequence) -> Result<(), ContractError> { - match sequence { - Sequence::Plain(seq) => { - self.add_field(4, seq) - }, - Sequence::Ticket(seq) => { - self.add_field(4, &0u32)?; - self.add_field(41, seq) - } +pub fn add_sequence(obj: &mut XRPLObject, sequence: &Sequence) -> Result<(), ContractError> { + match sequence { + Sequence::Plain(seq) => { + obj.add_field(4, seq) + }, + Sequence::Ticket(seq) => { + obj.add_field(4, &0u32)?; + obj.add_field(41, seq) } } } @@ -690,6 +677,7 @@ pub fn issue_payment( account: config.xrpl_multisig_address.to_string(), fee: config.xrpl_fee, sequence: Sequence::Ticket(ticket_number), + multisig_session_id: None, amount, destination }; @@ -709,6 +697,7 @@ pub fn issue_ticket_create(storage: &mut dyn Storage, config: &Config, ticket_co fee: config.xrpl_fee, sequence: Sequence::Plain(sequence_number.clone()), ticket_count, + multisig_session_id: None }; issue_tx( @@ -727,6 +716,7 @@ pub fn issue_signer_list_set(storage: &mut dyn Storage, config: &Config, workers sequence: Sequence::Plain(sequence_number.clone()), signer_quorum: workers.quorum, signer_entries: make_xrpl_signer_entries(workers.signers), + multisig_session_id: None }; issue_tx( @@ -756,7 +746,10 @@ pub fn public_key_to_xrpl_address(public_key: multisig::key::PublicKey) -> Strin let public_key_inner_hash = Sha256::digest(public_key_hex); let account_id = Ripemd160::digest(public_key_inner_hash); + return account_id_bytes_to_address(&account_id); +} +pub fn account_id_bytes_to_address(account_id: &[u8]) -> String { let address_type_prefix: &[u8] = &[0x00]; let payload = [address_type_prefix, &account_id].concat(); @@ -899,6 +892,232 @@ mod tests { use super::*; + fn serialize(value: T) -> String { + hex::encode_upper(value.xrpl_serialize().unwrap()) + } + + #[macro_export] + macro_rules! assert_hex_eq { + ($expected:expr, $actual:expr) => { + assert_eq!($expected, hex::encode_upper($actual)); + }; + } + + #[test] + fn test_encode_length() { + assert_hex_eq!("00", encode_length(0).unwrap()); + assert_hex_eq!("0A", encode_length(10).unwrap()); + assert_hex_eq!("C100", encode_length(193).unwrap()); + assert_hex_eq!("F10000", encode_length(12481).unwrap()); + assert_hex_eq!("FED417", encode_length(918744).unwrap()); + assert_eq!(None, encode_length(918745)); + } + + #[test] + fn test_account_id_to_bytes_address() { + assert_eq!("rrrrrrrrrrrrrrrrrrrrrhoLvTp", account_id_bytes_to_address(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); + assert_eq!("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi", account_id_bytes_to_address(&[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255])); + } + + #[test] + fn test_xrpl_serialize() -> Result<(), ContractError> { + assert_hex_eq!("0000", 0u16.xrpl_serialize()?); + assert_hex_eq!("0001", 1u16.xrpl_serialize()?); + assert_hex_eq!("FFFF", 0xffffu16.xrpl_serialize()?); + assert_hex_eq!("00000000", 0u32.xrpl_serialize()?); + assert_hex_eq!("00000005", 5u32.xrpl_serialize()?); + assert_hex_eq!("FFFFFFFF", 0xffffffffu32.xrpl_serialize()?); + assert_hex_eq!("00", HexBinary::from_hex("").unwrap().xrpl_serialize()?); + assert_hex_eq!("04DEADBEEF", HexBinary::from_hex("DEADBEEF").unwrap().xrpl_serialize()?); + assert_hex_eq!( + "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("0".to_string())) + .xrpl_serialize()? + ); + assert_hex_eq!( + "D4838D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("1".to_string())) + .xrpl_serialize()? + ); + // minimum amount + assert_hex_eq!( + "AC6386F26FC0FFFF00000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("-9999999999999999e80".to_string())) + .xrpl_serialize()? + ); + // less than minimum amount fails + assert!( + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("-10000000000000000e80".to_string())) + .xrpl_serialize() + .is_err() + ); + // minimum absolute amount + assert_hex_eq!( + "C0438D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("1000000000000000e-96".to_string())) + .xrpl_serialize()? + ); + // less than minimum absolute positive amount serializes to 0 + assert_hex_eq!( + "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("999999999999999e-96".to_string())) + .xrpl_serialize()? + ); + // less than minimum absolute negative amount serializes to 0 + assert_hex_eq!( + "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("-999999999999999e-96".to_string())) + .xrpl_serialize()? + ); + // maximum amount + assert_hex_eq!( + "EC6386F26FC0FFFF00000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("9999999999999999e80".to_string())) + .xrpl_serialize()? + ); + // more than maximum amount fails + assert!( + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("10000000000000000e80".to_string())) + .xrpl_serialize() + .is_err() + ); + // test integer and fractional part with zeroes + assert_hex_eq!( + "D58462510B02ED1500000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "USD".to_string(), + }, XRPLTokenAmount("0012340.0012345678900".to_string())) + .xrpl_serialize()? + ); + // currency can contain non-alphanumeric ascii letters + assert_hex_eq!( + "D4CEEBE0B40E8000000000000000000000000000247B3B00000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "${;".to_string(), + }, XRPLTokenAmount("42".to_string())) + .xrpl_serialize()? + ); + // TODO: these could be enforced on a type level: + // - currency cannot contain non-ascii letters + // - currency must not be more than 3 ascii letters + // - currency must not be less than 3 ascii letters + // XRP currency code is not allowed + assert!( + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + currency: "XRP".to_string(), + }, XRPLTokenAmount("42".to_string())) + .xrpl_serialize() + .is_err() + ); + // minimum XRP + assert_hex_eq!( + "4000000000000000", + XRPLPaymentAmount::Drops(0) + .xrpl_serialize()? + ); + assert_hex_eq!( + "4000000000000001", + XRPLPaymentAmount::Drops(1) + .xrpl_serialize()? + ); + assert_hex_eq!( + "40000000499602D2", + XRPLPaymentAmount::Drops(1234567890) + .xrpl_serialize()? + ); + // maximum XRP + assert_hex_eq!( + "416345785D8A0000", + XRPLPaymentAmount::Drops(100_000_000_000_000_000) + .xrpl_serialize()? + ); + // more than maximum XRP fails + assert!( + XRPLPaymentAmount::Drops(100_000_000_000_000_001) + .xrpl_serialize() + .is_err() + ); + // account "0" (with length prefix) + assert_hex_eq!( + "140000000000000000000000000000000000000000", + XRPLAddress("rrrrrrrrrrrrrrrrrrrrrhoLvTp".to_string()) + .xrpl_serialize()? + ); + // account "1" (with length prefix) + assert_hex_eq!( + "140000000000000000000000000000000000000001", + XRPLAddress("rrrrrrrrrrrrrrrrrrrrBZbvji".to_string()) + .xrpl_serialize()? + ); + // max acccount + assert_hex_eq!( + "14FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + XRPLAddress("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi".to_string()) + .xrpl_serialize()? + ); + assert_hex_eq!( + "13000081140000000000000000000000000000000000000000E1", + XRPLSignerEntry{ + account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".to_string(), + signer_weight: 0 + }.xrpl_serialize()? + ); + // { "NetworkID": 0 } + assert_hex_eq!( + "2100000000", + XRPLObject { fields: vec![(2, 1, 0u32.xrpl_serialize()?)]} + .xrpl_serialize()? + ); + // empty array + assert_hex_eq!( + "F1", + XRPLArray::{ field_code: 10, items: vec![] } + .xrpl_serialize()? + ); + // array with 1 element + assert_hex_eq!( + "EA13000081140000000000000000000000000000000000000000E1F1", + XRPLArray::{ field_code: 10, items: vec![ + XRPLSignerEntry{ + account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".to_string(), + signer_weight: 0 + }, + ] } + .xrpl_serialize()? + ); + Ok(()) + } + #[test] fn serialize_xrpl_unsigned_token_payment_transaction() { let unsigned_tx = XRPLPaymentTx { @@ -913,6 +1132,7 @@ mod tests { XRPLTokenAmount("0.3369568318".to_string()), ), destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), + multisig_session_id: None, }; let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize().unwrap(); assert_eq!( @@ -929,6 +1149,7 @@ mod tests { sequence: Sequence::Plain(1), amount: XRPLPaymentAmount::Drops(1000), destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), + multisig_session_id: None, }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); assert_eq!( @@ -942,6 +1163,7 @@ mod tests { sequence: Sequence::Plain(43497363), amount: XRPLPaymentAmount::Drops(1000000000), destination: nonempty::String::try_from("rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq").unwrap(), + multisig_session_id: None, }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); assert_eq!( @@ -959,6 +1181,7 @@ mod tests { sequence: Sequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), + multisig_session_id: Some(Uint64::from(5461264u64)), }), signers: vec![ XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), @@ -971,7 +1194,6 @@ mod tests { signing_pub_key: HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()), } ], - multisig_session_id: Uint64::from(5461264u64), }; let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); assert_eq!( @@ -984,27 +1206,27 @@ mod tests { fn serialize_xrpl_signed_xrp_ticket_create_transaction() { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::TicketCreate(XRPLTicketCreateTx { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), fee: 30, - sequence: Sequence::Plain(43497367), - ticket_count: 10 + sequence: Sequence::Plain(44218194), + ticket_count: 3, + multisig_session_id: Some(Uint64::from(5461264u64)), }), signers: vec![ XRPLSigner{ - account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), - txn_signature: HexBinary::from(hex::decode("3C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F551403").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9").unwrap()), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + txn_signature: HexBinary::from(hex::decode("304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()), }, XRPLSigner{ - account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), - txn_signature: HexBinary::from(hex::decode("5EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC04").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + txn_signature: HexBinary::from(hex::decode("3045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C58").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()), }, - ], - multisig_session_id: Uint64::from(0u64) + ] }; let encoded_signed_tx = signed_tx.xrpl_serialize().unwrap(); assert_eq!( - "12000A2200000000240297B79720280000000A68400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689AF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F974403C972711EB146F147405862AE43FB0086A20963682D77BD46E31A9D98F88FB655912A2B4BE6EBF403BC813EB5B60ADD1D7FF9D2F6F0591355612997E7F5514038114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B43574405EAF9A0190F66C663397ECD41F6043EF30DA8436ACD9ED94F65610E240E7825D26494461C5262A426870899EE9847199E18B4F36476234E1DBE834FC6265AC048114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1", + "12000A22000000002402A2B75220280000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC0088567446304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F8114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C588114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); } @@ -1014,37 +1236,37 @@ mod tests { fn serialize_xrpl_signed_signer_list_set_transaction() { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::SignerListSet(XRPLSignerListSetTx { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), fee: 30, - sequence: Sequence::Plain(43497378), + sequence: Sequence::Plain(44218445), signer_quorum: 3, signer_entries: vec![ XRPLSignerEntry{ - account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), signer_weight: 2 }, XRPLSignerEntry{ - account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), signer_weight: 1 } - ] + ], + multisig_session_id: Some(Uint64::from(5461264u64)) }), signers: vec![ XRPLSigner{ - account: "rn7JWRhHvsvea6JMWYFuBB3MizMxbgKApf".to_string(), - txn_signature: HexBinary::from(hex::decode("F4EE6E7AE1359360C16FF774EB907C908AE5E717A4DAAA8BF0C5A754A544B9690118E18EB9ABF2CE41A4853F346321F4624089AE803EB869B49F3ED506139A0E").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F9").unwrap()), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + txn_signature: HexBinary::from(hex::decode("3045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B39").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()), }, XRPLSigner{ - account: "rM9pYgHGm1Mqohp13XfZh6kbESkQPpJAKF".to_string(), - txn_signature: HexBinary::from(hex::decode("86B8AF804C7F4881E125F4F876C9EC292EEF811D572D4D4BA7C6CD533B13FB1B9A31ADB4A71DD54405135BEFCDEF3A98564479B681242250D42154A93EB1FE04").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435").unwrap()), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + txn_signature: HexBinary::from(hex::decode("3044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A").unwrap()), + signing_pub_key: HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()), }, ], - multisig_session_id: Uint64::from(0u64) }; let encoded_signed_tx = signed_tx.xrpl_serialize().unwrap(); assert_eq!( - "12000C2200000000240297B7A220230000000368400000000000001E73008114245409103F1B06F22FBCED389AAE0EFCE2F6689AF3E0107321EDDC432D6E86302084DCB8EBFA6EF7452DC8CBFA552D5F843D6BD1870EC9CD10F97440F4EE6E7AE1359360C16FF774EB907C908AE5E717A4DAAA8BF0C5A754A544B9690118E18EB9ABF2CE41A4853F346321F4624089AE803EB869B49F3ED506139A0E8114310A592CA22E8B35B819464F8A581A36C91DE857E1E0107321ED1B88E8E246E395E0CD45153E1579B1B43D7C1DF9B5481A34AABC43FF8562B435744086B8AF804C7F4881E125F4F876C9EC292EEF811D572D4D4BA7C6CD533B13FB1B9A31ADB4A71DD54405135BEFCDEF3A98564479B681242250D42154A93EB1FE048114DCE722505E32B29932618C5C9819AAEA03754AA5E1F1F4EB1300028114DCE722505E32B29932618C5C9819AAEA03754AA5E1EB1300018114310A592CA22E8B35B819464F8A581A36C91DE857E1F1", + "12000C22000000002402A2B84D20230000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674473045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B398114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74463044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A8114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F4EB1300028114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1EB1300018114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); } From b47d38d968713a328c50375d911b68ad73958465 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 31 Jan 2024 16:24:12 +0200 Subject: [PATCH 20/80] rename cc_id to message_id --- contracts/xrpl-multisig-prover/src/contract.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 4059da5d7..9e14dea4e 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -91,10 +91,9 @@ pub fn instantiate( #[cw_serde] pub enum ExecuteMsg { RegisterToken { denom: String, token: XRPLToken }, - // TODO: fix inconsistency between message_id & cc_id ConstructProof { message_id: CrossChainId }, FinalizeProof { multisig_session_id: Uint64 }, - UpdateTxStatus { cc_id: CrossChainId, message_status: MessageStatus }, + UpdateTxStatus { message_id: CrossChainId, message_status: MessageStatus }, UpdateWorkerSet, TicketCreate, } @@ -132,8 +131,8 @@ pub fn execute( ExecuteMsg::UpdateWorkerSet {} => { construct_signer_list_set_proof(deps.storage, querier, env, &config) }, - ExecuteMsg::UpdateTxStatus { cc_id, message_status } => { - update_tx_status(deps.storage, querier, cc_id, &message_status, config.axelar_multisig_address) + ExecuteMsg::UpdateTxStatus { message_id, message_status } => { + update_tx_status(deps.storage, querier, message_id, &message_status, config.axelar_multisig_address) }, ExecuteMsg::TicketCreate {} => { construct_ticket_create_proof(deps.storage, env.contract.address, &config) @@ -303,15 +302,15 @@ fn construct_ticket_create_proof( fn update_tx_status( storage: &mut dyn Storage, querier: Querier, - cc_id: CrossChainId, + message_id: CrossChainId, status: &MessageStatus, axelar_multisig_address: impl Into, ) -> Result { - if !querier.get_message_confirmation(cc_id.clone(), status)? { + if !querier.get_message_confirmation(message_id.clone(), status)? { return Err(ContractError::InvalidMessageStatus) } - let tx_hash: TxHash = TxHash::try_from(cc_id)?; + let tx_hash: TxHash = TxHash::try_from(message_id)?; match xrpl_multisig::update_tx_status(storage, axelar_multisig_address, tx_hash, status.clone().into())? { None => Ok(Response::default()), From d8a9e6eee45da307d148ca4e561412730a57aac1 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 31 Jan 2024 16:26:46 +0200 Subject: [PATCH 21/80] xrpl improve coin amount handling --- contracts/xrpl-multisig-prover/src/contract.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 9e14dea4e..d9572a1ff 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -180,10 +180,8 @@ fn construct_payment_proof( let coin = funds.remove(0); let xrpl_token = TOKENS.load(storage, coin.denom.clone())?; let message = querier.get_message(message_id.clone())?; - // TODO: handle coin.amount precision loss - let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { amount: coin.amount.to_string(), reason: "overflow".to_string() })?; - // TODO: find better way to distinguish XRP from the rest let xrpl_payment_amount = if xrpl_token.currency == XRPLToken::NATIVE_CURRENCY { + let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { amount: coin.amount.to_string(), reason: "overflow".to_string() })?; XRPLPaymentAmount::Drops(drops) } else { XRPLPaymentAmount::Token( @@ -191,7 +189,7 @@ fn construct_payment_proof( issuer: xrpl_token.issuer, currency: xrpl_token.currency, }, - XRPLTokenAmount(drops.to_string()), + XRPLTokenAmount(coin.amount.to_string()), ) }; From 1ee8ad0bb1b559b8fd0eda1a316db135c182a617 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 7 Feb 2024 16:43:42 +0200 Subject: [PATCH 22/80] xrpl separate message interface to msg.rs --- .../xrpl-multisig-prover/src/contract.rs | 40 +----------------- contracts/xrpl-multisig-prover/src/lib.rs | 1 + contracts/xrpl-multisig-prover/src/msg.rs | 41 +++++++++++++++++++ contracts/xrpl-multisig-prover/src/query.rs | 2 +- integration-tests/tests/message_routing.rs | 4 +- integration-tests/tests/test_utils/mod.rs | 28 ++++++------- integration-tests/tests/update_worker_set.rs | 2 +- 7 files changed, 61 insertions(+), 57 deletions(-) create mode 100644 contracts/xrpl-multisig-prover/src/msg.rs diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index d9572a1ff..e144644cf 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -11,11 +11,7 @@ use cosmwasm_std::{ use xrpl_voting_verifier::execute::MessageStatus; use crate::{ - error::ContractError, - state::{Config, CONFIG, REPLY_TX_HASH, TOKENS, CURRENT_WORKER_SET, NEXT_WORKER_SET, SIGNED_TO_UNSIGNED_TX_HASH, NEXT_SEQUENCE_NUMBER, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS}, - reply, - types::*, - xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query::{get_proof, self}, + axelar_workers, error::ContractError, msg::{ExecuteMsg, GetProofResponse, QueryMsg}, querier::{Querier, XRPL_CHAIN_NAME}, query::{get_proof, self}, reply, state::{Config, CONFIG, REPLY_TX_HASH, TOKENS, CURRENT_WORKER_SET, NEXT_WORKER_SET, SIGNED_TO_UNSIGNED_TX_HASH, NEXT_SEQUENCE_NUMBER, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS}, types::*, xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount} }; pub const START_MULTISIG_REPLY_ID: u64 = 1; @@ -88,15 +84,6 @@ pub fn instantiate( Ok(Response::new().add_message(msg)) } -#[cw_serde] -pub enum ExecuteMsg { - RegisterToken { denom: String, token: XRPLToken }, - ConstructProof { message_id: CrossChainId }, - FinalizeProof { multisig_session_id: Uint64 }, - UpdateTxStatus { message_id: CrossChainId, message_status: MessageStatus }, - UpdateWorkerSet, - TicketCreate, -} fn register_token( storage: &mut dyn Storage, @@ -329,31 +316,6 @@ pub fn reply( .map_err(axelar_wasm_std::ContractError::from) } -// TODO: move to msg.rs -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(GetProofResponse)] - GetProof { multisig_session_id: Uint64 }, - - #[returns(GetMessageToSignResponse)] - GetMessageToSign { multisig_session_id: Uint64, signer_xrpl_address: String }, - - #[returns(multisig::worker_set::WorkerSet)] - GetWorkerSet, -} - -#[cw_serde] -#[serde(tag = "status")] -pub enum GetProofResponse { - Completed { unsigned_tx_hash: TxHash, tx_blob: HexBinary}, - Pending { unsigned_tx_hash: TxHash }, -} - -#[cw_serde] -pub struct GetMessageToSignResponse { - pub tx_hash: HexBinary, -} #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { diff --git a/contracts/xrpl-multisig-prover/src/lib.rs b/contracts/xrpl-multisig-prover/src/lib.rs index f1c0dbfc9..893575092 100644 --- a/contracts/xrpl-multisig-prover/src/lib.rs +++ b/contracts/xrpl-multisig-prover/src/lib.rs @@ -8,3 +8,4 @@ pub mod query; pub mod xrpl_multisig; pub mod axelar_workers; pub mod querier; +pub mod msg; \ No newline at end of file diff --git a/contracts/xrpl-multisig-prover/src/msg.rs b/contracts/xrpl-multisig-prover/src/msg.rs new file mode 100644 index 000000000..15047b103 --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/msg.rs @@ -0,0 +1,41 @@ +use connection_router::state::CrossChainId; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{HexBinary, Uint64}; +use xrpl_voting_verifier::execute::MessageStatus; + +use crate::types::{TxHash, XRPLToken}; + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(GetProofResponse)] + GetProof { multisig_session_id: Uint64 }, + + #[returns(GetMessageToSignResponse)] + GetMessageToSign { multisig_session_id: Uint64, signer_xrpl_address: String }, + + #[returns(multisig::worker_set::WorkerSet)] + GetWorkerSet, +} + +#[cw_serde] +#[serde(tag = "status")] +pub enum GetProofResponse { + Completed { unsigned_tx_hash: TxHash, tx_blob: HexBinary}, + Pending { unsigned_tx_hash: TxHash }, +} + +#[cw_serde] +pub struct GetMessageToSignResponse { + pub tx_hash: HexBinary, +} + +#[cw_serde] +pub enum ExecuteMsg { + RegisterToken { denom: String, token: XRPLToken }, + ConstructProof { message_id: CrossChainId }, + FinalizeProof { multisig_session_id: Uint64 }, + UpdateTxStatus { message_id: CrossChainId, message_status: MessageStatus }, + UpdateWorkerSet, + TicketCreate, +} \ No newline at end of file diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 60018999c..c42b3617d 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -6,7 +6,7 @@ use multisig::types::MultisigState; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use crate::{ - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, contract::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, axelar_workers::WorkerSet, + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, msg::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, axelar_workers::WorkerSet, }; pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>, multisig_session_id: &Uint64) -> Result { diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index 3dd53ce34..fbfee10da 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -134,7 +134,7 @@ fn xrpl_ticket_create_can_be_proven() { ); assert!(matches!( proof, - xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } + xrpl_multisig_prover::msg::GetProofResponse::Completed { .. } )); println!("TicketCreate proof: {:?}", proof); @@ -253,7 +253,7 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { assert!(matches!( //proof.status, proof, - xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } + xrpl_multisig_prover::msg::GetProofResponse::Completed { .. } )); test_utils::finalize_xrpl_proof( diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index cfb6a8d40..705a2fe6d 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -249,7 +249,7 @@ pub fn construct_xrpl_ticket_create_proof_and_sign( let response = app.execute_contract( Addr::unchecked("relayer"), multisig_prover_address.clone(), - &xrpl_multisig_prover::contract::ExecuteMsg::TicketCreate, + &xrpl_multisig_prover::msg::ExecuteMsg::TicketCreate, &[], ); assert!(response.is_ok()); @@ -268,7 +268,7 @@ pub fn construct_xrpl_payment_proof_and_sign( let response = app.execute_contract( Addr::unchecked("relayer"), multisig_prover_address.clone(), - &xrpl_multisig_prover::contract::ExecuteMsg::ConstructProof { + &xrpl_multisig_prover::msg::ExecuteMsg::ConstructProof { message_id: message.cc_id.clone(), }, &[Coin { @@ -293,7 +293,7 @@ pub fn construct_xrpl_signer_list_set_proof_and_sign( let response = app.execute_contract( Addr::unchecked("relayer"), multisig_prover_address.clone(), - &xrpl_multisig_prover::contract::ExecuteMsg::UpdateWorkerSet, + &xrpl_multisig_prover::msg::ExecuteMsg::UpdateWorkerSet, &[], ); let response = response.unwrap(); @@ -324,7 +324,7 @@ pub fn sign_xrpl_proof( &session_id, &xrpl_signer_address ) { - xrpl_multisig_prover::contract::GetMessageToSignResponse { tx_hash } => tx_hash, + xrpl_multisig_prover::msg::GetMessageToSignResponse { tx_hash } => tx_hash, }; let signature = tofn::ecdsa::sign( @@ -402,7 +402,7 @@ pub fn get_xrpl_worker_set( ) -> multisig::worker_set::WorkerSet { let query_response = app.wrap().query_wasm_smart( multisig_prover_address, - &xrpl_multisig_prover::contract::QueryMsg::GetWorkerSet, + &xrpl_multisig_prover::msg::QueryMsg::GetWorkerSet, ); assert!(query_response.is_ok()); query_response.unwrap() @@ -412,10 +412,10 @@ pub fn get_xrpl_proof( app: &mut App, multisig_prover_address: &Addr, multisig_session_id: &Uint64, -) -> xrpl_multisig_prover::contract::GetProofResponse { +) -> xrpl_multisig_prover::msg::GetProofResponse { let query_response = app.wrap().query_wasm_smart( multisig_prover_address, - &xrpl_multisig_prover::contract::QueryMsg::GetProof { + &xrpl_multisig_prover::msg::QueryMsg::GetProof { multisig_session_id: *multisig_session_id, }, ); @@ -428,10 +428,10 @@ pub fn get_xrpl_message_to_sign( multisig_prover_address: &Addr, multisig_session_id: &Uint64, xrpl_signer_address: &String, -) -> xrpl_multisig_prover::contract::GetMessageToSignResponse { +) -> xrpl_multisig_prover::msg::GetMessageToSignResponse { let query_response = app.wrap().query_wasm_smart( multisig_prover_address, - &xrpl_multisig_prover::contract::QueryMsg::GetMessageToSign { + &xrpl_multisig_prover::msg::QueryMsg::GetMessageToSign { multisig_session_id: *multisig_session_id, signer_xrpl_address: xrpl_signer_address.clone(), }, @@ -448,7 +448,7 @@ pub fn finalize_xrpl_proof( let response = app.execute_contract( Addr::unchecked("relayer"), multisig_prover_address.clone(), - &xrpl_multisig_prover::contract::ExecuteMsg::FinalizeProof { + &xrpl_multisig_prover::msg::ExecuteMsg::FinalizeProof { multisig_session_id: *multisig_session_id, }, &[], @@ -460,14 +460,14 @@ pub fn finalize_xrpl_proof( pub fn xrpl_update_tx_status( app: &mut App, multisig_prover_address: &Addr, - cc_id: CrossChainId, + message_id: CrossChainId, message_status: xrpl_voting_verifier::execute::MessageStatus, ) { let response = app.execute_contract( Addr::unchecked("relayer"), multisig_prover_address.clone(), - &xrpl_multisig_prover::contract::ExecuteMsg::UpdateTxStatus { - cc_id, + &xrpl_multisig_prover::msg::ExecuteMsg::UpdateTxStatus { + message_id, message_status, }, &[], @@ -980,7 +980,7 @@ pub fn setup_xrpl(protocol: &mut Protocol, chain_name: ChainName) -> Chain { let response = protocol.app.execute_contract( Addr::unchecked("doesn't matter"), // TODO: should fail unless admin multisig_prover_address.clone(), - &xrpl_multisig_prover::contract::ExecuteMsg::RegisterToken { + &xrpl_multisig_prover::msg::ExecuteMsg::RegisterToken { denom: XRP_DENOMINATION.to_string(), token: XRPLToken { issuer: "".to_string(), diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index 5f2947fae..0d38e9a0a 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -185,7 +185,7 @@ fn xrpl_worker_set_can_be_initialized_and_then_manually_updated() { ); assert!(matches!( proof, - xrpl_multisig_prover::contract::GetProofResponse::Completed { .. } + xrpl_multisig_prover::msg::GetProofResponse::Completed { .. } )); println!("SignerListSet proof: {:?}", proof); From 1cdcdb995b773697a30b7a0adf0a999d635cdccb Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 7 Feb 2024 17:09:30 +0200 Subject: [PATCH 23/80] xrpl message verification function --- contracts/xrpl-multisig-prover/src/error.rs | 3 +++ contracts/xrpl-multisig-prover/src/query.rs | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 957b6a992..594b4b209 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -87,6 +87,9 @@ pub enum ContractError { #[error("failed to serialize")] FailedToSerialize, + + #[error("signature verification failed")] + SignatureVerificationFailed, } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index c42b3617d..faa8bd005 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -1,6 +1,7 @@ use cosmwasm_std::{StdResult, Uint64, HexBinary, Storage}; -use multisig::types::MultisigState; +use multisig::{key::Signature, types::MultisigState}; +use multisig::key::PublicKey; // TODO: remove dependency? use k256::{ecdsa, schnorr::signature::SignatureEncoding}; @@ -50,6 +51,15 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, }) } +pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: PublicKey, signature: Signature) -> StdResult { + let signer_xrpl_address = xrpl_multisig::public_key_to_xrpl_address(public_key.clone()); + let m = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; + + // m.tx_hash is going to be over 32 bytes due to inclusion of the signer address, so it has to be passed unchecked + signature.verify(&multisig::types::MsgToSign::unchecked(m.tx_hash), &public_key) + .map_err(|_e| ContractError::SignatureVerificationFailed.into()) +} + pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: &Uint64) -> StdResult { let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; From 198808efdfe518559c6fa9dec782bf16685c2198 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Thu, 8 Feb 2024 18:17:51 +0200 Subject: [PATCH 24/80] xrpl: sort signers in multisig transactions and support ed25519 public keys in serialization --- contracts/xrpl-multisig-prover/src/query.rs | 2 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 104 ++++++++++++++++-- 2 files changed, 95 insertions(+), 11 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index faa8bd005..4d126fa56 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -17,7 +17,7 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul let xrpl_address = xrpl_multisig::public_key_to_xrpl_address(axelar_signer.pub_key.clone()); Ok(XRPLSigner { account: xrpl_address, - signing_pub_key: axelar_signer.pub_key.clone().into(), + signing_pub_key: axelar_signer.pub_key.clone(), // TODO: should work with Ed25519 signatures too txn_signature: HexBinary::from(ecdsa::Signature::to_der( &ecdsa::Signature::try_from(signature.clone().as_ref()) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index c02ec8474..ca4b7eae0 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -5,6 +5,7 @@ use axelar_wasm_std::{nonempty, FnExt}; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; +use multisig::key::PublicKey; use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; use bigdecimal::{BigDecimal, Signed, ToPrimitive, Zero}; @@ -249,7 +250,7 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { pub struct XRPLSigner { pub account: String, pub txn_signature: HexBinary, - pub signing_pub_key: HexBinary, + pub signing_pub_key: PublicKey, } #[cw_serde] @@ -446,9 +447,16 @@ impl XRPLTypedSerialize for XRPLPaymentAmount { impl XRPLSerialize for XRPLSignedTransaction { fn xrpl_serialize(self: &XRPLSignedTransaction) -> Result, ContractError> { + let mut sorted_signers = self.signers.clone(); + sorted_signers.sort_by(|a, b| { + // the Signers array must be sorted based on the numeric value of the signer addresses + // https://xrpl.org/multi-signing.html#sending-multi-signed-transactions + let a = bs58::decode(a.account.clone()).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); + let b = bs58::decode(b.account.clone()).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); + return a.cmp(&b); + }); let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; - obj.add_field(3, &XRPLArray{ field_code: 16, items: self.signers.clone() })?; - //obj.add_field(9, &XRPLMemo(memo))?; + obj.add_field(3, &XRPLArray{ field_code: 16, items: sorted_signers })?; obj.xrpl_serialize() } @@ -524,6 +532,21 @@ impl XRPLTypedSerialize for HexBinary { const TYPE_CODE: u8 = BLOB_TYPE_CODE; } +impl XRPLSerialize for PublicKey { + fn xrpl_serialize(&self) -> Result, ContractError> { + match self.clone() { + // rippled prefixes Ed25519 public keys with the byte 0xED so both types of public key are 33 bytes. + // https://xrpl.org/cryptographic-keys.html + Self::Ed25519(hex) => HexBinary::from_hex(format!("ED{}", hex.to_hex()).as_str())?.xrpl_serialize(), + Self::Ecdsa(hex) => hex.xrpl_serialize(), + } + } +} + +impl XRPLTypedSerialize for PublicKey { + const TYPE_CODE: u8 = BLOB_TYPE_CODE; +} + impl XRPLSerialize for XRPLSigner { fn xrpl_serialize(&self) -> Result, ContractError> { let mut obj = XRPLObject::new(); @@ -888,7 +911,7 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: CrossChainId) #[cfg(test)] mod tests { - use multisig::key::PublicKey; + use multisig::key::{KeyType, PublicKey}; use super::*; @@ -1186,12 +1209,12 @@ mod tests { XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), txn_signature: HexBinary::from(hex::decode("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), }, XRPLSigner{ account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), txn_signature: HexBinary::from(hex::decode("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), } ], }; @@ -1202,6 +1225,67 @@ mod tests { ); } + #[test] + fn tx_serialization_sort_signers() { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), + fee: 30, + sequence: Sequence::Ticket(44218193), + amount: XRPLPaymentAmount::Drops(100000000), + destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), + multisig_session_id: Some(Uint64::from(5461264u64)), + }), signers: vec![ + XRPLSigner{ + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + txn_signature: HexBinary::from(hex::decode("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), + }, + XRPLSigner{ + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + txn_signature: HexBinary::from(hex::decode("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), + }, + ], + }; + let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); + assert_eq!( + "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + hex::encode_upper(encoded_signed_tx) + ); + } + + #[test] + fn tx_serialization_ed25519_signers() { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { + account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(), + fee: 30, + sequence: Sequence::Ticket(45205896), + amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string() }, XRPLTokenAmount("100000000".to_string())), + destination: nonempty::String::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo").unwrap(), + multisig_session_id: Some(Uint64::from(5461264u64)), + }), signers: vec![ + XRPLSigner{ + account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".to_string(), + txn_signature: HexBinary::from(hex::decode("531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ed25519, HexBinary::from(hex::decode("45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f").unwrap()))).unwrap(), + }, + XRPLSigner{ + account: "rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5".to_string(), + txn_signature: HexBinary::from(hex::decode("76CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E0106").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ed25519, HexBinary::from(hex::decode("dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b").unwrap()))).unwrap(), + }, + ], + }; + let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); + assert_eq!( + "12000022000000002400000000202902B1C98861D6838D7EA4C680000000000000000000000000004554480000000000EC792533BC26024CFAA5DDC2D04128E59581309C68400000000000001E73008114EC792533BC26024CFAA5DDC2D04128E59581309C831439659AAAD4DC8603798352FCF954419A67977536F3E0107321EDDD9822C7FA239DDA9913EBEE813ECBE69E35D88FF651548D5CC42C033A8A667B744076CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E010681142B3CF7B1986F5CB4EFEF11F933F40EC3106412C2E1E0107321ED45E67EAF446E6C26EB3A2B55B64339ECF3A4D1D03180BEE20EB5AFDD23FA644F7440531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09811472C14C0DB6CEF64A87CC3D152D7B0E917D372BE7E1F1F9EA7D03535510E1F1", + hex::encode_upper(encoded_signed_tx) + ); + } + + #[test] fn serialize_xrpl_signed_xrp_ticket_create_transaction() { let signed_tx = XRPLSignedTransaction { @@ -1215,12 +1299,12 @@ mod tests { XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), txn_signature: HexBinary::from(hex::decode("304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), }, XRPLSigner{ account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), txn_signature: HexBinary::from(hex::decode("3045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C58").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), }, ] }; @@ -1255,12 +1339,12 @@ mod tests { XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), txn_signature: HexBinary::from(hex::decode("3045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B39").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), }, XRPLSigner{ account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), txn_signature: HexBinary::from(hex::decode("3044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A").unwrap()), - signing_pub_key: HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()), + signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), }, ], }; From 150984109180622bbe0de62d4dd4838e1dc87a6d Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Thu, 8 Feb 2024 18:41:37 +0200 Subject: [PATCH 25/80] xrpl message id is tx_id without event index --- contracts/xrpl-multisig-prover/src/error.rs | 3 --- contracts/xrpl-multisig-prover/src/types.rs | 22 +-------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 594b4b209..9508d94cf 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -79,9 +79,6 @@ pub enum ContractError { #[error("invalid blob")] InvalidBlob, - #[error("invalid message ID {0}")] - InvalidMessageID(String), - #[error("failed to encode signature")] FailedToEncodeSignature, diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index af9eb4df7..5833fa287 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -16,33 +16,13 @@ pub enum TransactionStatus { FailedOffChain, } -// TODO: import from verifier -pub fn parse_message_id( - message_id: &nonempty::String, -) -> Result<(nonempty::String, u64), ContractError> { - // expected format: : - let components = message_id.split(":").collect::>(); - - if components.len() != 2 { - return Err(ContractError::InvalidMessageID(message_id.to_string())); - } - - Ok(( - components[0].try_into()?, - components[1] - .parse::() - .map_err(|_| ContractError::InvalidMessageID(message_id.to_string()))?, - )) -} - #[cw_serde] pub struct TxHash(pub HexBinary); impl TryFrom for TxHash { type Error = ContractError; fn try_from(cc_id: CrossChainId) -> Result { - // TODO check this is correct - let (tx_id, _event_index) = parse_message_id(&cc_id.id)?; + let tx_id = &cc_id.id; Ok(Self(HexBinary::from_hex(tx_id.to_ascii_lowercase().as_str())?)) } } From 6d459bc51c16cc98385a14debe6f825a5ecff525 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Thu, 8 Feb 2024 18:42:12 +0200 Subject: [PATCH 26/80] xrpl remove obsolete todo --- contracts/xrpl-multisig-prover/src/error.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 9508d94cf..3bb9eb8ba 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -74,8 +74,6 @@ pub enum ContractError { #[error("signing session not completed")] SigningSessionNotCompleted, - // TODO: do not expose low level errors - #[error("invalid blob")] InvalidBlob, From 3ed191b146d43d15b5260ff4805024a752dc5880 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Thu, 8 Feb 2024 18:44:59 +0200 Subject: [PATCH 27/80] xrpl remove obsolete function --- contracts/xrpl-multisig-prover/src/xrpl_multisig.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index ca4b7eae0..7faaccb20 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -915,10 +915,6 @@ mod tests { use super::*; - fn serialize(value: T) -> String { - hex::encode_upper(value.xrpl_serialize().unwrap()) - } - #[macro_export] macro_rules! assert_hex_eq { ($expected:expr, $actual:expr) => { From 0f1ce94af4b7c6252e43253b0681f961acc417c7 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Thu, 8 Feb 2024 18:45:35 +0200 Subject: [PATCH 28/80] xrpl remove obsolete import --- contracts/xrpl-multisig-prover/src/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 5833fa287..e9e2bac14 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -1,4 +1,3 @@ -use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256}; From f21179e64193704b5269973139cb697bafd149eb Mon Sep 17 00:00:00 2001 From: k4m4 Date: Thu, 8 Feb 2024 18:37:17 +0100 Subject: [PATCH 29/80] Revert "xrpl message id is tx_id without event index" This reverts commit 150984109180622bbe0de62d4dd4838e1dc87a6d. --- contracts/xrpl-multisig-prover/src/error.rs | 3 +++ contracts/xrpl-multisig-prover/src/types.rs | 22 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 3bb9eb8ba..07f947bec 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -77,6 +77,9 @@ pub enum ContractError { #[error("invalid blob")] InvalidBlob, + #[error("invalid message ID {0}")] + InvalidMessageID(String), + #[error("failed to encode signature")] FailedToEncodeSignature, diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index e9e2bac14..42975a482 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -15,13 +15,33 @@ pub enum TransactionStatus { FailedOffChain, } +// TODO: import from verifier +pub fn parse_message_id( + message_id: &nonempty::String, +) -> Result<(nonempty::String, u64), ContractError> { + // expected format: : + let components = message_id.split(":").collect::>(); + + if components.len() != 2 { + return Err(ContractError::InvalidMessageID(message_id.to_string())); + } + + Ok(( + components[0].try_into()?, + components[1] + .parse::() + .map_err(|_| ContractError::InvalidMessageID(message_id.to_string()))?, + )) +} + #[cw_serde] pub struct TxHash(pub HexBinary); impl TryFrom for TxHash { type Error = ContractError; fn try_from(cc_id: CrossChainId) -> Result { - let tx_id = &cc_id.id; + // TODO check this is correct + let (tx_id, _event_index) = parse_message_id(&cc_id.id)?; Ok(Self(HexBinary::from_hex(tx_id.to_ascii_lowercase().as_str())?)) } } From 92836ab54f508f56b8f6ed6de1a6945e81d87cd1 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Thu, 8 Feb 2024 18:37:33 +0100 Subject: [PATCH 30/80] Revert "xrpl remove obsolete import" This reverts commit 0f1ce94af4b7c6252e43253b0681f961acc417c7. --- contracts/xrpl-multisig-prover/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 42975a482..af9eb4df7 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256}; From 4678211405c5f1030eaa659bfa819e92028c69f6 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Thu, 8 Feb 2024 19:03:59 +0100 Subject: [PATCH 31/80] xrpl-multisig-prover: remove finalize_proof & modify update_tx_status --- .../xrpl-multisig-prover/src/contract.rs | 73 +++++----- contracts/xrpl-multisig-prover/src/error.rs | 3 + contracts/xrpl-multisig-prover/src/msg.rs | 11 +- contracts/xrpl-multisig-prover/src/querier.rs | 4 +- contracts/xrpl-multisig-prover/src/query.rs | 18 ++- contracts/xrpl-multisig-prover/src/state.rs | 2 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 32 +++-- integration-tests/tests/message_routing.rs | 36 +++-- integration-tests/tests/test_utils/mod.rs | 128 +++++++++++------- integration-tests/tests/update_worker_set.rs | 12 +- 10 files changed, 179 insertions(+), 140 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index e144644cf..286abd40d 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -3,7 +3,7 @@ use std::str::FromStr; #[cfg(not(feature = "library"))] use axelar_wasm_std::Threshold; use connection_router::state::{CrossChainId, ChainName}; -use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{ entry_point, Storage, wasm_execute, SubMsg, Reply, DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_binary, Deps, StdResult, Binary, Addr, HexBinary, @@ -11,7 +11,12 @@ use cosmwasm_std::{ use xrpl_voting_verifier::execute::MessageStatus; use crate::{ - axelar_workers, error::ContractError, msg::{ExecuteMsg, GetProofResponse, QueryMsg}, querier::{Querier, XRPL_CHAIN_NAME}, query::{get_proof, self}, reply, state::{Config, CONFIG, REPLY_TX_HASH, TOKENS, CURRENT_WORKER_SET, NEXT_WORKER_SET, SIGNED_TO_UNSIGNED_TX_HASH, NEXT_SEQUENCE_NUMBER, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS}, types::*, xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount} + error::ContractError, + state::{Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, MULTISIG_SESSION_TX, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO}, + msg::{ExecuteMsg, QueryMsg}, + reply, + types::*, + xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount, XRPLSerialize}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query, }; pub const START_MULTISIG_REPLY_ID: u64 = 1; @@ -60,6 +65,7 @@ pub fn instantiate( worker_set_diff_threshold: msg.worker_set_diff_threshold, xrpl_fee: msg.xrpl_fee, ticket_count_threshold: msg.ticket_count_threshold, + key_type: multisig::key::KeyType::Ecdsa, }; CONFIG.save(deps.storage, &config)?; @@ -84,7 +90,6 @@ pub fn instantiate( Ok(Response::new().add_message(msg)) } - fn register_token( storage: &mut dyn Storage, denom: String, @@ -112,14 +117,11 @@ pub fn execute( ExecuteMsg::ConstructProof { message_id } => { construct_payment_proof(deps.storage, querier, info, env.contract.address, &config, message_id) }, - ExecuteMsg::FinalizeProof { multisig_session_id } => { - finalize_proof(deps.storage, querier, &multisig_session_id) - }, ExecuteMsg::UpdateWorkerSet {} => { construct_signer_list_set_proof(deps.storage, querier, env, &config) }, - ExecuteMsg::UpdateTxStatus { message_id, message_status } => { - update_tx_status(deps.storage, querier, message_id, &message_status, config.axelar_multisig_address) + ExecuteMsg::UpdateTxStatus { multisig_session_id, signers, message_status } => { + update_tx_status(deps.storage, querier, &multisig_session_id, &signers, &message_status, config.axelar_multisig_address) }, ExecuteMsg::TicketCreate {} => { construct_ticket_create_proof(deps.storage, env.contract.address, &config) @@ -129,28 +131,6 @@ pub fn execute( Ok(res) } -// TODO: proof submitted on XRPL may be -// different than the one submitted here -// (i.e., the signed hash is different because more signatures -// were gathered after the TX was broadcasted to XRPL -// but before finalize_proof was called) -fn finalize_proof( - storage: &mut dyn Storage, - querier: Querier, - multisig_session_id: &Uint64 -) -> Result { - match get_proof(storage, querier, multisig_session_id)? { - GetProofResponse::Pending { unsigned_tx_hash: _ } => { - Err(ContractError::SigningSessionNotCompleted) - }, - GetProofResponse::Completed { unsigned_tx_hash, tx_blob } => { - let tx_hash = xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?; - SIGNED_TO_UNSIGNED_TX_HASH.save(storage, tx_hash, &unsigned_tx_hash)?; - Ok(Response::default()) - } - } -} - fn construct_payment_proof( storage: &mut dyn Storage, querier: Querier, @@ -287,17 +267,39 @@ fn construct_ticket_create_proof( fn update_tx_status( storage: &mut dyn Storage, querier: Querier, - message_id: CrossChainId, + multisig_session_id: &Uint64, + signers: &Vec, status: &MessageStatus, axelar_multisig_address: impl Into, ) -> Result { - if !querier.get_message_confirmation(message_id.clone(), status)? { - return Err(ContractError::InvalidMessageStatus) + let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; + let tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; + let multisig_session = querier.get_multisig_session(multisig_session_id.clone())?; + + let axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)> = multisig_session.signers + .iter() + .filter(|(signer, signature)| signature.is_some() && signers.contains(&signer.address)) + .map(|(signer, signature)| (signer.clone(), signature.clone().unwrap())) + .collect(); + + if axelar_signers.len() != signers.len() { + return Err(ContractError::SignatureNotFound); } - let tx_hash: TxHash = TxHash::try_from(message_id)?; + let signed_tx = query::make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers, multisig_session_id)?; // TODO: RELOCATE FUNCTION + let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); + let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?).into(); + + let cc_id = CrossChainId { + chain: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), + id: axelar_wasm_std::nonempty::String::try_from(tx_hash.to_string() + ":0")?, // TODO: FIX + }; + + if !querier.get_message_confirmation(cc_id.clone(), status)? { + return Err(ContractError::InvalidMessageStatus) + } - match xrpl_multisig::update_tx_status(storage, axelar_multisig_address, tx_hash, status.clone().into())? { + match xrpl_multisig::update_tx_status(storage, axelar_multisig_address, unsigned_tx_hash, status.clone().into())? { None => Ok(Response::default()), Some(msg) => Ok(Response::new().add_message(msg)) } @@ -316,7 +318,6 @@ pub fn reply( .map_err(axelar_wasm_std::ContractError::from) } - #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { let config = CONFIG.load(deps.storage)?; diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 07f947bec..4fedd2e29 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -88,6 +88,9 @@ pub enum ContractError { #[error("signature verification failed")] SignatureVerificationFailed, + + #[error("signature not found")] + SignatureNotFound, } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/msg.rs b/contracts/xrpl-multisig-prover/src/msg.rs index 15047b103..2bb2b6919 100644 --- a/contracts/xrpl-multisig-prover/src/msg.rs +++ b/contracts/xrpl-multisig-prover/src/msg.rs @@ -1,6 +1,6 @@ use connection_router::state::CrossChainId; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{HexBinary, Uint64}; +use cosmwasm_std::{HexBinary, Uint64, Addr}; use xrpl_voting_verifier::execute::MessageStatus; use crate::types::{TxHash, XRPLToken}; @@ -34,8 +34,11 @@ pub struct GetMessageToSignResponse { pub enum ExecuteMsg { RegisterToken { denom: String, token: XRPLToken }, ConstructProof { message_id: CrossChainId }, - FinalizeProof { multisig_session_id: Uint64 }, - UpdateTxStatus { message_id: CrossChainId, message_status: MessageStatus }, + UpdateTxStatus { + multisig_session_id: Uint64, + signers: Vec, + message_status: MessageStatus, + }, UpdateWorkerSet, TicketCreate, -} \ No newline at end of file +} diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index f109c3932..2735ff311 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -5,7 +5,7 @@ use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; use cosmwasm_std::{ to_binary, QueryRequest, WasmQuery, QuerierWrapper, Uint64, }; -use multisig::{key::{KeyType, PublicKey}, msg::Multisig}; +use multisig::{key::PublicKey, msg::Multisig}; use xrpl_voting_verifier::execute::MessageStatus; use crate::{ @@ -52,7 +52,7 @@ impl<'a> Querier<'a> { query(self.querier, self.config.axelar_multisig_address.to_string(), &multisig::msg::QueryMsg::GetPublicKey { worker_address, - key_type: KeyType::Ecdsa, + key_type: self.config.key_type, }, ) } diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 4d126fa56..5a75dbebd 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -7,7 +7,7 @@ use multisig::key::PublicKey; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use crate::{ - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, msg::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, axelar_workers::WorkerSet, + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, msg::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, }; pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>, multisig_session_id: &Uint64) -> Result { @@ -15,14 +15,20 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul .iter() .map(|(axelar_signer, signature)| -> Result { let xrpl_address = xrpl_multisig::public_key_to_xrpl_address(axelar_signer.pub_key.clone()); - Ok(XRPLSigner { - account: xrpl_address, - signing_pub_key: axelar_signer.pub_key.clone(), - // TODO: should work with Ed25519 signatures too - txn_signature: HexBinary::from(ecdsa::Signature::to_der( + let txn_signature = match signature { + // TODO: use unwrapped signature instead of ignoring it + multisig::key::Signature::Ecdsa(_) | + multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( &ecdsa::Signature::try_from(signature.clone().as_ref()) .map_err(|_| ContractError::FailedToEncodeSignature)? ).to_vec()), + _ => unimplemented!("Unsupported signature type"), + }; + + Ok(XRPLSigner { + account: xrpl_address, + signing_pub_key: axelar_signer.pub_key.clone().into(), + txn_signature, }) }) .collect::, ContractError>>()?; diff --git a/contracts/xrpl-multisig-prover/src/state.rs b/contracts/xrpl-multisig-prover/src/state.rs index 8da8261fc..f824f69b0 100644 --- a/contracts/xrpl-multisig-prover/src/state.rs +++ b/contracts/xrpl-multisig-prover/src/state.rs @@ -17,6 +17,7 @@ pub struct Config { pub worker_set_diff_threshold: u32, pub xrpl_fee: u64, pub ticket_count_threshold: u32, + pub key_type: multisig::key::KeyType, } pub const CONFIG: Item = Item::new("config"); @@ -32,7 +33,6 @@ pub const MULTISIG_SESSION_TX: Map = Map::new("multisig_session_tx" pub const NEXT_SEQUENCE_NUMBER: Item = Item::new("next_sequence_number"); pub const LAST_ASSIGNED_TICKET_NUMBER: Item = Item::new("last_assigned_ticket_number"); -pub const SIGNED_TO_UNSIGNED_TX_HASH: Map = Map::new("signed_to_unsigned_tx_hash"); pub const MESSAGE_ID_TO_TICKET: Map = Map::new("message_id_to_ticket"); pub const CONFIRMED_TRANSACTIONS: Map = Map::new("confirmed_transactions"); pub const AVAILABLE_TICKETS: Item> = Item::new("available_tickets"); diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 7faaccb20..fb590a8b6 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -13,7 +13,7 @@ use std::str::FromStr; use crate::{ error::ContractError, - state::{Config, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS, TRANSACTION_INFO, NEXT_SEQUENCE_NUMBER, CONFIRMED_TRANSACTIONS, MESSAGE_ID_TO_TICKET, LATEST_SEQUENTIAL_TX_HASH, SIGNED_TO_UNSIGNED_TX_HASH, NEXT_WORKER_SET, CURRENT_WORKER_SET}, + state::{Config, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS, TRANSACTION_INFO, NEXT_SEQUENCE_NUMBER, CONFIRMED_TRANSACTIONS, MESSAGE_ID_TO_TICKET, LATEST_SEQUENTIAL_TX_HASH, NEXT_WORKER_SET, CURRENT_WORKER_SET}, types::*, axelar_workers::{WorkerSet, AxelarSigner}, }; @@ -828,8 +828,12 @@ fn mark_ticket_unavailable(storage: &mut dyn Storage, ticket: u32) -> Result<(), Ok(()) } -pub fn update_tx_status(storage: &mut dyn Storage, axelar_multisig_address: impl Into, tx_hash: TxHash, new_status: TransactionStatus) -> Result, ContractError> { - let unsigned_tx_hash = SIGNED_TO_UNSIGNED_TX_HASH.load(storage, tx_hash.clone())?; +pub fn update_tx_status( + storage: &mut dyn Storage, + axelar_multisig_address: impl Into, + unsigned_tx_hash: TxHash, + new_status: TransactionStatus +) -> Result, ContractError> { let mut tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; if tx_info.status != TransactionStatus::Pending { return Err(ContractError::TransactionStatusAlreadyUpdated); @@ -1193,13 +1197,19 @@ mod tests { #[test] fn serialize_xrpl_signed_xrp_payment_transaction() { - let signed_tx = XRPLSignedTransaction { + let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), fee: 30, - sequence: Sequence::Ticket(44218193), - amount: XRPLPaymentAmount::Drops(100000000), - destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), + sequence: Sequence::Ticket(44218194), + amount: XRPLPaymentAmount::Token( + XRPLToken { + currency: "ETH".to_string(), + issuer: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), + }, + XRPLTokenAmount("100000000".to_string()), + ), + destination: nonempty::String::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo").unwrap(), multisig_session_id: Some(Uint64::from(5461264u64)), }), signers: vec![ XRPLSigner{ @@ -1216,14 +1226,14 @@ mod tests { }; let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); assert_eq!( - "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + "12000022000000002400000000202902A2B75261D6838D7EA4C680000000000000000000000000004554480000000000447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E468400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831439659AAAD4DC8603798352FCF954419A67977536F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674473045022100A016B2A54600BE2D32B9EE695BD8111E0C6D755B5BFCEBCB16DE590AC3AF2AAC022051893F471D8E6998B1E98045CF94235A491A1E435B93821DBB2D4E2030E45C328114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100B06F72C1DD288D9453913272318B29582C50F4C170CDE9BDDFAE44076F189E2D0220715403F65E7630AE77ACDD15D78C215F3AB2FD2CAE764F254D060051B66505418114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); } #[test] fn tx_serialization_sort_signers() { - let signed_tx = XRPLSignedTransaction { + let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), fee: 30, @@ -1253,7 +1263,7 @@ mod tests { #[test] fn tx_serialization_ed25519_signers() { - let signed_tx = XRPLSignedTransaction { + let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(), fee: 30, @@ -1311,7 +1321,6 @@ mod tests { ); } - #[test] fn serialize_xrpl_signed_signer_list_set_transaction() { let signed_tx = XRPLSignedTransaction { @@ -1351,7 +1360,6 @@ mod tests { ); } - #[test] fn ed25519_public_key_to_xrpl_address() { assert_eq!( diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index fbfee10da..53e770cc8 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -1,5 +1,7 @@ use connection_router::state::{CrossChainId, Message}; -use cosmwasm_std::{HexBinary, Uint128}; +use cosmwasm_std::{HexBinary, Uint128, Coin}; + +use crate::test_utils::{XRP_DENOMINATION, ETH_DENOMINATION}; mod test_utils; /// Tests that a single message can be routed fully through the protocol. Submits a message to the @@ -138,15 +140,9 @@ fn xrpl_ticket_create_can_be_proven() { )); println!("TicketCreate proof: {:?}", proof); - test_utils::finalize_xrpl_proof( - &mut protocol.app, - &xrpl.multisig_prover_address, - &session_id, - ); - let proof_msg_id = CrossChainId { chain: xrpl.chain_name.clone(), - id: "B0660BA5DC6C96B20D4E4DC19E2BF421B0F7D9E62FDC3B3F2AFCB322A6E3582D:0" + id: "3da9f142fdc0d570d040dde73de20a0625a549fc70cc564849dbc746b5c6469a:0" .to_string() .try_into() .unwrap(), @@ -170,7 +166,8 @@ fn xrpl_ticket_create_can_be_proven() { test_utils::xrpl_update_tx_status( &mut protocol.app, &xrpl.multisig_prover_address, - proof_msg_id, + workers.iter().map(|w| w.addr.clone()).collect(), + session_id, xrpl_voting_verifier::execute::MessageStatus::Succeeded ); } @@ -182,7 +179,7 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { let msg = Message { cc_id: CrossChainId { chain: source_chain.chain_name.clone(), - id: "0x88d7956fd7b6fcec846548d83bd25727f2585b4be3add21438ae9fbb34625924:3" + id: "0xaff42a67c474758ce97bd9b69c395c6dc6019707b400e06c30b0878a9357b2ea:3" .to_string() .try_into() .unwrap(), @@ -192,7 +189,7 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { .to_string() .try_into() .unwrap(), - destination_address: "r4ueUc4mo1X7yvmg78s5gV9uLhgLnPkJxz" + destination_address: "raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo" .to_string() .try_into() .unwrap(), @@ -242,6 +239,12 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { &protocol.multisig_address, msg, &workers, + &[Coin { + denom: ETH_DENOMINATION.to_string(), + // amount: Uint128::MAX, + // amount: Uint128::from(10u128.pow(29)), // scaled down to 10^17 drops = max XRP + amount: Uint128::from(100000000u128) + }], ); let proof = test_utils::get_xrpl_proof( @@ -256,15 +259,9 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { xrpl_multisig_prover::msg::GetProofResponse::Completed { .. } )); - test_utils::finalize_xrpl_proof( - &mut protocol.app, - &xrpl.multisig_prover_address, - &session_id, - ); - let proof_msg_id = CrossChainId { chain: xrpl.chain_name.clone(), - id: "2B779BF106E36B5FA21518E279DC6A02E26DF63115010EB40CA7B0FCD73E28C9:0" + id: "8108b96bdbc5ff44fd868d973bdcaae3f38e2fe3906a33a8dc1d881b20498ff7:0" .to_string() .try_into() .unwrap(), @@ -289,7 +286,8 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { test_utils::xrpl_update_tx_status( &mut protocol.app, &xrpl.multisig_prover_address, - proof_msg_id, + workers.iter().map(|w| w.addr.clone()).collect(), + session_id, xrpl_voting_verifier::execute::MessageStatus::Succeeded ); diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 705a2fe6d..9008680a1 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, ops::Add}; +use std::{fmt::Debug, ops::Add, str::FromStr}; use axelar_wasm_std::{ nonempty, @@ -12,17 +12,18 @@ use cosmwasm_std::{ }; use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; -use k256::ecdsa; +use k256::{ecdsa, pkcs8::der::Decode}; use multisig::{ key::{KeyType, PublicKey}, worker_set::WorkerSet, }; use multisig_prover::encoding::{make_operators, Encoder}; -use tofn::ecdsa::KeyPair; +use tofn::{ecdsa::KeyPair, ed25519}; use xrpl_multisig_prover::types::XRPLToken; pub const AXL_DENOMINATION: &str = "uaxl"; pub const XRP_DENOMINATION: &str = "uxrp"; +pub const ETH_DENOMINATION: &str = "ueth"; fn get_event_attribute<'a>( events: &'a [Event], @@ -264,6 +265,7 @@ pub fn construct_xrpl_payment_proof_and_sign( multisig_address: &Addr, message: Message, workers: &Vec, + coins: &[Coin], ) -> Uint64 { let response = app.execute_contract( Addr::unchecked("relayer"), @@ -271,12 +273,7 @@ pub fn construct_xrpl_payment_proof_and_sign( &xrpl_multisig_prover::msg::ExecuteMsg::ConstructProof { message_id: message.cc_id.clone(), }, - &[Coin { - denom: XRP_DENOMINATION.to_string(), - // amount: Uint128::MAX, - // amount: Uint128::from(10u128.pow(29)), // scaled down to 10^17 drops = max XRP - amount: Uint128::from(100000000u128) - }], + coins, ); assert!(response.is_ok()); let response = response.unwrap(); @@ -336,15 +333,13 @@ pub fn sign_xrpl_proof( ) .unwrap(); - // TODO: why from_der and then back to_der? - let sig = ecdsa::Signature::from_der(&signature).unwrap(); - let response = app.execute_contract( worker.addr.clone(), multisig_address.clone(), &multisig::msg::ExecuteMsg::SubmitSignature { session_id, - signature: HexBinary::from(sig.to_vec()), + // TODO: why from_der and then back to_der? + signature: HexBinary::from(ecdsa::Signature::from_der(&signature).unwrap().to_vec()), }, &[], ); @@ -440,35 +435,20 @@ pub fn get_xrpl_message_to_sign( query_response.unwrap() } -pub fn finalize_xrpl_proof( - app: &mut App, - multisig_prover_address: &Addr, - multisig_session_id: &Uint64, -) { - let response = app.execute_contract( - Addr::unchecked("relayer"), - multisig_prover_address.clone(), - &xrpl_multisig_prover::msg::ExecuteMsg::FinalizeProof { - multisig_session_id: *multisig_session_id, - }, - &[], - ); - - assert!(response.is_ok()); -} - pub fn xrpl_update_tx_status( app: &mut App, multisig_prover_address: &Addr, - message_id: CrossChainId, + signers: Vec, + multisig_session_id: Uint64, message_status: xrpl_voting_verifier::execute::MessageStatus, ) { let response = app.execute_contract( Addr::unchecked("relayer"), multisig_prover_address.clone(), &xrpl_multisig_prover::msg::ExecuteMsg::UpdateTxStatus { - message_id, message_status, + multisig_session_id, + signers, }, &[], ); @@ -523,14 +503,30 @@ pub fn setup_protocol(service_name: nonempty::String) -> Protocol { let genesis = Addr::unchecked("genesis"); // TODO: return relayer let relayer = Addr::unchecked("relayer"); + let xrpl_init_coins = vec![ + coins(u128::MAX, XRP_DENOMINATION), + coins(u128::MAX, ETH_DENOMINATION), + ].concat(); let mut app = App::new(|router, _, storage| { router .bank - .init_balance(storage, &genesis, coins(u128::MAX, AXL_DENOMINATION)) + .init_balance( + storage, + &genesis, + vec![ + coins(u128::MAX, AXL_DENOMINATION), + xrpl_init_coins.clone(), + ].concat()) .unwrap(); - // TODO: relocate - router.bank.init_balance(storage, &relayer, coins(u128::MAX, XRP_DENOMINATION)).unwrap() }); + + let response = app.send_tokens( + genesis.clone(), + relayer, + &xrpl_init_coins, + ); + + assert!(response.is_ok()); let router_admin_address = Addr::unchecked("admin"); let governance_address = Addr::unchecked("governance"); let nexus_gateway = Addr::unchecked("nexus_gateway"); @@ -799,6 +795,7 @@ pub fn workers_to_worker_set(protocol: &mut Protocol, workers: &Vec) -> ) } +// TODO: fix duplication pub fn xrpl_workers_to_worker_set(protocol: &mut Protocol, workers: &Vec) -> WorkerSet { // get public keys let mut pub_keys = vec![]; @@ -930,7 +927,26 @@ pub fn setup_chain(protocol: &mut Protocol, chain_name: ChainName) -> Chain { } } -pub fn setup_xrpl(protocol: &mut Protocol, chain_name: ChainName) -> Chain { +pub fn register_token( + protocol: &mut Protocol, + multisig_prover_address: Addr, + denom: String, + token: XRPLToken, +) { + let response = protocol.app.execute_contract( + Addr::unchecked("doesn't matter"), // TODO: should fail unless admin + multisig_prover_address.clone(), + &xrpl_multisig_prover::msg::ExecuteMsg::RegisterToken { + denom, + token, + }, + &[], + ); + assert!(response.is_ok()); +} + +pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { + let chain_name = ChainName::from_str("XRPL").unwrap(); let voting_verifier_address = instantiate_xrpl_voting_verifier( &mut protocol.app, xrpl_voting_verifier::msg::InstantiateMsg { @@ -944,7 +960,7 @@ pub fn setup_xrpl(protocol: &mut Protocol, chain_name: ChainName) -> Chain { voting_threshold: (9, 10).try_into().unwrap(), block_expiry: 10, confirmation_height: 5, - source_chain: ChainName::from_str("XRPL").unwrap(), + source_chain: chain_name.clone(), rewards_address: protocol.rewards_address.to_string(), }, ); @@ -955,6 +971,7 @@ pub fn setup_xrpl(protocol: &mut Protocol, chain_name: ChainName) -> Chain { verifier_address: voting_verifier_address.to_string(), }, ); + let xrpl_multisig_address = "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(); let multisig_prover_address = instantiate_xrpl_multisig_prover( &mut protocol.app, xrpl_multisig_prover::contract::InstantiateMsg { @@ -966,30 +983,37 @@ pub fn setup_xrpl(protocol: &mut Protocol, chain_name: ChainName) -> Chain { service_name: protocol.service_name.to_string(), worker_set_diff_threshold: 1, xrpl_fee: 30, - xrpl_multisig_address: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), + xrpl_multisig_address: xrpl_multisig_address.clone(), ticket_count_threshold: 1, - next_sequence_number: 44218441, - last_assigned_ticket_number: 44218188, + next_sequence_number: 45205906, + last_assigned_ticket_number: 45205895, available_tickets: vec![ vec![], - (44218189..44218438).collect::>() + (45205896..45205907).collect::>() ].concat(), }, ); - let response = protocol.app.execute_contract( - Addr::unchecked("doesn't matter"), // TODO: should fail unless admin + register_token( + protocol, multisig_prover_address.clone(), - &xrpl_multisig_prover::msg::ExecuteMsg::RegisterToken { - denom: XRP_DENOMINATION.to_string(), - token: XRPLToken { - issuer: "".to_string(), - currency: XRPLToken::NATIVE_CURRENCY.to_string(), - }, + XRP_DENOMINATION.to_string(), + XRPLToken { + issuer: "".to_string(), + currency: XRPLToken::NATIVE_CURRENCY.to_string(), }, - &[], ); - assert!(response.is_ok()); + + register_token( + protocol, + multisig_prover_address.clone(), + ETH_DENOMINATION.to_string(), + XRPLToken { + issuer: xrpl_multisig_address, + currency: "ETH".to_string(), + }, + ); + let response = protocol.app.execute_contract( protocol.governance_address.clone(), protocol.multisig_address.clone(), @@ -1294,7 +1318,7 @@ pub fn setup_xrpl_destination_test_case() -> (Protocol, Chain, Chain, Vec Date: Thu, 8 Feb 2024 19:11:32 +0100 Subject: [PATCH 32/80] xrpl-multisig-prover: restore signed_xrp_payment_transaction serlization test --- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index fb590a8b6..4c26e573c 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1201,15 +1201,9 @@ mod tests { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), fee: 30, - sequence: Sequence::Ticket(44218194), - amount: XRPLPaymentAmount::Token( - XRPLToken { - currency: "ETH".to_string(), - issuer: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), - }, - XRPLTokenAmount("100000000".to_string()), - ), - destination: nonempty::String::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo").unwrap(), + sequence: Sequence::Ticket(44218193), + amount: XRPLPaymentAmount::Drops(100000000), + destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), multisig_session_id: Some(Uint64::from(5461264u64)), }), signers: vec![ XRPLSigner{ @@ -1226,7 +1220,7 @@ mod tests { }; let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); assert_eq!( - "12000022000000002400000000202902A2B75261D6838D7EA4C680000000000000000000000000004554480000000000447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E468400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831439659AAAD4DC8603798352FCF954419A67977536F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674473045022100A016B2A54600BE2D32B9EE695BD8111E0C6D755B5BFCEBCB16DE590AC3AF2AAC022051893F471D8E6998B1E98045CF94235A491A1E435B93821DBB2D4E2030E45C328114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100B06F72C1DD288D9453913272318B29582C50F4C170CDE9BDDFAE44076F189E2D0220715403F65E7630AE77ACDD15D78C215F3AB2FD2CAE764F254D060051B66505418114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); } From c571ff82b986fbe5d2848ff0a0407ac26528cc41 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 13 Feb 2024 12:24:58 +0200 Subject: [PATCH 33/80] ampd xrpl signing started event --- Cargo.lock | 1 + ampd/Cargo.toml | 1 + ampd/src/handlers/mod.rs | 1 + ampd/src/handlers/xrpl_multisig.rs | 439 ++++++++++++++++++ contracts/xrpl-multisig-prover/src/query.rs | 6 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 28 +- integration-tests/tests/test_utils/mod.rs | 2 +- 7 files changed, 455 insertions(+), 23 deletions(-) create mode 100644 ampd/src/handlers/xrpl_multisig.rs diff --git a/Cargo.lock b/Cargo.lock index 4066cabe6..cf3a79ef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,6 +209,7 @@ dependencies = [ "valuable", "valuable-serde", "voting-verifier", + "xrpl-multisig-prover", ] [[package]] diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index de814c8a9..fe1766b77 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -63,6 +63,7 @@ url = "2.3.1" valuable = { version = "0.1.0", features = ["derive"] } valuable-serde = { version = "0.1.0", features = ["std"] } voting-verifier = { workspace = true } +xrpl-multisig-prover = { workspace = true } [dev-dependencies] elliptic-curve = "0.13.5" diff --git a/ampd/src/handlers/mod.rs b/ampd/src/handlers/mod.rs index e87db6573..ddbe3c403 100644 --- a/ampd/src/handlers/mod.rs +++ b/ampd/src/handlers/mod.rs @@ -5,6 +5,7 @@ mod errors; pub mod evm_verify_msg; pub mod evm_verify_worker_set; pub mod multisig; +pub mod xrpl_multisig; pub mod sui_verify_msg; pub mod sui_verify_worker_set; diff --git a/ampd/src/handlers/xrpl_multisig.rs b/ampd/src/handlers/xrpl_multisig.rs new file mode 100644 index 000000000..731481a29 --- /dev/null +++ b/ampd/src/handlers/xrpl_multisig.rs @@ -0,0 +1,439 @@ +use std::collections::HashMap; +use std::convert::TryInto; + +use async_trait::async_trait; +use cosmrs::cosmwasm::MsgExecuteContract; +use cosmwasm_std::{HexBinary, Uint64}; +use ecdsa::VerifyingKey; +use error_stack::ResultExt; +use hex::encode; +use serde::de::Error as DeserializeError; +use serde::{Deserialize, Deserializer}; +use tokio::sync::watch::Receiver; +use tracing::info; + +use events::Error::EventTypeMismatch; +use events_derive; +use events_derive::try_from; +use multisig::msg::ExecuteMsg; + +use crate::event_processor::EventHandler; +use crate::handlers::errors::Error::{self, DeserializeEvent}; +use crate::queue::queued_broadcaster::BroadcasterClient; +use crate::tofnd::grpc::SharableEcdsaClient; +use crate::tofnd::MessageDigest; +use crate::types::PublicKey; +use crate::types::TMAddress; +use xrpl_multisig_prover::xrpl_multisig::{public_key_to_xrpl_address, decode_address, xrpl_hash}; + +#[derive(Debug, Deserialize)] +#[try_from("wasm-signing_started")] +struct XRPLSigningStartedEvent { + #[serde(rename = "_contract_address")] + multisig_address: TMAddress, + session_id: u64, + #[serde(deserialize_with = "deserialize_public_keys")] + pub_keys: HashMap, + unsigned_tx: HexBinary, + expires_at: u64, +} + +fn deserialize_public_keys<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let keys_by_address: HashMap = + HashMap::deserialize(deserializer)?; + + keys_by_address + .into_iter() + .map(|(address, pk)| match pk { + multisig::key::PublicKey::Ecdsa(hex) => Ok(( + address, + VerifyingKey::from_sec1_bytes(hex.as_ref()) + .map_err(D::Error::custom)? + .into(), + )), + + multisig::key::PublicKey::Ed25519(hex) => { + let pk: cosmrs::tendermint::PublicKey = + cosmrs::tendermint::crypto::ed25519::VerificationKey::try_from(hex.as_ref()) + .map_err(D::Error::custom)? + .into(); + Ok((address, pk.into())) + } + }) + .collect() +} + +pub struct Handler +where + B: BroadcasterClient, +{ + worker: TMAddress, + multisig: TMAddress, + broadcaster: B, + signer: SharableEcdsaClient, + latest_block_height: Receiver, +} + +impl Handler +where + B: BroadcasterClient, +{ + pub fn new( + worker: TMAddress, + multisig: TMAddress, + broadcaster: B, + signer: SharableEcdsaClient, + latest_block_height: Receiver, + ) -> Self { + Self { + worker, + multisig, + broadcaster, + signer, + latest_block_height, + } + } + + async fn broadcast_signature( + &self, + session_id: impl Into, + signature: impl Into, + ) -> error_stack::Result<(), Error> { + let msg = serde_json::to_vec(&ExecuteMsg::SubmitSignature { + session_id: session_id.into(), + signature: signature.into(), + }) + .expect("submit signature msg should serialize"); + + let tx = MsgExecuteContract { + sender: self.worker.as_ref().clone(), + contract: self.multisig.as_ref().clone(), + msg, + funds: vec![], + }; + + self.broadcaster + .broadcast(tx) + .await + .change_context(Error::Broadcaster) + } +} + +#[async_trait] +impl EventHandler for Handler +where + B: BroadcasterClient + Send + Sync, +{ + type Err = Error; + + async fn handle(&self, event: &events::Event) -> error_stack::Result<(), Error> { + let XRPLSigningStartedEvent { + multisig_address, + session_id, + pub_keys, + unsigned_tx, + expires_at, + } = match event.try_into() as error_stack::Result<_, _> { + Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { + return Ok(()); + } + result => result.change_context(DeserializeEvent)?, + }; + + if self.multisig != multisig_address { + return Ok(()); + } + + info!( + session_id = session_id, + msg = encode(&unsigned_tx), + "get xrpl signing request", + ); + + let latest_block_height = *self.latest_block_height.borrow(); + if latest_block_height >= expires_at { + info!( + session_id = session_id.to_string(), + "skipping expired signing session" + ); + return Ok(()); + } + + match pub_keys.get(&self.worker) { + Some(pub_key) => { + let pub_key_hex = HexBinary::from_hex(hex::encode_upper(pub_key.to_bytes()).as_str()).unwrap(); + let multisig_pub_key = multisig::key::PublicKey::try_from((multisig::key::KeyType::Ecdsa, pub_key_hex)).unwrap(); + let xrpl_address = public_key_to_xrpl_address(&multisig_pub_key); + + let serialized_signer_xrpl_address = decode_address(&xrpl_address).unwrap(); + let msg = &[unsigned_tx.to_vec(), serialized_signer_xrpl_address.into()].concat(); + let msg_digest = MessageDigest::from(xrpl_hash(msg)); + + let signature = self + .signer + .sign(self.multisig.to_string().as_str(), msg_digest, pub_key) + .await + .change_context(Error::Sign)?; + + info!(signature = encode(&signature), "ready to submit signature"); + + self.broadcast_signature(session_id, signature).await?; + + Ok(()) + } + None => { + info!("worker is not a participant"); + Ok(()) + } + } + } +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + use std::convert::{TryFrom, TryInto}; + use std::time::Duration; + + use base64::engine::general_purpose::STANDARD; + use base64::Engine; + use connection_router::state::ChainName; + use cosmos_sdk_proto::cosmos::base::abci::v1beta1::TxResponse; + use cosmrs::{AccountId, Gas}; + use cosmwasm_std::{HexBinary, Uint64}; + use ecdsa::SigningKey; + use error_stack::{Report, Result}; + use rand::distributions::Alphanumeric; + use rand::rngs::OsRng; + use rand::Rng; + use tendermint::abci; + use tokio::sync::watch; + + use multisig::events::Event::SigningStarted; + use multisig::key::PublicKey; + use multisig::types::MsgToSign; + + use crate::broadcaster::MockBroadcaster; + use crate::queue::queued_broadcaster::{QueuedBroadcaster, QueuedBroadcasterClient}; + use crate::tofnd; + use crate::tofnd::grpc::{MockEcdsaClient, SharableEcdsaClient}; + use crate::types; + + use super::*; + + const MULTISIG_ADDRESS: &str = "axelarvaloper1zh9wrak6ke4n6fclj5e8yk397czv430ygs5jz7"; + + fn rand_account() -> TMAddress { + types::PublicKey::from(SigningKey::random(&mut OsRng).verifying_key()) + .account_id("axelar") + .unwrap() + .into() + } + + fn rand_public_key() -> multisig::key::PublicKey { + multisig::key::PublicKey::Ecdsa(HexBinary::from( + types::PublicKey::from(SigningKey::random(&mut OsRng).verifying_key()).to_bytes(), + )) + } + + fn rand_message() -> HexBinary { + let digest: [u8; 32] = rand::random(); + HexBinary::from(digest.as_slice()) + } + + fn rand_chain_name() -> ChainName { + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect::() + .try_into() + .unwrap() + } + + fn signing_started_event() -> events::Event { + let pub_keys = (0..10) + .map(|_| (rand_account().to_string(), rand_public_key())) + .collect::>(); + + let poll_started = SigningStarted { + session_id: Uint64::one(), + worker_set_id: "worker_set_id".to_string(), + pub_keys, + msg: MsgToSign::unchecked(rand_message()), + chain_name: rand_chain_name(), + expires_at: 100u64, + }; + + let mut event: cosmwasm_std::Event = poll_started.into(); + event.ty = format!("wasm-{}", event.ty); + event = event.add_attribute("_contract_address", MULTISIG_ADDRESS); + + events::Event::try_from(abci::Event::new( + event.ty, + event + .attributes + .into_iter() + .map(|cosmwasm_std::Attribute { key, value }| { + (STANDARD.encode(key), STANDARD.encode(value)) + }), + )) + .unwrap() + } + + fn get_handler( + worker: TMAddress, + multisig: TMAddress, + signer: SharableEcdsaClient, + latest_block_height: u64, + ) -> Handler { + let mut broadcaster = MockBroadcaster::new(); + broadcaster + .expect_broadcast() + .returning(|_| Ok(TxResponse::default())); + + let (broadcaster, _) = + QueuedBroadcaster::new(broadcaster, Gas::default(), 100, Duration::from_secs(5)); + + let (tx, rx) = watch::channel(latest_block_height); + + Handler::new(worker, multisig, broadcaster.client(), signer, rx) + } + + #[test] + fn should_not_deserialize_incorrect_event_type() { + // incorrect event type + let mut event: events::Event = signing_started_event(); + match event { + events::Event::Abci { + ref mut event_type, .. + } => { + *event_type = "incorrect".into(); + } + _ => panic!("incorrect event type"), + } + let event: Result = (&event).try_into(); + + assert!(matches!( + event.unwrap_err().current_context(), + events::Error::EventTypeMismatch(_) + )); + } + + #[test] + fn should_not_deserialize_invalid_pub_key() { + let mut event = signing_started_event(); + + let invalid_pub_key: [u8; 32] = rand::random(); + let mut map: HashMap = HashMap::new(); + map.insert( + rand_account().to_string(), + PublicKey::Ecdsa(HexBinary::from(invalid_pub_key.as_slice())), + ); + match event { + events::Event::Abci { + ref mut attributes, .. + } => { + attributes.insert("pub_keys".into(), serde_json::to_value(map).unwrap()); + } + _ => panic!("incorrect event type"), + } + + let event: Result = (&event).try_into(); + + assert!(matches!( + event.unwrap_err().current_context(), + events::Error::DeserializationFailed(_, _) + )); + } + + #[test] + fn should_deserialize_event() { + let event: Result = + (&signing_started_event()).try_into(); + + assert!(event.is_ok()); + } + + #[tokio::test] + async fn should_not_handle_event_if_multisig_address_does_not_match() { + let mut client = MockEcdsaClient::new(); + client + .expect_sign() + .returning(move |_, _, _| Err(Report::from(tofnd::error::Error::SignFailed))); + + let handler = get_handler( + rand_account(), + rand_account(), + SharableEcdsaClient::new(client), + 100u64, + ); + + assert!(handler.handle(&signing_started_event()).await.is_ok()); + } + + #[tokio::test] + async fn should_not_handle_event_if_worker_is_not_a_participant() { + let mut client = MockEcdsaClient::new(); + client + .expect_sign() + .returning(move |_, _, _| Err(Report::from(tofnd::error::Error::SignFailed))); + + let handler = get_handler( + rand_account(), + TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), + SharableEcdsaClient::new(client), + 100u64, + ); + + assert!(handler.handle(&signing_started_event()).await.is_ok()); + } + + #[tokio::test] + async fn should_not_handle_event_if_sign_failed() { + let mut client = MockEcdsaClient::new(); + client + .expect_sign() + .returning(move |_, _, _| Err(Report::from(tofnd::error::Error::SignFailed))); + + let event = signing_started_event(); + let signing_started: XRPLSigningStartedEvent = ((&event).try_into() as Result<_, _>).unwrap(); + let worker = signing_started.pub_keys.keys().next().unwrap().clone(); + let handler = get_handler( + worker, + TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), + SharableEcdsaClient::new(client), + 99u64, + ); + + assert!(matches!( + *handler.handle(&event).await.unwrap_err().current_context(), + Error::Sign + )); + } + + #[tokio::test] + async fn should_not_handle_event_if_session_expired() { + let mut client = MockEcdsaClient::new(); + client + .expect_sign() + .returning(move |_, _, _| Err(Report::from(tofnd::error::Error::SignFailed))); + + let event = signing_started_event(); + let signing_started: XRPLSigningStartedEvent = ((&event).try_into() as Result<_, _>).unwrap(); + let worker = signing_started.pub_keys.keys().next().unwrap().clone(); + let handler = get_handler( + worker, + TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), + SharableEcdsaClient::new(client), + 101u64, + ); + + assert!(handler.handle(&signing_started_event()).await.is_ok()); + } +} diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 5a75dbebd..1cf6a313b 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -14,7 +14,7 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul let xrpl_signers: Vec = axelar_signers .iter() .map(|(axelar_signer, signature)| -> Result { - let xrpl_address = xrpl_multisig::public_key_to_xrpl_address(axelar_signer.pub_key.clone()); + let xrpl_address = xrpl_multisig::public_key_to_xrpl_address(&axelar_signer.pub_key); let txn_signature = match signature { // TODO: use unwrapped signature instead of ignoring it multisig::key::Signature::Ecdsa(_) | @@ -53,12 +53,12 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, let serialized_tx = &[serialized_unsigned_tx, serialized_signer_xrpl_address.to_vec()].concat(); Ok(GetMessageToSignResponse { - tx_hash: xrpl_multisig::xrpl_hash(None, serialized_tx).into() + tx_hash: xrpl_multisig::xrpl_hash(serialized_tx).into() }) } pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: PublicKey, signature: Signature) -> StdResult { - let signer_xrpl_address = xrpl_multisig::public_key_to_xrpl_address(public_key.clone()); + let signer_xrpl_address = xrpl_multisig::public_key_to_xrpl_address(&public_key); let m = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; // m.tx_hash is going to be over 32 bytes due to inclusion of the signer address, so it has to be passed unchecked diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index d48e9abd5..e3e7e7ac8 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -385,7 +385,8 @@ pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result) -> Result { - let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(Some(HASH_PREFIX_SIGNED_TRANSACTION), encoded_signed_tx.as_slice())); + let msg = [HASH_PREFIX_SIGNED_TRANSACTION.to_vec(), encoded_signed_tx].concat(); + let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(msg.as_slice())); let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); Ok(tx_hash) } @@ -640,19 +641,8 @@ impl XRPLSerialize for XRPLObject { } } -// TODO: impl XRPLSerialize for all types implementing Into - -// TODO: fix to not take prefix as param -pub fn xrpl_hash( - prefix: Option<[u8; 4]>, - tx_blob: &[u8], -) -> [u8; 32] { - let mut hasher = match prefix { - Some(prefix) => Sha512::new_with_prefix(prefix), - None => Sha512::new(), - }; - hasher.update(tx_blob); - let hash: [u8; 64] = hasher.finalize().into(); +pub fn xrpl_hash(tx_blob: &[u8]) -> [u8; 32] { + let hash: [u8; 64] = Sha512::digest(tx_blob).into(); let mut half_hash: [u8; 32] = [0; 32]; half_hash.copy_from_slice(&hash[..32]); half_hash @@ -755,15 +745,15 @@ fn make_xrpl_signer_entries(signers: BTreeSet) -> Vec String { - let public_key_hex: HexBinary = public_key.into(); +pub fn public_key_to_xrpl_address(public_key: &multisig::key::PublicKey) -> String { + let public_key_hex: HexBinary = public_key.clone().into(); assert!(public_key_hex.len() == 33); @@ -1357,7 +1347,7 @@ mod tests { #[test] fn ed25519_public_key_to_xrpl_address() { assert_eq!( - public_key_to_xrpl_address(PublicKey::Ed25519(HexBinary::from(hex::decode("ED9434799226374926EDA3B54B1B461B4ABF7237962EAE18528FEA67595397FA32").unwrap()))), + public_key_to_xrpl_address(&PublicKey::Ed25519(HexBinary::from(hex::decode("ED9434799226374926EDA3B54B1B461B4ABF7237962EAE18528FEA67595397FA32").unwrap()))), "rDTXLQ7ZKZVKz33zJbHjgVShjsBnqMBhmN" ); } @@ -1365,7 +1355,7 @@ mod tests { #[test] fn secp256k1_public_key_to_xrpl_address() { assert_eq!( - public_key_to_xrpl_address(PublicKey::Ecdsa(HexBinary::from(hex::decode("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D").unwrap()))), + public_key_to_xrpl_address(&PublicKey::Ecdsa(HexBinary::from(hex::decode("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D").unwrap()))), "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H" ); } diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 16305137d..b4d5eca73 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -291,7 +291,7 @@ pub fn sign_xrpl_proof( for worker in workers { let xrpl_signer_address = xrpl_multisig_prover::xrpl_multisig::public_key_to_xrpl_address( - multisig::key::PublicKey::Ecdsa(worker.key_pair.encoded_verifying_key().into()) + &multisig::key::PublicKey::Ecdsa(worker.key_pair.encoded_verifying_key().into()) ); let msg_to_sign = match get_xrpl_message_to_sign( From 2d5740cab2829d10f230aaa06130905ffbbc80d6 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Tue, 13 Feb 2024 12:07:01 +0100 Subject: [PATCH 34/80] multisig: add expires_at attribute to Multisig type --- contracts/multisig-prover/src/test/mocks/multisig.rs | 1 + contracts/multisig/src/contract/query.rs | 1 + contracts/multisig/src/msg.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/contracts/multisig-prover/src/test/mocks/multisig.rs b/contracts/multisig-prover/src/test/mocks/multisig.rs index 8a8c357f0..ff1a89f35 100644 --- a/contracts/multisig-prover/src/test/mocks/multisig.rs +++ b/contracts/multisig-prover/src/test/mocks/multisig.rs @@ -122,6 +122,7 @@ mod query { state: MultisigState::Completed { completed_at: 12345, }, + expires_at: 23456, quorum, signers, } diff --git a/contracts/multisig/src/contract/query.rs b/contracts/multisig/src/contract/query.rs index 7e5d88a27..33c66d7cf 100644 --- a/contracts/multisig/src/contract/query.rs +++ b/contracts/multisig/src/contract/query.rs @@ -20,6 +20,7 @@ pub fn get_multisig(deps: Deps, session_id: Uint64) -> StdResult { Ok(Multisig { state: session.state, + expires_at: session.expires_at, quorum: worker_set.threshold, signers: signers_with_sigs, }) diff --git a/contracts/multisig/src/msg.rs b/contracts/multisig/src/msg.rs index 149b6de07..ba4062465 100644 --- a/contracts/multisig/src/msg.rs +++ b/contracts/multisig/src/msg.rs @@ -80,6 +80,7 @@ pub struct Signer { #[cw_serde] pub struct Multisig { pub state: MultisigState, + pub expires_at: u64, pub quorum: Uint256, pub signers: Vec<(Signer, Option)>, } From a9146d3e064631376d51b990452c6f55e763123a Mon Sep 17 00:00:00 2001 From: k4m4 Date: Tue, 13 Feb 2024 12:11:23 +0100 Subject: [PATCH 35/80] xrpl-multisig-prover: allow one multisig session per payment --- .../xrpl-multisig-prover/src/contract.rs | 28 +++++++++++++------ contracts/xrpl-multisig-prover/src/error.rs | 6 ++++ contracts/xrpl-multisig-prover/src/reply.rs | 15 ++++++++-- contracts/xrpl-multisig-prover/src/state.rs | 2 ++ 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 4a433c61d..557f89095 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -8,14 +8,10 @@ use cosmwasm_std::{ entry_point, Storage, wasm_execute, SubMsg, Reply, DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_binary, Deps, StdResult, Binary, Addr, HexBinary, }; +use multisig::types::MultisigState; use crate::{ - error::ContractError, - state::{Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, MULTISIG_SESSION_TX, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO}, - msg::{ExecuteMsg, QueryMsg}, - reply, - types::*, - xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount, XRPLSerialize}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query, + axelar_workers, error::ContractError, msg::{ExecuteMsg, QueryMsg}, querier::{Querier, XRPL_CHAIN_NAME}, query, reply, state::{Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, REPLY_MESSAGE_ID, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO}, types::*, xrpl_multisig::{self, XRPLPaymentAmount, XRPLSerialize, XRPLTokenAmount} }; pub const START_MULTISIG_REPLY_ID: u64 = 1; @@ -114,7 +110,7 @@ pub fn execute( register_token(deps.storage, denom, &token) }, ExecuteMsg::ConstructProof { message_id } => { - construct_payment_proof(deps.storage, querier, info, env.contract.address, &config, message_id) + construct_payment_proof(deps.storage, querier, info, env.contract.address, env.block.height, &config, message_id) }, ExecuteMsg::UpdateWorkerSet {} => { construct_signer_list_set_proof(deps.storage, querier, env, &config) @@ -135,6 +131,7 @@ fn construct_payment_proof( querier: Querier, info: MessageInfo, self_address: Addr, + block_height: u64, config: &Config, message_id: CrossChainId, ) -> Result { @@ -142,6 +139,20 @@ fn construct_payment_proof( return Err(ContractError::InvalidPaymentAmount); } + match MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, message_id.clone())? { + Some(multisig_session_id) => { + let multisig_session = querier.get_multisig_session(Uint64::from(multisig_session_id))?; + if let MultisigState::Completed { .. } = multisig_session.state { + return Err(ContractError::PaymentAlreadySigned); + } + + if multisig_session.expires_at <= block_height { + return Err(ContractError::PaymentAlreadyHasActiveSigningSession); + } + }, + None => (), + }; + let mut funds = info.funds; let coin = funds.remove(0); let xrpl_token = TOKENS.load(storage, coin.denom.clone())?; @@ -164,9 +175,10 @@ fn construct_payment_proof( config, message.destination_address.to_string().try_into()?, xrpl_payment_amount, - message_id, + message_id.clone(), )?; + REPLY_MESSAGE_ID.save(storage, &message_id)?; Ok( start_signing_session( storage, diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 5bb9c2c86..7fb515bbe 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -98,6 +98,12 @@ pub enum ContractError { #[error("invalid message destination address")] InvalidMessageDestinationAddress, + + #[error("payment already has active signing session")] + PaymentAlreadyHasActiveSigningSession, + + #[error("payment already signed")] + PaymentAlreadySigned, } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index e1a6844f5..610f07c91 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -2,9 +2,7 @@ use cosmwasm_std::{from_binary, DepsMut, Reply, Response, Uint64}; use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; use crate::{ - error::ContractError, - events::Event, - state::{MULTISIG_SESSION_TX, REPLY_TX_HASH}, + error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, REPLY_MESSAGE_ID, REPLY_TX_HASH} }; pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { @@ -23,6 +21,17 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { + MESSAGE_ID_TO_MULTISIG_SESSION_ID.save( + deps.storage, + message_id, + &multisig_session_id.u64(), + )? + }, + None => (), + } + Ok(Response::new().add_event( Event::ProofUnderConstruction { tx_hash, diff --git a/contracts/xrpl-multisig-prover/src/state.rs b/contracts/xrpl-multisig-prover/src/state.rs index f824f69b0..30546f812 100644 --- a/contracts/xrpl-multisig-prover/src/state.rs +++ b/contracts/xrpl-multisig-prover/src/state.rs @@ -22,6 +22,7 @@ pub struct Config { pub const CONFIG: Item = Item::new("config"); pub const REPLY_TX_HASH: Item = Item::new("reply_tx_hash"); +pub const REPLY_MESSAGE_ID: Item = Item::new("reply_message_id"); pub const MULTISIG_SESSION_TX: Map = Map::new("multisig_session_tx"); // The next seq. no. is determined on TicketCreate and depends on the number of created tickets, @@ -34,6 +35,7 @@ pub const NEXT_SEQUENCE_NUMBER: Item = Item::new("next_sequence_number"); pub const LAST_ASSIGNED_TICKET_NUMBER: Item = Item::new("last_assigned_ticket_number"); pub const MESSAGE_ID_TO_TICKET: Map = Map::new("message_id_to_ticket"); +pub const MESSAGE_ID_TO_MULTISIG_SESSION_ID: Map = Map::new("message_id_to_multisig_session_id"); pub const CONFIRMED_TRANSACTIONS: Map = Map::new("confirmed_transactions"); pub const AVAILABLE_TICKETS: Item> = Item::new("available_tickets"); pub const TRANSACTION_INFO: Map = Map::new("transaction_info"); From 9862500745072b9b6e848f37787f39d85c74a4b1 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Tue, 13 Feb 2024 12:12:12 +0100 Subject: [PATCH 36/80] xrpl-multisig-prover: remove redundant param from make_xrpl_signed_tx --- contracts/xrpl-multisig-prover/src/contract.rs | 2 +- contracts/xrpl-multisig-prover/src/query.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 557f89095..861c0a80c 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -309,7 +309,7 @@ fn update_tx_status( return Err(ContractError::SignatureNotFound); } - let signed_tx = query::make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers, multisig_session_id)?; // TODO: RELOCATE FUNCTION + let signed_tx = query::make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; // TODO: RELOCATE FUNCTION let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?).into(); diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 1cf6a313b..47c69fd74 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -10,7 +10,7 @@ use crate::{ state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, msg::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, }; -pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>, multisig_session_id: &Uint64) -> Result { +pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { let xrpl_signers: Vec = axelar_signers .iter() .map(|(axelar_signer, signature)| -> Result { @@ -82,7 +82,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & .map(|(signer, signature)| (signer.clone(), signature.clone().unwrap())) .collect(); - let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers, multisig_session_id)?; + let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; let tx_blob: HexBinary = HexBinary::from(signed_tx.xrpl_serialize()?); GetProofResponse::Completed { unsigned_tx_hash, tx_blob } } From 26498e03b5d88ae7a240a0a941ce1ae04f43681e Mon Sep 17 00:00:00 2001 From: k4m4 Date: Tue, 13 Feb 2024 12:13:07 +0100 Subject: [PATCH 37/80] xrpl-multisig-prover: remove redundant imports --- contracts/xrpl-multisig-prover/src/error.rs | 1 - contracts/xrpl-multisig-prover/src/querier.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 7fb515bbe..31dad3d97 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -1,7 +1,6 @@ use axelar_wasm_std::nonempty; use axelar_wasm_std_derive::IntoContractError; use cosmwasm_std::StdError; -use gateway::error; use thiserror::Error; #[derive(Error, Debug, PartialEq, IntoContractError)] diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index 1545267c1..b2aea55e4 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ to_binary, QueryRequest, WasmQuery, QuerierWrapper, Uint64, }; use multisig::{key::PublicKey, msg::Multisig}; -use axelar_wasm_std::{operators::Operators, VerificationStatus}; +use axelar_wasm_std::VerificationStatus; use crate::{ error::ContractError, From 46a9b9855a1a284ad4b799f5a3701358b484fd63 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 13 Feb 2024 16:24:32 +0200 Subject: [PATCH 38/80] xrpl multisig prover signing started event --- ampd/src/handlers/config.rs | 3 ++ ampd/src/handlers/xrpl_multisig.rs | 6 ++-- ampd/src/lib.rs | 12 ++++++++ .../xrpl-multisig-prover/src/contract.rs | 15 ++++++++-- contracts/xrpl-multisig-prover/src/events.rs | 26 +++++++++++++++++ contracts/xrpl-multisig-prover/src/msg.rs | 6 +++- contracts/xrpl-multisig-prover/src/query.rs | 8 ++++-- contracts/xrpl-multisig-prover/src/reply.rs | 28 ++++++++++++++++--- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 26 ++++++++--------- 9 files changed, 104 insertions(+), 26 deletions(-) diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index d50f31225..4f2c64d0e 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -34,6 +34,9 @@ pub enum Config { MultisigSigner { cosmwasm_contract: TMAddress, }, + XRPLMultisigSigner { + cosmwasm_contract: TMAddress, + }, SuiMsgVerifier { cosmwasm_contract: TMAddress, rpc_url: Url, diff --git a/ampd/src/handlers/xrpl_multisig.rs b/ampd/src/handlers/xrpl_multisig.rs index 731481a29..3c27f90e7 100644 --- a/ampd/src/handlers/xrpl_multisig.rs +++ b/ampd/src/handlers/xrpl_multisig.rs @@ -24,7 +24,7 @@ use crate::tofnd::grpc::SharableEcdsaClient; use crate::tofnd::MessageDigest; use crate::types::PublicKey; use crate::types::TMAddress; -use xrpl_multisig_prover::xrpl_multisig::{public_key_to_xrpl_address, decode_address, xrpl_hash}; +use xrpl_multisig_prover::xrpl_multisig::{public_key_to_xrpl_address, decode_address, xrpl_hash, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}; #[derive(Debug, Deserialize)] #[try_from("wasm-signing_started")] @@ -172,7 +172,7 @@ where let serialized_signer_xrpl_address = decode_address(&xrpl_address).unwrap(); let msg = &[unsigned_tx.to_vec(), serialized_signer_xrpl_address.into()].concat(); - let msg_digest = MessageDigest::from(xrpl_hash(msg)); + let msg_digest = MessageDigest::from(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)); let signature = self .signer @@ -300,7 +300,7 @@ mod test { let (broadcaster, _) = QueuedBroadcaster::new(broadcaster, Gas::default(), 100, Duration::from_secs(5)); - let (tx, rx) = watch::channel(latest_block_height); + let (_tx, rx) = watch::channel(latest_block_height); Handler::new(worker, multisig, broadcaster.client(), signer, rx) } diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index d11632d57..c8e470c9e 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -253,6 +253,18 @@ where ), stream_timeout, ), + handlers::config::Config::XRPLMultisigSigner { cosmwasm_contract } => self + .create_handler_task( + "xrpl-multisig-signer", + handlers::xrpl_multisig::Handler::new( + worker.clone(), + cosmwasm_contract, + self.broadcaster.client(), + self.ecdsa_client.clone(), + self.block_height_monitor.latest_block_height(), + ), + stream_timeout, + ), handlers::config::Config::SuiMsgVerifier { cosmwasm_contract, rpc_url, diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 861c0a80c..8f4e7ad27 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -8,10 +8,16 @@ use cosmwasm_std::{ entry_point, Storage, wasm_execute, SubMsg, Reply, DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_binary, Deps, StdResult, Binary, Addr, HexBinary, }; + use multisig::types::MultisigState; use crate::{ - axelar_workers, error::ContractError, msg::{ExecuteMsg, QueryMsg}, querier::{Querier, XRPL_CHAIN_NAME}, query, reply, state::{Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, REPLY_MESSAGE_ID, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO}, types::*, xrpl_multisig::{self, XRPLPaymentAmount, XRPLSerialize, XRPLTokenAmount} + error::ContractError, + state::{Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, MULTISIG_SESSION_TX, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO, REPLY_MESSAGE_ID, MESSAGE_ID_TO_MULTISIG_SESSION_ID}, + msg::{ExecuteMsg, QueryMsg}, + reply, + types::*, + xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount, XRPLSerialize}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query, }; pub const START_MULTISIG_REPLY_ID: u64 = 1; @@ -352,7 +358,12 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetMessageToSign { multisig_session_id, signer_xrpl_address, - } => to_binary(&query::get_message_to_sign(deps.storage, &multisig_session_id, &signer_xrpl_address)?), + } => to_binary(&query::get_message_to_sign(deps.storage, &multisig_session_id, &signer_xrpl_address)?), + QueryMsg::VerifyMessage { + multisig_session_id, + public_key, + signature + } => to_binary(&query::verify_message(deps.storage, &multisig_session_id, &public_key, &signature)?), QueryMsg::GetWorkerSet {} => to_binary(&query::get_worker_set(deps.storage)?), } } diff --git a/contracts/xrpl-multisig-prover/src/events.rs b/contracts/xrpl-multisig-prover/src/events.rs index 94fc2262a..99e598581 100644 --- a/contracts/xrpl-multisig-prover/src/events.rs +++ b/contracts/xrpl-multisig-prover/src/events.rs @@ -2,9 +2,12 @@ use std::collections::HashMap; use axelar_wasm_std::Snapshot; use cosmwasm_std::{HexBinary, Uint64}; +use multisig::key::PublicKey; use serde_json::to_string; + use crate::types::TxHash; + pub enum Event { ProofUnderConstruction { tx_hash: TxHash, @@ -15,11 +18,34 @@ pub enum Event { snapshot: Snapshot, pub_keys: HashMap, }, + XRPLSigningStarted { + session_id: Uint64, + worker_set_id: String, + pub_keys: HashMap, + unsigned_tx: HexBinary, + expires_at: u64, + }, } impl From for cosmwasm_std::Event { fn from(other: Event) -> Self { match other { + Event::XRPLSigningStarted { + session_id, + worker_set_id, + pub_keys, + unsigned_tx, + expires_at, + } => cosmwasm_std::Event::new("signing_started") + .add_attribute("session_id", session_id) + .add_attribute("worker_set_id", worker_set_id) + .add_attribute( + "pub_keys", + to_string(&pub_keys) + .expect("violated invariant: pub_keys are not serializable"), + ) + .add_attribute("unsigned_tx", unsigned_tx.to_hex()) + .add_attribute("expires_at", expires_at.to_string()), Event::ProofUnderConstruction { tx_hash, multisig_session_id, diff --git a/contracts/xrpl-multisig-prover/src/msg.rs b/contracts/xrpl-multisig-prover/src/msg.rs index d7871e718..a47e08147 100644 --- a/contracts/xrpl-multisig-prover/src/msg.rs +++ b/contracts/xrpl-multisig-prover/src/msg.rs @@ -1,7 +1,8 @@ -use connection_router::{state::CrossChainId, Message}; +use connection_router::state::CrossChainId; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{HexBinary, Uint64, Addr}; use axelar_wasm_std::VerificationStatus; +use multisig::key::{PublicKey, Signature}; use crate::types::{TxHash, XRPLToken}; @@ -14,6 +15,9 @@ pub enum QueryMsg { #[returns(GetMessageToSignResponse)] GetMessageToSign { multisig_session_id: Uint64, signer_xrpl_address: String }, + #[returns(bool)] + VerifyMessage { multisig_session_id: Uint64, public_key: PublicKey, signature: Signature }, + #[returns(multisig::worker_set::WorkerSet)] GetWorkerSet, } diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 47c69fd74..0e9cabb71 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -6,8 +6,10 @@ use multisig::key::PublicKey; // TODO: remove dependency? use k256::{ecdsa, schnorr::signature::SignatureEncoding}; +use crate::msg::GetMessageToSignResponse; +use crate::types::TxHash; use crate::{ - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize}, querier::Querier, msg::{GetProofResponse, GetMessageToSignResponse}, types::TransactionStatus, error::ContractError, + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, }; pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { @@ -53,11 +55,11 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, let serialized_tx = &[serialized_unsigned_tx, serialized_signer_xrpl_address.to_vec()].concat(); Ok(GetMessageToSignResponse { - tx_hash: xrpl_multisig::xrpl_hash(serialized_tx).into() + tx_hash: xrpl_multisig::xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, serialized_tx).into() }) } -pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: PublicKey, signature: Signature) -> StdResult { +pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: &PublicKey, signature: &Signature) -> StdResult { let signer_xrpl_address = xrpl_multisig::public_key_to_xrpl_address(&public_key); let m = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 610f07c91..5f29f1a7b 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -1,12 +1,16 @@ -use cosmwasm_std::{from_binary, DepsMut, Reply, Response, Uint64}; +use std::collections::HashMap; + +use cosmwasm_std::{from_binary, Attribute, DepsMut, HexBinary, Reply, Response, SubMsgResult, Uint64}; use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; +use serde_json::from_str; +use std::str::FromStr; use crate::{ - error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, REPLY_MESSAGE_ID, REPLY_TX_HASH} + error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, xrpl_multisig::XRPLSerialize }; pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { - match parse_reply_execute_data(reply) { + match parse_reply_execute_data(reply.clone()) { Ok(MsgExecuteContractResponse { data: Some(data) }) => { let tx_hash = REPLY_TX_HASH.load(deps.storage)?; @@ -32,13 +36,29 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result (), } + let tx_info = TRANSACTION_INFO.load(deps.storage, tx_hash.clone())?; + + let res = reply.result.unwrap(); + + let evt_attributes: Vec = res.events + .into_iter() + .filter(|e| e.ty == "signing_started") + .map(|e| e.attributes) + .flatten() + .filter(|a| a.key != "msg") + .collect(); + + let evt = cosmwasm_std::Event::new("xrpl_signing_started") + .add_attributes(evt_attributes) + .add_attribute("unsigned_tx", HexBinary::from(tx_info.unsigned_contents.xrpl_serialize()?).to_hex()); + Ok(Response::new().add_event( Event::ProofUnderConstruction { tx_hash, multisig_session_id, } .into(), - )) + ).add_event(evt)) } Ok(MsgExecuteContractResponse { data: None }) => Err(ContractError::InvalidContractReply { reason: "no data".to_string(), diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index e3e7e7ac8..00eca7968 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -374,6 +374,7 @@ pub fn decode_address(address: &String) -> Result<[u8; 20], ContractError> { } pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; +pub const HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result { let encoded_unsigned_tx = serde_json::to_vec(unsigned_tx).map_err(|_| ContractError::FailedToSerialize)?; @@ -385,8 +386,7 @@ pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result) -> Result { - let msg = [HASH_PREFIX_SIGNED_TRANSACTION.to_vec(), encoded_signed_tx].concat(); - let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(msg.as_slice())); + let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())); let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); Ok(tx_hash) } @@ -458,18 +458,13 @@ impl XRPLSerialize for XRPLSignedTransaction { }); let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; obj.add_field(3, &XRPLArray{ field_code: 16, items: sorted_signers })?; - obj.xrpl_serialize() } } impl XRPLSerialize for XRPLUnsignedTx { fn xrpl_serialize(self: &XRPLUnsignedTx) -> Result, ContractError> { - let obj = XRPLObject::try_from(self)?; - - let mut result = Vec::from((0x534D5400 as u32).to_be_bytes()); // prefix for multisignature signing - result.extend(obj.xrpl_serialize()?); - Ok(result) + XRPLObject::try_from(self)?.xrpl_serialize() } } @@ -641,8 +636,13 @@ impl XRPLSerialize for XRPLObject { } } -pub fn xrpl_hash(tx_blob: &[u8]) -> [u8; 32] { - let hash: [u8; 64] = Sha512::digest(tx_blob).into(); +pub fn xrpl_hash( + prefix: [u8; 4], + tx_blob: &[u8], +) -> [u8; 32] { + let mut hasher = Sha512::new_with_prefix(prefix); + hasher.update(tx_blob); + let hash: [u8; 64] = hasher.finalize().into(); let mut half_hash: [u8; 32] = [0; 32]; half_hash.copy_from_slice(&hash[..32]); half_hash @@ -1149,7 +1149,7 @@ mod tests { }; let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize().unwrap(); assert_eq!( - "534D54001200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", + "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", hex::encode_upper(encoded_unsigned_tx) ); } @@ -1166,7 +1166,7 @@ mod tests { }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); assert_eq!( - "534D5400120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", + "120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", hex::encode_upper(encoded_unsigned_tx) ); @@ -1180,7 +1180,7 @@ mod tests { }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); assert_eq!( - "534D54001200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6", + "1200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6", hex::encode_upper(encoded_unsigned_tx) ); } From 8681da3797717301a0a7376387e70bd5052db9ed Mon Sep 17 00:00:00 2001 From: k4m4 Date: Tue, 13 Feb 2024 15:57:42 +0100 Subject: [PATCH 39/80] xrpl-multisig-prover: fix incorrect integration test xrpl multisig address --- integration-tests/tests/message_routing.rs | 8 ++++---- integration-tests/tests/test_utils/mod.rs | 8 ++++---- integration-tests/tests/update_worker_set.rs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index 5b25c05f4..dbedd5271 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -141,14 +141,14 @@ fn xrpl_ticket_create_can_be_proven() { )); println!("TicketCreate proof: {:?}", proof); - let xrpl_multisig_address = "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(); // TODO: fix duplicate definition + let xrpl_multisig_address = "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(); // TODO: fix duplicate definition let proof_msgs = vec![Message { destination_chain: xrpl.chain_name.clone(), source_address: Address::try_from(xrpl_multisig_address.clone()).unwrap(), destination_address: Address::try_from(xrpl_multisig_address).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "3da9f142fdc0d570d040dde73de20a0625a549fc70cc564849dbc746b5c6469a:0" + id: "9c2f220fe5ee650b3cd10b0a72af1206b3912afce8376214234354180198c5d5:0" .to_string() .try_into() .unwrap(), @@ -262,14 +262,14 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { xrpl_multisig_prover::msg::GetProofResponse::Completed { .. } )); - let xrpl_multisig_address = "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(); // TODO: fix duplicate definition + let xrpl_multisig_address = "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(); // TODO: fix duplicate definition let proof_msgs = vec![Message { destination_chain: xrpl.chain_name.clone(), source_address: Address::try_from(xrpl_multisig_address).unwrap(), destination_address: Address::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".to_string()).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "8108b96bdbc5ff44fd868d973bdcaae3f38e2fe3906a33a8dc1d881b20498ff7:0" + id: "3012df8615c9f1d8ac3bba050bf4fcbf3981156962108ca8751e12146ec4b584:0" .to_string() .try_into() .unwrap(), diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index b4d5eca73..2f452a22f 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1043,7 +1043,7 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { verifier_address: voting_verifier_address.to_string(), }, ); - let xrpl_multisig_address = "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(); // TODO: fix duplicate definition + let xrpl_multisig_address = "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(); // TODO: fix duplicate definition let multisig_prover_address = instantiate_xrpl_multisig_prover( &mut protocol.app, xrpl_multisig_prover::contract::InstantiateMsg { @@ -1057,11 +1057,11 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { xrpl_fee: 30, xrpl_multisig_address: xrpl_multisig_address.clone(), ticket_count_threshold: 1, - next_sequence_number: 45205906, - last_assigned_ticket_number: 45205895, + next_sequence_number: 44218446, + last_assigned_ticket_number: 44218195, available_tickets: vec![ vec![], - (45205896..45205907).collect::>() + (44218195..44218200).collect::>() ].concat(), }, ); diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index 94cff530c..a97e71e51 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -191,14 +191,14 @@ fn xrpl_worker_set_can_be_initialized_and_then_manually_updated() { )); println!("SignerListSet proof: {:?}", proof); - let xrpl_multisig_address = "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(); // TODO: fix duplicate definition + let xrpl_multisig_address = "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(); // TODO: fix duplicate definition let proof_msgs = vec![Message { destination_chain: xrpl.chain_name.clone(), source_address: Address::try_from(xrpl_multisig_address.clone()).unwrap(), destination_address: Address::try_from(xrpl_multisig_address).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "cc9b3b3c7561a14449aa4baec9d1e72ea78068a74c5bf77d0c70629d53a46bd5:0" + id: "fbf428da41656ca3aef36287bfcb6d8491daa76f20c201c4a60172450ab517f9:0" .to_string() .try_into() .unwrap(), From bf1cbf15968cb9ab10b35921072034591220e738 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Tue, 13 Feb 2024 15:58:14 +0100 Subject: [PATCH 40/80] xrpl-multisig-prover: remove redundant prints from integration tests --- integration-tests/tests/test_utils/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 2f452a22f..51b6af3a9 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -55,7 +55,6 @@ pub fn verify_messages( &gateway::msg::ExecuteMsg::VerifyMessages(msgs.to_vec()), &[], ); - println!("Response: {:?}", response); assert!(response.is_ok()); let response = response.unwrap(); @@ -433,7 +432,6 @@ pub fn xrpl_update_tx_status( }, &[], ); - println!("response: {:?}", response); assert!(response.is_ok()); } From b0c5bd0eefe48b24819e70001b980883d139fc8d Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 13 Feb 2024 21:04:57 +0200 Subject: [PATCH 41/80] xrpl integration tests use unsigned_tx instead of session id for signing, like ampd --- ampd/src/handlers/errors.rs | 4 +++ ampd/src/handlers/xrpl_multisig.rs | 10 +++--- .../xrpl-multisig-prover/src/contract.rs | 4 --- contracts/xrpl-multisig-prover/src/msg.rs | 8 ----- contracts/xrpl-multisig-prover/src/query.rs | 12 +++---- contracts/xrpl-multisig-prover/src/reply.rs | 4 +-- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 6 ++++ integration-tests/tests/test_utils/mod.rs | 35 +++++-------------- 8 files changed, 28 insertions(+), 55 deletions(-) diff --git a/ampd/src/handlers/errors.rs b/ampd/src/handlers/errors.rs index b926714f8..905b99b8c 100644 --- a/ampd/src/handlers/errors.rs +++ b/ampd/src/handlers/errors.rs @@ -12,4 +12,8 @@ pub enum Error { Sign, #[error("failed to get transaction receipts")] TxReceipts, + #[error("failed to parse public key")] + PublicKey, + #[error("failed to prepare message for signing")] + MessageToSign, } diff --git a/ampd/src/handlers/xrpl_multisig.rs b/ampd/src/handlers/xrpl_multisig.rs index 3c27f90e7..7707689fb 100644 --- a/ampd/src/handlers/xrpl_multisig.rs +++ b/ampd/src/handlers/xrpl_multisig.rs @@ -24,7 +24,7 @@ use crate::tofnd::grpc::SharableEcdsaClient; use crate::tofnd::MessageDigest; use crate::types::PublicKey; use crate::types::TMAddress; -use xrpl_multisig_prover::xrpl_multisig::{public_key_to_xrpl_address, decode_address, xrpl_hash, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}; +use xrpl_multisig_prover::xrpl_multisig::public_key_to_xrpl_address; #[derive(Debug, Deserialize)] #[try_from("wasm-signing_started")] @@ -166,13 +166,11 @@ where match pub_keys.get(&self.worker) { Some(pub_key) => { - let pub_key_hex = HexBinary::from_hex(hex::encode_upper(pub_key.to_bytes()).as_str()).unwrap(); - let multisig_pub_key = multisig::key::PublicKey::try_from((multisig::key::KeyType::Ecdsa, pub_key_hex)).unwrap(); + let pub_key_hex = HexBinary::from(pub_key.to_bytes()); + let multisig_pub_key = multisig::key::PublicKey::try_from((multisig::key::KeyType::Ecdsa, pub_key_hex)).map_err(|_e| Error::PublicKey)?; let xrpl_address = public_key_to_xrpl_address(&multisig_pub_key); - let serialized_signer_xrpl_address = decode_address(&xrpl_address).unwrap(); - let msg = &[unsigned_tx.to_vec(), serialized_signer_xrpl_address.into()].concat(); - let msg_digest = MessageDigest::from(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)); + let msg_digest = MessageDigest::from(xrpl_multisig_prover::xrpl_multisig::message_to_sign(&unsigned_tx, &xrpl_address).map_err(|_e| Error::MessageToSign)?); let signature = self .signer diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 8f4e7ad27..4dce79a8c 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -355,10 +355,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetProof { multisig_session_id, } => to_binary(&query::get_proof(deps.storage, querier, &multisig_session_id)?), - QueryMsg::GetMessageToSign { - multisig_session_id, - signer_xrpl_address, - } => to_binary(&query::get_message_to_sign(deps.storage, &multisig_session_id, &signer_xrpl_address)?), QueryMsg::VerifyMessage { multisig_session_id, public_key, diff --git a/contracts/xrpl-multisig-prover/src/msg.rs b/contracts/xrpl-multisig-prover/src/msg.rs index a47e08147..46099fe47 100644 --- a/contracts/xrpl-multisig-prover/src/msg.rs +++ b/contracts/xrpl-multisig-prover/src/msg.rs @@ -12,9 +12,6 @@ pub enum QueryMsg { #[returns(GetProofResponse)] GetProof { multisig_session_id: Uint64 }, - #[returns(GetMessageToSignResponse)] - GetMessageToSign { multisig_session_id: Uint64, signer_xrpl_address: String }, - #[returns(bool)] VerifyMessage { multisig_session_id: Uint64, public_key: PublicKey, signature: Signature }, @@ -29,11 +26,6 @@ pub enum GetProofResponse { Pending { unsigned_tx_hash: TxHash }, } -#[cw_serde] -pub struct GetMessageToSignResponse { - pub tx_hash: HexBinary, -} - #[cw_serde] pub enum ExecuteMsg { RegisterToken { denom: String, token: XRPLToken }, diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 0e9cabb71..4c276f98d 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -6,8 +6,6 @@ use multisig::key::PublicKey; // TODO: remove dependency? use k256::{ecdsa, schnorr::signature::SignatureEncoding}; -use crate::msg::GetMessageToSignResponse; -use crate::types::TxHash; use crate::{ state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, }; @@ -41,7 +39,7 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul }) } -pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &String) -> StdResult { +pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &String) -> StdResult { let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; @@ -54,17 +52,15 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, let serialized_tx = &[serialized_unsigned_tx, serialized_signer_xrpl_address.to_vec()].concat(); - Ok(GetMessageToSignResponse { - tx_hash: xrpl_multisig::xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, serialized_tx).into() - }) + Ok(xrpl_multisig::xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, serialized_tx).into()) } pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: &PublicKey, signature: &Signature) -> StdResult { let signer_xrpl_address = xrpl_multisig::public_key_to_xrpl_address(&public_key); - let m = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; + let tx_hash = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; // m.tx_hash is going to be over 32 bytes due to inclusion of the signer address, so it has to be passed unchecked - signature.verify(&multisig::types::MsgToSign::unchecked(m.tx_hash), &public_key) + signature.verify(&multisig::types::MsgToSign::unchecked(tx_hash), &public_key) .map_err(|_e| ContractError::SignatureVerificationFailed.into()) } diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 5f29f1a7b..264f7bece 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -42,10 +42,10 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result = res.events .into_iter() - .filter(|e| e.ty == "signing_started") + .filter(|e| e.ty == "wasm-signing_started") .map(|e| e.attributes) .flatten() - .filter(|a| a.key != "msg") + .filter(|a| !a.key.starts_with("_") && a.key != "msg") .collect(); let evt = cosmwasm_std::Event::new("xrpl_signing_started") diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 00eca7968..d8876e407 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -391,6 +391,12 @@ pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result Result<[u8; 32], ContractError> { + let serialized_signer_xrpl_address = decode_address(&signer_xrpl_address)?; + let msg = &[encoded_unsigned_tx.to_vec(), serialized_signer_xrpl_address.into()].concat(); + Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)) +} + pub struct XRPLAddress(String); pub trait XRPLSerialize { diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 51b6af3a9..378b1ff1e 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -288,23 +288,21 @@ pub fn sign_xrpl_proof( .map(|attr| attr.value.as_str().try_into().unwrap()) .expect("couldn't get session_id"); + let unsigned_tx: HexBinary = + get_event_attribute(&response.events, "wasm-xrpl_signing_started", "unsigned_tx") + .map(|attr| HexBinary::from_hex(attr.value.as_str()).unwrap()) + .expect("couldn't get unsigned_tx"); + for worker in workers { let xrpl_signer_address = xrpl_multisig_prover::xrpl_multisig::public_key_to_xrpl_address( &multisig::key::PublicKey::Ecdsa(worker.key_pair.encoded_verifying_key().into()) ); - - let msg_to_sign = match get_xrpl_message_to_sign( - app, - multisig_prover_address, - &session_id, - &xrpl_signer_address - ) { - xrpl_multisig_prover::msg::GetMessageToSignResponse { tx_hash } => tx_hash, - }; + + let msg = xrpl_multisig_prover::xrpl_multisig::message_to_sign(&unsigned_tx, &xrpl_signer_address).unwrap(); let signature = tofn::ecdsa::sign( worker.key_pair.signing_key(), - &msg_to_sign + &msg .as_slice() .try_into() .unwrap(), @@ -396,23 +394,6 @@ pub fn get_xrpl_proof( query_response.unwrap() } -pub fn get_xrpl_message_to_sign( - app: &mut App, - multisig_prover_address: &Addr, - multisig_session_id: &Uint64, - xrpl_signer_address: &String, -) -> xrpl_multisig_prover::msg::GetMessageToSignResponse { - let query_response = app.wrap().query_wasm_smart( - multisig_prover_address, - &xrpl_multisig_prover::msg::QueryMsg::GetMessageToSign { - multisig_session_id: *multisig_session_id, - signer_xrpl_address: xrpl_signer_address.clone(), - }, - ); - assert!(query_response.is_ok()); - query_response.unwrap() -} - pub fn xrpl_update_tx_status( app: &mut App, multisig_prover_address: &Addr, From 8985a3cc738c3b07cffff0eb9fc67939f1cfdab9 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 14 Feb 2024 14:18:31 +0100 Subject: [PATCH 42/80] xrpl-multisig-prover: uncomment reward integration test assertions --- integration-tests/tests/message_routing.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index dbedd5271..a66b0f4e3 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -301,8 +301,6 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { VerificationStatus::SucceededOnChain ); - // TODO: FIX REWARDS - /* // Advance the height to be able to distribute rewards test_utils::advance_height( &mut protocol.app, @@ -332,5 +330,5 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { .query_balance(worker.addr, test_utils::AXL_DENOMINATION) .unwrap(); assert_eq!(balance.amount, expected_rewards); - }*/ + } } From 74d34aa2aaedda658b77000b42376830bd2f6ffa Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 14 Feb 2024 14:38:46 +0100 Subject: [PATCH 43/80] xrpl-multisig-prover: only governance can register token --- contracts/xrpl-multisig-prover/src/contract.rs | 16 ++++++++++++++-- contracts/xrpl-multisig-prover/src/state.rs | 1 + integration-tests/tests/test_utils/mod.rs | 3 ++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 4dce79a8c..eb2e9d858 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -37,6 +37,7 @@ pub struct InstantiateMsg { pub available_tickets: Vec, pub next_sequence_number: u32, pub last_assigned_ticket_number: u32, + pub governance_address: String, } #[cfg_attr(not(feature = "library"), entry_point)] @@ -50,6 +51,7 @@ pub fn instantiate( let gateway_address = deps.api.addr_validate(&msg.gateway_address)?; let voting_verifier_address = deps.api.addr_validate(&msg.voting_verifier_address)?; let service_registry_address = deps.api.addr_validate(&msg.service_registry_address)?; + let governance_address = deps.api.addr_validate(&msg.governance_address)?; if msg.signing_threshold.numerator() > u32::MAX.into() { return Err(ContractError::InvalidSigningThreshold.into()); @@ -67,6 +69,7 @@ pub fn instantiate( xrpl_fee: msg.xrpl_fee, ticket_count_threshold: msg.ticket_count_threshold, key_type: multisig::key::KeyType::Ecdsa, + governance_address, }; CONFIG.save(deps.storage, &config)?; @@ -91,11 +94,21 @@ pub fn instantiate( Ok(Response::new().add_message(msg)) } +pub fn require_governance(governance: &Addr, sender: &Addr) -> Result<(), ContractError> { + if governance != sender { + return Err(ContractError::Unauthorized); + } + Ok(()) +} + fn register_token( storage: &mut dyn Storage, + config: &Config, + sender: &Addr, denom: String, token: &XRPLToken, ) -> Result { + require_governance(&config.governance_address, sender)?; TOKENS.save(storage, denom, token)?; Ok(Response::default()) } @@ -111,9 +124,8 @@ pub fn execute( let querier = Querier::new(deps.querier, config.clone()); let res = match msg { - // TODO: should be admin-only ExecuteMsg::RegisterToken { denom, token } => { - register_token(deps.storage, denom, &token) + register_token(deps.storage, &config, &info.sender, denom, &token) }, ExecuteMsg::ConstructProof { message_id } => { construct_payment_proof(deps.storage, querier, info, env.contract.address, env.block.height, &config, message_id) diff --git a/contracts/xrpl-multisig-prover/src/state.rs b/contracts/xrpl-multisig-prover/src/state.rs index 30546f812..e26e54779 100644 --- a/contracts/xrpl-multisig-prover/src/state.rs +++ b/contracts/xrpl-multisig-prover/src/state.rs @@ -18,6 +18,7 @@ pub struct Config { pub xrpl_fee: u64, pub ticket_count_threshold: u32, pub key_type: multisig::key::KeyType, + pub governance_address: Addr, } pub const CONFIG: Item = Item::new("config"); diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 378b1ff1e..1e98c6068 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -985,7 +985,7 @@ pub fn register_token( token: XRPLToken, ) { let response = protocol.app.execute_contract( - Addr::unchecked("doesn't matter"), // TODO: should fail unless admin + protocol.governance_address.clone(), multisig_prover_address.clone(), &xrpl_multisig_prover::msg::ExecuteMsg::RegisterToken { denom, @@ -1042,6 +1042,7 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { vec![], (44218195..44218200).collect::>() ].concat(), + governance_address: protocol.governance_address.to_string(), }, ); From 51524d5ee914c461b23cae2c65acb6d6fe14f7b7 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 14 Feb 2024 16:29:38 +0100 Subject: [PATCH 44/80] xrpl-multisig-prover: create get_next_multisig_session_id query function --- contracts/multisig-prover/src/test/mocks/multisig.rs | 1 + contracts/multisig/src/contract.rs | 1 + contracts/multisig/src/contract/query.rs | 6 ++++++ contracts/multisig/src/msg.rs | 3 +++ 4 files changed, 11 insertions(+) diff --git a/contracts/multisig-prover/src/test/mocks/multisig.rs b/contracts/multisig-prover/src/test/mocks/multisig.rs index ff1a89f35..4ed74abea 100644 --- a/contracts/multisig-prover/src/test/mocks/multisig.rs +++ b/contracts/multisig-prover/src/test/mocks/multisig.rs @@ -89,6 +89,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { worker_address, key_type, )), + QueryMsg::GetNextSessionId => unimplemented!(), } } diff --git a/contracts/multisig/src/contract.rs b/contracts/multisig/src/contract.rs index c343de0d7..0a282d579 100644 --- a/contracts/multisig/src/contract.rs +++ b/contracts/multisig/src/contract.rs @@ -103,6 +103,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { deps.api.addr_validate(&worker_address)?, key_type, )?), + QueryMsg::GetNextSessionId => to_binary(&query::get_next_session_id(deps)?), } } diff --git a/contracts/multisig/src/contract/query.rs b/contracts/multisig/src/contract/query.rs index 33c66d7cf..34fa38429 100644 --- a/contracts/multisig/src/contract/query.rs +++ b/contracts/multisig/src/contract/query.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use crate::{ key::{KeyType, PublicKey}, state::{load_pub_key, load_session_signatures}, @@ -34,3 +36,7 @@ pub fn get_public_key(deps: Deps, worker: Addr, key_type: KeyType) -> StdResult< let raw = load_pub_key(deps.storage, worker, key_type)?; Ok(PublicKey::try_from((key_type, raw)).expect("could not decode pub key")) } + +pub fn get_next_session_id(deps: Deps) -> StdResult { + Ok(SIGNING_SESSION_COUNTER.load(deps.storage)?.add(Uint64::one())) +} diff --git a/contracts/multisig/src/msg.rs b/contracts/multisig/src/msg.rs index ba4062465..c290a8518 100644 --- a/contracts/multisig/src/msg.rs +++ b/contracts/multisig/src/msg.rs @@ -67,6 +67,9 @@ pub enum QueryMsg { worker_address: String, key_type: KeyType, }, + + #[returns(Uint64)] + GetNextSessionId, } #[cw_serde] From 8f72e942395f5a760ed330a5cf2c09f2db594b96 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 14 Feb 2024 16:34:31 +0100 Subject: [PATCH 45/80] xrpl-multisig-prover: include multisig_session_id in memo & modify token registration logic --- .../src/axelar_workers.rs | 2 +- .../xrpl-multisig-prover/src/contract.rs | 40 ++++++---- contracts/xrpl-multisig-prover/src/error.rs | 3 + contracts/xrpl-multisig-prover/src/querier.rs | 6 ++ contracts/xrpl-multisig-prover/src/reply.rs | 2 + contracts/xrpl-multisig-prover/src/state.rs | 2 + contracts/xrpl-multisig-prover/src/types.rs | 4 - .../xrpl-multisig-prover/src/xrpl_multisig.rs | 73 ++++++++++--------- integration-tests/tests/message_routing.rs | 4 +- integration-tests/tests/test_utils/mod.rs | 13 +--- integration-tests/tests/update_worker_set.rs | 2 +- 11 files changed, 84 insertions(+), 67 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index 3fe534077..67e6fbaf9 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -96,7 +96,7 @@ fn convert_or_scale_weights(weights: Vec) -> Vec { } pub fn get_active_worker_set( - querier: Querier, + querier: &Querier, signing_threshold: Threshold, block_height: u64, ) -> Result { diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index eb2e9d858..40e21cd6e 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -38,6 +38,7 @@ pub struct InstantiateMsg { pub next_sequence_number: u32, pub last_assigned_ticket_number: u32, pub governance_address: String, + pub xrp_denom: String, } #[cfg_attr(not(feature = "library"), entry_point)] @@ -70,6 +71,7 @@ pub fn instantiate( ticket_count_threshold: msg.ticket_count_threshold, key_type: multisig::key::KeyType::Ecdsa, governance_address, + xrp_denom: msg.xrp_denom, }; CONFIG.save(deps.storage, &config)?; @@ -79,7 +81,7 @@ pub fn instantiate( AVAILABLE_TICKETS.save(deps.storage, &msg.available_tickets)?; let querier = Querier::new(deps.querier, config.clone()); - let new_worker_set = axelar_workers::get_active_worker_set(querier, msg.signing_threshold, env.block.height)?; + let new_worker_set = axelar_workers::get_active_worker_set(&querier, msg.signing_threshold, env.block.height)?; CURRENT_WORKER_SET.save(deps.storage, &new_worker_set)?; @@ -108,6 +110,10 @@ fn register_token( denom: String, token: &XRPLToken, ) -> Result { + if token.currency == "XRP" || token.currency.len() != 3 { + return Err(ContractError::InvalidTokenDenom); + } + require_governance(&config.governance_address, sender)?; TOKENS.save(storage, denom, token)?; Ok(Response::default()) @@ -128,16 +134,16 @@ pub fn execute( register_token(deps.storage, &config, &info.sender, denom, &token) }, ExecuteMsg::ConstructProof { message_id } => { - construct_payment_proof(deps.storage, querier, info, env.contract.address, env.block.height, &config, message_id) + construct_payment_proof(deps.storage, &querier, info, env.contract.address, env.block.height, &config, message_id) }, ExecuteMsg::UpdateWorkerSet {} => { - construct_signer_list_set_proof(deps.storage, querier, env, &config) + construct_signer_list_set_proof(deps.storage, &querier, env, &config) }, ExecuteMsg::UpdateTxStatus { multisig_session_id, signers, message_id, message_status } => { - update_tx_status(deps.storage, querier, &multisig_session_id, &signers, &message_id, message_status, config.axelar_multisig_address, config.xrpl_multisig_address) + update_tx_status(deps.storage, &querier, &multisig_session_id, &signers, &message_id, message_status, config.axelar_multisig_address, config.xrpl_multisig_address) }, ExecuteMsg::TicketCreate {} => { - construct_ticket_create_proof(deps.storage, env.contract.address, &config) + construct_ticket_create_proof(deps.storage, &querier, env.contract.address, &config) }, }?; @@ -146,7 +152,7 @@ pub fn execute( fn construct_payment_proof( storage: &mut dyn Storage, - querier: Querier, + querier: &Querier, info: MessageInfo, self_address: Addr, block_height: u64, @@ -157,6 +163,7 @@ fn construct_payment_proof( return Err(ContractError::InvalidPaymentAmount); } + // Protect against double signing the same message ID match MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, message_id.clone())? { Some(multisig_session_id) => { let multisig_session = querier.get_multisig_session(Uint64::from(multisig_session_id))?; @@ -173,27 +180,27 @@ fn construct_payment_proof( let mut funds = info.funds; let coin = funds.remove(0); - let xrpl_token = TOKENS.load(storage, coin.denom.clone())?; let message = querier.get_message(message_id.clone())?; - let xrpl_payment_amount = if xrpl_token.currency == XRPLToken::NATIVE_CURRENCY { + let xrpl_payment_amount = if coin.denom == config.xrp_denom { + // TODO: handle decimal precision conversion let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { amount: coin.amount.to_string(), reason: "overflow".to_string() })?; XRPLPaymentAmount::Drops(drops) } else { + let xrpl_token = TOKENS.load(storage, coin.denom.clone())?; XRPLPaymentAmount::Token( - XRPLToken { - issuer: xrpl_token.issuer, - currency: xrpl_token.currency, - }, + xrpl_token, XRPLTokenAmount(coin.amount.to_string()), ) }; + let multisig_session_id = querier.get_next_multisig_session_id()?; let tx_hash = xrpl_multisig::issue_payment( storage, config, message.destination_address.to_string().try_into()?, xrpl_payment_amount, message_id.clone(), + multisig_session_id, )?; REPLY_MESSAGE_ID.save(storage, &message_id)?; @@ -231,7 +238,7 @@ pub fn start_signing_session( fn construct_signer_list_set_proof( storage: &mut dyn Storage, - querier: Querier, + querier: &Querier, env: Env, config: &Config, ) -> Result { @@ -249,10 +256,12 @@ fn construct_signer_list_set_proof( return Err(ContractError::WorkerSetUnchanged.into()) } + let multisig_session_id = querier.get_next_multisig_session_id()?; let tx_hash = xrpl_multisig::issue_signer_list_set( storage, config, cur_worker_set, + multisig_session_id, )?; NEXT_WORKER_SET.save(storage, tx_hash.clone(), &new_worker_set)?; @@ -269,6 +278,7 @@ fn construct_signer_list_set_proof( fn construct_ticket_create_proof( storage: &mut dyn Storage, + querier: &Querier, self_address: Addr, config: &Config, ) -> Result { @@ -277,10 +287,12 @@ fn construct_ticket_create_proof( return Err(ContractError::TicketCountThresholdNotReached.into()); } + let multisig_session_id = querier.get_next_multisig_session_id()?; let tx_hash = xrpl_multisig::issue_ticket_create( storage, config, ticket_count, + multisig_session_id, )?; let response = start_signing_session( @@ -295,7 +307,7 @@ fn construct_ticket_create_proof( fn update_tx_status( storage: &mut dyn Storage, - querier: Querier, + querier: &Querier, multisig_session_id: &Uint64, signers: &Vec, message_id: &CrossChainId, diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 31dad3d97..16dac4af7 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -103,6 +103,9 @@ pub enum ContractError { #[error("payment already signed")] PaymentAlreadySigned, + + #[error("invalid token denomination")] + InvalidTokenDenom, } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index b2aea55e4..6ab1ee68a 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -81,4 +81,10 @@ impl<'a> Querier<'a> { }; query(self.querier, self.config.axelar_multisig_address.to_string(), &query_msg) } + + pub fn get_next_multisig_session_id(&self) -> Result { + query(self.querier, self.config.axelar_multisig_address.to_string(), + &multisig::msg::QueryMsg::GetNextSessionId, + ) + } } diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 264f7bece..0840d9083 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -36,6 +36,8 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result (), } + REPLY_MESSAGE_ID.remove(deps.storage); + let tx_info = TRANSACTION_INFO.load(deps.storage, tx_hash.clone())?; let res = reply.result.unwrap(); diff --git a/contracts/xrpl-multisig-prover/src/state.rs b/contracts/xrpl-multisig-prover/src/state.rs index e26e54779..75af8706e 100644 --- a/contracts/xrpl-multisig-prover/src/state.rs +++ b/contracts/xrpl-multisig-prover/src/state.rs @@ -19,11 +19,13 @@ pub struct Config { pub ticket_count_threshold: u32, pub key_type: multisig::key::KeyType, pub governance_address: Addr, + pub xrp_denom: String, } pub const CONFIG: Item = Item::new("config"); pub const REPLY_TX_HASH: Item = Item::new("reply_tx_hash"); pub const REPLY_MESSAGE_ID: Item = Item::new("reply_message_id"); +// TODO: rename to MULTISIG_SESSION_ID_TO_TX_HASH pub const MULTISIG_SESSION_TX: Map = Map::new("multisig_session_tx"); // The next seq. no. is determined on TicketCreate and depends on the number of created tickets, diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 7cabe5622..990f1d622 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -125,7 +125,3 @@ pub struct XRPLToken { pub issuer: String, pub currency: String, } - -impl XRPLToken { - pub const NATIVE_CURRENCY: &'static str = "XRP"; -} diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index d8876e407..0bcf77aed 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -147,7 +147,7 @@ pub struct XRPLPaymentTx { pub sequence: Sequence, pub amount: XRPLPaymentAmount, pub destination: nonempty::String, - pub multisig_session_id: Option + pub multisig_session_id: Uint64 } impl TryFrom<&XRPLPaymentTx> for XRPLObject { @@ -166,11 +166,9 @@ impl TryFrom<&XRPLPaymentTx> for XRPLObject { obj.add_field(1, &XRPLAddress(tx.account.clone()))?; obj.add_field(3, &XRPLAddress(tx.destination.to_string()))?; - if let Some(multisig_session_id) = tx.multisig_session_id { - let memo_data: Vec = multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; - } + let memo_data: Vec = tx.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; Ok(obj) } @@ -183,7 +181,7 @@ pub struct XRPLSignerListSetTx { pub sequence: Sequence, pub signer_quorum: u32, pub signer_entries: Vec, - pub multisig_session_id: Option + pub multisig_session_id: Uint64 } impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { @@ -202,11 +200,9 @@ impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { obj.add_field(4, &XRPLArray{ field_code: 11, items: tx.signer_entries.clone() })?; - if let Some(multisig_session_id) = tx.multisig_session_id { - let memo_data: Vec = multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; - } + let memo_data: Vec = tx.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; Ok(obj) } @@ -219,7 +215,7 @@ pub struct XRPLTicketCreateTx { pub fee: u64, pub sequence: Sequence, pub ticket_count: u32, - pub multisig_session_id: Option + pub multisig_session_id: Uint64 } impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { @@ -236,11 +232,9 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { obj.add_field(3, &HexBinary::from_hex("")?)?; obj.add_field(1, &XRPLAddress(tx.account.clone()))?; - if let Some(multisig_session_id) = tx.multisig_session_id { - let memo_data: Vec = multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; - } + let memo_data: Vec = tx.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; Ok(obj) } @@ -689,6 +683,7 @@ pub fn issue_payment( destination: nonempty::String, amount: XRPLPaymentAmount, message_id: CrossChainId, + multisig_session_id: Uint64, ) -> Result { let ticket_number = assign_ticket_number(storage, message_id.clone())?; @@ -696,7 +691,7 @@ pub fn issue_payment( account: config.xrpl_multisig_address.to_string(), fee: config.xrpl_fee, sequence: Sequence::Ticket(ticket_number), - multisig_session_id: None, + multisig_session_id, amount, destination }; @@ -708,7 +703,12 @@ pub fn issue_payment( ) } -pub fn issue_ticket_create(storage: &mut dyn Storage, config: &Config, ticket_count: u32) -> Result { +pub fn issue_ticket_create( + storage: &mut dyn Storage, + config: &Config, + ticket_count: u32, + multisig_session_id: Uint64 +) -> Result { let sequence_number = get_next_sequence_number(storage)?; let tx = XRPLTicketCreateTx { @@ -716,7 +716,7 @@ pub fn issue_ticket_create(storage: &mut dyn Storage, config: &Config, ticket_co fee: config.xrpl_fee, sequence: Sequence::Plain(sequence_number.clone()), ticket_count, - multisig_session_id: None + multisig_session_id, }; issue_tx( @@ -726,7 +726,12 @@ pub fn issue_ticket_create(storage: &mut dyn Storage, config: &Config, ticket_co ) } -pub fn issue_signer_list_set(storage: &mut dyn Storage, config: &Config, workers: WorkerSet) -> Result { +pub fn issue_signer_list_set( + storage: &mut dyn Storage, + config: &Config, + workers: WorkerSet, + multisig_session_id: Uint64 +) -> Result { let sequence_number = get_next_sequence_number(storage)?; let tx = XRPLSignerListSetTx { @@ -735,7 +740,7 @@ pub fn issue_signer_list_set(storage: &mut dyn Storage, config: &Config, workers sequence: Sequence::Plain(sequence_number.clone()), signer_quorum: workers.quorum, signer_entries: make_xrpl_signer_entries(workers.signers), - multisig_session_id: None + multisig_session_id, }; issue_tx( @@ -1151,11 +1156,11 @@ mod tests { XRPLTokenAmount("0.3369568318".to_string()), ), destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), - multisig_session_id: None, + multisig_session_id: Uint64::from(1u8), }; let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize().unwrap(); assert_eq!( - "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", + "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D0101E1F1", hex::encode_upper(encoded_unsigned_tx) ); } @@ -1168,11 +1173,11 @@ mod tests { sequence: Sequence::Plain(1), amount: XRPLPaymentAmount::Drops(1000), destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), - multisig_session_id: None, + multisig_session_id: Uint64::from(0u8), }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); assert_eq!( - "120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", + "120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D00E1F1", hex::encode_upper(encoded_unsigned_tx) ); @@ -1182,11 +1187,11 @@ mod tests { sequence: Sequence::Plain(43497363), amount: XRPLPaymentAmount::Drops(1000000000), destination: nonempty::String::try_from("rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq").unwrap(), - multisig_session_id: None, + multisig_session_id: Uint64::from(1337u16), }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); assert_eq!( - "1200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6", + "1200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6F9EA7D020539E1F1", hex::encode_upper(encoded_unsigned_tx) ); } @@ -1200,7 +1205,7 @@ mod tests { sequence: Sequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), - multisig_session_id: Some(Uint64::from(5461264u64)), + multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), @@ -1230,7 +1235,7 @@ mod tests { sequence: Sequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), - multisig_session_id: Some(Uint64::from(5461264u64)), + multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), @@ -1260,7 +1265,7 @@ mod tests { sequence: Sequence::Ticket(45205896), amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string() }, XRPLTokenAmount("100000000".to_string())), destination: nonempty::String::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo").unwrap(), - multisig_session_id: Some(Uint64::from(5461264u64)), + multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".to_string(), @@ -1290,7 +1295,7 @@ mod tests { fee: 30, sequence: Sequence::Plain(44218194), ticket_count: 3, - multisig_session_id: Some(Uint64::from(5461264u64)), + multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), @@ -1329,7 +1334,7 @@ mod tests { signer_weight: 1 } ], - multisig_session_id: Some(Uint64::from(5461264u64)) + multisig_session_id: Uint64::from(5461264u64) }), signers: vec![ XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index a66b0f4e3..13e75abcd 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -148,7 +148,7 @@ fn xrpl_ticket_create_can_be_proven() { destination_address: Address::try_from(xrpl_multisig_address).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "9c2f220fe5ee650b3cd10b0a72af1206b3912afce8376214234354180198c5d5:0" + id: "8c23c331f52ed2856308fea85be7583f8ff726ad87b0dbbcbe5e371563b7c02d:0" .to_string() .try_into() .unwrap(), @@ -269,7 +269,7 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { destination_address: Address::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".to_string()).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "3012df8615c9f1d8ac3bba050bf4fcbf3981156962108ca8751e12146ec4b584:0" + id: "21b61845c5a4e3b00e60f21c0fde6c0208eff28c9e57709306f47672bf8f984d:0" .to_string() .try_into() .unwrap(), diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 1e98c6068..8487316bd 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -297,7 +297,7 @@ pub fn sign_xrpl_proof( let xrpl_signer_address = xrpl_multisig_prover::xrpl_multisig::public_key_to_xrpl_address( &multisig::key::PublicKey::Ecdsa(worker.key_pair.encoded_verifying_key().into()) ); - + let msg = xrpl_multisig_prover::xrpl_multisig::message_to_sign(&unsigned_tx, &xrpl_signer_address).unwrap(); let signature = tofn::ecdsa::sign( @@ -1043,16 +1043,7 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { (44218195..44218200).collect::>() ].concat(), governance_address: protocol.governance_address.to_string(), - }, - ); - - register_token( - protocol, - multisig_prover_address.clone(), - XRP_DENOMINATION.to_string(), - XRPLToken { - issuer: "".to_string(), - currency: XRPLToken::NATIVE_CURRENCY.to_string(), + xrp_denom: "uxrp".to_string(), }, ); diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index a97e71e51..ec734147a 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -198,7 +198,7 @@ fn xrpl_worker_set_can_be_initialized_and_then_manually_updated() { destination_address: Address::try_from(xrpl_multisig_address).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "fbf428da41656ca3aef36287bfcb6d8491daa76f20c201c4a60172450ab517f9:0" + id: "f6471a07847f853d13601496cb643c6f111856ab336b204deefde520657900fb:0" .to_string() .try_into() .unwrap(), From 6165a46db764f560e13aa312096a6ddaf495eff4 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 14 Feb 2024 17:15:32 +0100 Subject: [PATCH 46/80] xrpl-multisig-prover: relocate query in update_tx_status --- contracts/xrpl-multisig-prover/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 40e21cd6e..63f449d6c 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -343,11 +343,11 @@ fn update_tx_status( let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?).into(); - let actual_status = querier.get_message_status(message)?; if parse_message_id(&message_id.id)?.0.to_string() != tx_hash.to_string() { return Err(ContractError::InvalidMessageID(message_id.id.to_string())); } + let actual_status = querier.get_message_status(message)?; if status != actual_status { return Err(ContractError::InvalidMessageStatus) } From ce3c16435bcf60fc46531dea9680c392d917ce0e Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 14 Feb 2024 17:20:17 +0100 Subject: [PATCH 47/80] xrpl-multisig-prover: move make_xrpl_signed_tx to xrpl_multisig --- .../xrpl-multisig-prover/src/contract.rs | 2 +- contracts/xrpl-multisig-prover/src/query.rs | 36 ++----------------- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 30 ++++++++++++++++ 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 63f449d6c..55d9e4c8f 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -339,7 +339,7 @@ fn update_tx_status( return Err(ContractError::SignatureNotFound); } - let signed_tx = query::make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; // TODO: RELOCATE FUNCTION + let signed_tx = xrpl_multisig::make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?).into(); diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 4c276f98d..931767cde 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -3,42 +3,10 @@ use cosmwasm_std::{StdResult, Uint64, HexBinary, Storage}; use multisig::{key::Signature, types::MultisigState}; use multisig::key::PublicKey; -// TODO: remove dependency? -use k256::{ecdsa, schnorr::signature::SignatureEncoding}; - use crate::{ - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{XRPLUnsignedTx, XRPLSignedTransaction, XRPLSigner, self, XRPLSerialize, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, XRPLSerialize, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, }; -pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { - let xrpl_signers: Vec = axelar_signers - .iter() - .map(|(axelar_signer, signature)| -> Result { - let xrpl_address = xrpl_multisig::public_key_to_xrpl_address(&axelar_signer.pub_key); - let txn_signature = match signature { - // TODO: use unwrapped signature instead of ignoring it - multisig::key::Signature::Ecdsa(_) | - multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( - &ecdsa::Signature::try_from(signature.clone().as_ref()) - .map_err(|_| ContractError::FailedToEncodeSignature)? - ).to_vec()), - _ => unimplemented!("Unsupported signature type"), - }; - - Ok(XRPLSigner { - account: xrpl_address, - signing_pub_key: axelar_signer.pub_key.clone().into(), - txn_signature, - }) - }) - .collect::, ContractError>>()?; - - Ok(XRPLSignedTransaction { - unsigned_tx, - signers: xrpl_signers, - }) -} - pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &String) -> StdResult { let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; @@ -80,7 +48,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & .map(|(signer, signature)| (signer.clone(), signature.clone().unwrap())) .collect(); - let signed_tx = make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; + let signed_tx = xrpl_multisig::make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; let tx_blob: HexBinary = HexBinary::from(signed_tx.xrpl_serialize()?); GetProofResponse::Completed { unsigned_tx_hash, tx_blob } } diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 0bcf77aed..1d0d9be2f 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -5,6 +5,7 @@ use axelar_wasm_std::{nonempty, FnExt}; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; +use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use multisig::key::PublicKey; use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; @@ -829,6 +830,35 @@ fn mark_ticket_unavailable(storage: &mut dyn Storage, ticket: u32) -> Result<(), Ok(()) } +pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { + let xrpl_signers: Vec = axelar_signers + .iter() + .map(|(axelar_signer, signature)| -> Result { + let xrpl_address = public_key_to_xrpl_address(&axelar_signer.pub_key); + let txn_signature = match signature { + // TODO: use unwrapped signature instead of ignoring it + multisig::key::Signature::Ecdsa(_) | + multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( + &ecdsa::Signature::try_from(signature.clone().as_ref()) + .map_err(|_| ContractError::FailedToEncodeSignature)? + ).to_vec()), + _ => unimplemented!("Unsupported signature type"), + }; + + Ok(XRPLSigner { + account: xrpl_address, + signing_pub_key: axelar_signer.pub_key.clone().into(), + txn_signature, + }) + }) + .collect::, ContractError>>()?; + + Ok(XRPLSignedTransaction { + unsigned_tx, + signers: xrpl_signers, + }) +} + pub fn update_tx_status( storage: &mut dyn Storage, axelar_multisig_address: impl Into, From c740e60da3a70ccaaa35ec6e45a31e482f0e8aca Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Fri, 16 Feb 2024 15:25:48 +0100 Subject: [PATCH 48/80] wip: remove unwraps and clones --- .../src/axelar_workers.rs | 17 +- .../xrpl-multisig-prover/src/contract.rs | 30 +-- contracts/xrpl-multisig-prover/src/querier.rs | 14 +- contracts/xrpl-multisig-prover/src/query.rs | 6 +- contracts/xrpl-multisig-prover/src/reply.rs | 4 +- contracts/xrpl-multisig-prover/src/state.rs | 12 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 199 ++++++++++-------- 7 files changed, 146 insertions(+), 136 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index 67e6fbaf9..50e8fab5b 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -56,8 +56,7 @@ impl WorkerSet { pub fn pub_keys_by_address(&self) -> HashMap { self .signers - .clone() - .into_iter() + .iter() .map(|signer| { ( signer.address.to_string(), @@ -82,8 +81,7 @@ fn convert_or_scale_weights(weights: Vec) -> Vec { let max_u16_as_uint256 = Uint256::from(u16::MAX); // Scaling down weights - .clone() - .into_iter() + .iter() .map(|weight| { // multiply_ratio returns a rounded down value let scaled = weight.multiply_ratio(max_u16_as_uint256, *max_weight); @@ -103,21 +101,20 @@ pub fn get_active_worker_set( let workers: Vec = querier.get_active_workers()?; let participants: Vec = workers - .into_iter() - .map(|worker| Participant::try_from(worker)) + .iter() + .map(|worker| Participant::try_from(worker.clone())) .filter(|result| result.is_ok()) .map(|result| result.unwrap()) .collect(); let weights = convert_or_scale_weights(participants - .clone() - .into_iter() + .iter() .map(|participant| Uint256::from(participant.weight)) .collect()); let mut signers: Vec = vec![]; for (i, participant) in participants.iter().enumerate() { - let pub_key: PublicKey = querier.get_public_key(participant.address.clone().to_string())?; + let pub_key: PublicKey = querier.get_public_key(&participant.address.to_string())?; signers.push(AxelarSigner { address: participant.address.clone(), weight: weights[i], @@ -134,7 +131,7 @@ pub fn get_active_worker_set( .unwrap() as u32; let worker_set = WorkerSet { - signers: signers.into_iter().collect(), + signers: BTreeSet::from_iter(signers.into_iter()), quorum, created_at: block_height, }; diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 55d9e4c8f..5a54dbd22 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -115,7 +115,7 @@ fn register_token( } require_governance(&config.governance_address, sender)?; - TOKENS.save(storage, denom, token)?; + TOKENS.save(storage, &denom, token)?; Ok(Response::default()) } @@ -164,9 +164,9 @@ fn construct_payment_proof( } // Protect against double signing the same message ID - match MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, message_id.clone())? { + match MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, &message_id)? { Some(multisig_session_id) => { - let multisig_session = querier.get_multisig_session(Uint64::from(multisig_session_id))?; + let multisig_session = querier.get_multisig_session(&Uint64::from(multisig_session_id))?; if let MultisigState::Completed { .. } = multisig_session.state { return Err(ContractError::PaymentAlreadySigned); } @@ -180,13 +180,13 @@ fn construct_payment_proof( let mut funds = info.funds; let coin = funds.remove(0); - let message = querier.get_message(message_id.clone())?; + let message = querier.get_message(&message_id)?; let xrpl_payment_amount = if coin.denom == config.xrp_denom { // TODO: handle decimal precision conversion let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { amount: coin.amount.to_string(), reason: "overflow".to_string() })?; XRPLPaymentAmount::Drops(drops) } else { - let xrpl_token = TOKENS.load(storage, coin.denom.clone())?; + let xrpl_token = TOKENS.load(storage, &coin.denom)?; XRPLPaymentAmount::Token( xrpl_token, XRPLTokenAmount(coin.amount.to_string()), @@ -198,9 +198,9 @@ fn construct_payment_proof( storage, config, message.destination_address.to_string().try_into()?, - xrpl_payment_amount, - message_id.clone(), - multisig_session_id, + &xrpl_payment_amount, + &message_id, + &multisig_session_id, )?; REPLY_MESSAGE_ID.save(storage, &message_id)?; @@ -231,7 +231,7 @@ pub fn start_signing_session( sig_verifier: None, }; - let wasm_msg = wasm_execute(config.axelar_multisig_address.clone(), &start_sig_msg, vec![])?; + let wasm_msg = wasm_execute(&config.axelar_multisig_address, &start_sig_msg, vec![])?; Ok(Response::new().add_submessage(SubMsg::reply_on_success(wasm_msg, START_MULTISIG_REPLY_ID))) } @@ -264,7 +264,7 @@ fn construct_signer_list_set_proof( multisig_session_id, )?; - NEXT_WORKER_SET.save(storage, tx_hash.clone(), &new_worker_set)?; + NEXT_WORKER_SET.save(storage, &tx_hash, &new_worker_set)?; Ok( start_signing_session( @@ -316,8 +316,8 @@ fn update_tx_status( xrpl_multisig_address: String, ) -> Result { let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; - let tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; - let multisig_session = querier.get_multisig_session(multisig_session_id.clone())?; + let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; + let multisig_session = querier.get_multisig_session(&multisig_session_id)?; let message = Message { destination_chain: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), source_address: Address::from_str(&xrpl_multisig_address).map_err(|_| ContractError::InvalidAddress)?, @@ -331,8 +331,8 @@ fn update_tx_status( let axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)> = multisig_session.signers .iter() - .filter(|(signer, signature)| signature.is_some() && signers.contains(&signer.address)) - .map(|(signer, signature)| (signer.clone(), signature.clone().unwrap())) + .filter(|(signer, _)| signers.contains(&signer.address)) + .filter_map(|(signer, signature)| signature.as_ref().map(|signature| (signer.clone(), signature.clone()))) .collect(); if axelar_signers.len() != signers.len() { @@ -352,7 +352,7 @@ fn update_tx_status( return Err(ContractError::InvalidMessageStatus) } - match xrpl_multisig::update_tx_status(storage, axelar_multisig_address, unsigned_tx_hash, status.clone().into())? { + match xrpl_multisig::update_tx_status(storage, axelar_multisig_address, unsigned_tx_hash, status.into())? { None => Ok(Response::default()), Some(msg) => Ok(Response::new().add_message(msg)) } diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index 6ab1ee68a..cb947590b 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -48,22 +48,22 @@ impl<'a> Querier<'a> { ) } - pub fn get_public_key(&self, worker_address: String) -> Result { + pub fn get_public_key(&self, worker_address: &String) -> Result { query(self.querier, self.config.axelar_multisig_address.to_string(), &multisig::msg::QueryMsg::GetPublicKey { - worker_address, + worker_address: worker_address.clone(), key_type: self.config.key_type, }, ) } - pub fn get_message(&self, message_id: CrossChainId) -> Result { + pub fn get_message(&self, message_id: &CrossChainId) -> Result { let messages: Vec = query(self.querier, self.config.gateway_address.to_string(), &gateway::msg::QueryMsg::GetMessages { - message_ids: vec![message_id], + message_ids: vec![message_id.clone()], } )?; - Ok(messages[0].clone()) + Ok(messages[0]) } pub fn get_message_status(&self, message: Message) -> Result { @@ -75,9 +75,9 @@ impl<'a> Querier<'a> { Ok(statuses[0].1) } - pub fn get_multisig_session(&self, multisig_session_id: Uint64) -> Result { + pub fn get_multisig_session(&self, multisig_session_id: &Uint64) -> Result { let query_msg = multisig::msg::QueryMsg::GetMultisig { - session_id: multisig_session_id, + session_id: multisig_session_id.clone(), }; query(self.querier, self.config.axelar_multisig_address.to_string(), &query_msg) } diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 931767cde..9e398a170 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -10,7 +10,7 @@ use crate::{ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &String) -> StdResult { let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; - let tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; + let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; if tx_info.status != TransactionStatus::Pending { return Err(ContractError::TransactionStatusNotPending.into()); } @@ -35,9 +35,9 @@ pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, publi pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: &Uint64) -> StdResult { let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; - let tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; + let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; - let multisig_session= querier.get_multisig_session(multisig_session_id.clone())?; + let multisig_session= querier.get_multisig_session(&multisig_session_id)?; let response = match multisig_session.state { MultisigState::Pending => GetProofResponse::Pending { unsigned_tx_hash }, diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 0840d9083..bd37515cf 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -29,7 +29,7 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { MESSAGE_ID_TO_MULTISIG_SESSION_ID.save( deps.storage, - message_id, + &message_id, &multisig_session_id.u64(), )? }, @@ -38,7 +38,7 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result = Map::new("multisig_session_tx" pub const NEXT_SEQUENCE_NUMBER: Item = Item::new("next_sequence_number"); pub const LAST_ASSIGNED_TICKET_NUMBER: Item = Item::new("last_assigned_ticket_number"); -pub const MESSAGE_ID_TO_TICKET: Map = Map::new("message_id_to_ticket"); -pub const MESSAGE_ID_TO_MULTISIG_SESSION_ID: Map = Map::new("message_id_to_multisig_session_id"); -pub const CONFIRMED_TRANSACTIONS: Map = Map::new("confirmed_transactions"); +pub const MESSAGE_ID_TO_TICKET: Map<&CrossChainId, u32> = Map::new("message_id_to_ticket"); +pub const MESSAGE_ID_TO_MULTISIG_SESSION_ID: Map<&CrossChainId, u64> = Map::new("message_id_to_multisig_session_id"); +pub const CONFIRMED_TRANSACTIONS: Map<&u32, TxHash> = Map::new("confirmed_transactions"); pub const AVAILABLE_TICKETS: Item> = Item::new("available_tickets"); -pub const TRANSACTION_INFO: Map = Map::new("transaction_info"); +pub const TRANSACTION_INFO: Map<&TxHash, TransactionInfo> = Map::new("transaction_info"); pub const LATEST_SEQUENTIAL_TX_HASH: Item = Item::new("latest_sequential_tx_hash"); -pub const TOKENS: Map = Map::new("tokens"); +pub const TOKENS: Map<&String, XRPLToken> = Map::new("tokens"); pub const CURRENT_WORKER_SET: Item = Item::new("current_worker_set"); -pub const NEXT_WORKER_SET: Map = Map::new("next_worker_set"); +pub const NEXT_WORKER_SET: Map<&TxHash, WorkerSet> = Map::new("next_worker_set"); diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 1d0d9be2f..8a4068585 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -58,16 +58,16 @@ pub enum XRPLUnsignedTx { } impl XRPLUnsignedTx { - pub fn sequence(&self) -> Sequence { + pub fn sequence(&self) -> &Sequence { match self { XRPLUnsignedTx::Payment(tx) => { - tx.sequence.clone() + &tx.sequence }, XRPLUnsignedTx::TicketCreate(tx) => { - tx.sequence.clone() + &tx.sequence }, XRPLUnsignedTx::SignerListSet(tx) => { - tx.sequence.clone() + &tx.sequence } } } @@ -119,7 +119,6 @@ impl TryFrom<&XRPLMemo> for XRPLObject { type Error = ContractError; fn try_from(memo: &XRPLMemo) -> Result { - println!("memo contents hex {}", hex::encode(memo.0.clone())); let mut obj = XRPLObject::new(); obj.add_field(13, &memo.0)?; Ok(obj) @@ -201,7 +200,7 @@ impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { obj.add_field(4, &XRPLArray{ field_code: 11, items: tx.signer_entries.clone() })?; - let memo_data: Vec = tx.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo_data: Vec = tx.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; @@ -233,7 +232,7 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { obj.add_field(3, &HexBinary::from_hex("")?)?; obj.add_field(1, &XRPLAddress(tx.account.clone()))?; - let memo_data: Vec = tx.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo_data: Vec = tx.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; @@ -241,9 +240,41 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { } } +#[cw_serde] +pub struct XRPLAccountId([u8; 20]); + +impl XRPLSerialize for XRPLAccountId { + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut result: Vec = Vec::new(); + result.extend(vec![20]); // 0x14, length-encoding + result.extend(self.0); + Ok(result) + } +} + +impl XRPLTypedSerialize for XRPLAccountId { + const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; +} + +impl TryFrom<&PublicKey> for XRPLAccountId { + type Error = ContractError; + + fn try_from(pub_key: &PublicKey) -> Result { + Ok(XRPLAccountId(decode_address(&public_key_to_xrpl_address(&pub_key))?)) + } +} + +impl TryFrom<&str> for XRPLAccountId { + type Error = ContractError; + + fn try_from(address: &str) -> Result { + Ok(XRPLAccountId(decode_address(&address)?)) + } +} + #[cw_serde] pub struct XRPLSigner { - pub account: String, + pub account: XRPLAccountId, pub txn_signature: HexBinary, pub signing_pub_key: PublicKey, } @@ -254,6 +285,20 @@ pub struct XRPLSignedTransaction { pub signers: Vec } +impl XRPLSerialize for XRPLSignedTransaction { + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut sorted_signers = self.signers.clone(); + sorted_signers.sort_by(|a, b| { + // the Signers array must be sorted based on the numeric value of the signer addresses + // https://xrpl.org/multi-signing.html#sending-multi-signed-transactions + a.account.0.cmp(&b.account.0) + }); + let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; + obj.add_field(3, &XRPLArray{ field_code: 16, items: sorted_signers })?; + obj.xrpl_serialize() + } +} + pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { let last_assigned_ticket_number: u32 = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; // TODO: handle no available tickets @@ -357,7 +402,7 @@ pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { Ok(buffer) } -pub fn decode_address(address: &String) -> Result<[u8; 20], ContractError> { +pub fn decode_address(address: &str) -> Result<[u8; 20], ContractError> { let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); // .map_err(|_| ContractError::InvalidAddress)?; if res.len() != 25 { @@ -375,15 +420,11 @@ pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result) -> Result { - let tx_hash_hex: HexBinary = HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())); - let tx_hash: TxHash = TxHash(tx_hash_hex.clone()); - Ok(tx_hash) + Ok(TxHash(HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())))) } pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_xrpl_address: &String) -> Result<[u8; 32], ContractError> { @@ -394,6 +435,19 @@ pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_xrpl_address: &St pub struct XRPLAddress(String); +impl XRPLSerialize for XRPLAddress { + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut result: Vec = Vec::new(); + result.extend(vec![20]); // 0x14, length-encoding + result.extend(decode_address(&self.0)?); + Ok(result) + } +} + +impl XRPLTypedSerialize for XRPLAddress { + const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; +} + pub trait XRPLSerialize { fn xrpl_serialize(&self) -> Result, ContractError>; } @@ -447,22 +501,6 @@ impl XRPLTypedSerialize for XRPLPaymentAmount { const TYPE_CODE: u8 = AMOUNT_TYPE_CODE; } -impl XRPLSerialize for XRPLSignedTransaction { - fn xrpl_serialize(self: &XRPLSignedTransaction) -> Result, ContractError> { - let mut sorted_signers = self.signers.clone(); - sorted_signers.sort_by(|a, b| { - // the Signers array must be sorted based on the numeric value of the signer addresses - // https://xrpl.org/multi-signing.html#sending-multi-signed-transactions - let a = bs58::decode(a.account.clone()).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); - let b = bs58::decode(b.account.clone()).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); - return a.cmp(&b); - }); - let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; - obj.add_field(3, &XRPLArray{ field_code: 16, items: sorted_signers })?; - obj.xrpl_serialize() - } -} - impl XRPLSerialize for XRPLUnsignedTx { fn xrpl_serialize(self: &XRPLUnsignedTx) -> Result, ContractError> { XRPLObject::try_from(self)?.xrpl_serialize() @@ -549,7 +587,7 @@ impl XRPLSerialize for XRPLSigner { let mut obj = XRPLObject::new(); obj.add_field(3, &self.signing_pub_key)?; obj.add_field(4, &self.txn_signature)?; - obj.add_field(1, &XRPLAddress(self.account.clone()))?; + obj.add_field(1, &self.account)?; let mut result = obj.xrpl_serialize()?; result.extend(field_id(OBJECT_TYPE_CODE, 1)); Ok(result) @@ -560,20 +598,6 @@ impl XRPLTypedSerialize for XRPLSigner { const TYPE_CODE: u8 = OBJECT_TYPE_CODE; } -impl XRPLSerialize for XRPLAddress { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut result: Vec = Vec::new(); - result.extend(vec![20]); // 0x14, length-encoding - result.extend(decode_address(&self.0)?); - Ok(result) - } -} - -impl XRPLTypedSerialize for XRPLAddress { - const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; -} - - impl XRPLSerialize for XRPLSignerEntry { fn xrpl_serialize(&self) -> Result, ContractError> { let mut obj = XRPLObject::new(); @@ -658,7 +682,7 @@ fn issue_tx( TRANSACTION_INFO.save( storage, - tx_hash.clone(), + &tx_hash, &TransactionInfo { status: TransactionStatus::Pending, unsigned_contents: tx.clone(), @@ -682,25 +706,25 @@ pub fn issue_payment( storage: &mut dyn Storage, config: &Config, destination: nonempty::String, - amount: XRPLPaymentAmount, - message_id: CrossChainId, - multisig_session_id: Uint64, + amount: &XRPLPaymentAmount, + message_id: &CrossChainId, + multisig_session_id: &Uint64, ) -> Result { - let ticket_number = assign_ticket_number(storage, message_id.clone())?; + let ticket_number = assign_ticket_number(storage, message_id)?; let tx = XRPLPaymentTx { account: config.xrpl_multisig_address.to_string(), fee: config.xrpl_fee, sequence: Sequence::Ticket(ticket_number), - multisig_session_id, - amount, + multisig_session_id: multisig_session_id.clone(), + amount: amount.clone(), destination }; issue_tx( storage, XRPLUnsignedTx::Payment(tx), - Some(message_id), + Some(message_id.clone()), ) } @@ -791,7 +815,7 @@ pub fn account_id_bytes_to_address(account_id: &[u8]) -> String { fn get_next_sequence_number(storage: &dyn Storage) -> Result { match load_latest_sequential_tx_info(storage)? { Some(latest_sequential_tx_info) if latest_sequential_tx_info.status == TransactionStatus::Pending => { - Ok(latest_sequential_tx_info.unsigned_contents.sequence().into()) + Ok(latest_sequential_tx_info.unsigned_contents.sequence().clone().into()) }, _ => NEXT_SEQUENCE_NUMBER.load(storage).map_err(|e| e.into()) } @@ -800,21 +824,15 @@ fn get_next_sequence_number(storage: &dyn Storage) -> Result fn load_latest_sequential_tx_info( storage: &dyn Storage, ) -> Result, ContractError> { - let latest_sequential_tx_hash = LATEST_SEQUENTIAL_TX_HASH.may_load(storage)?; - if latest_sequential_tx_hash.is_none() { - return Ok(None) - } - - Ok(TRANSACTION_INFO.may_load(storage, latest_sequential_tx_hash.unwrap())?) + LATEST_SEQUENTIAL_TX_HASH + .may_load(storage)? + .map_or(Ok(None), |tx_hash| Ok(TRANSACTION_INFO.may_load(storage, &tx_hash)?)) } fn mark_tickets_available(storage: &mut dyn Storage, tickets: impl Iterator) -> Result<(), ContractError> { AVAILABLE_TICKETS.update(storage, |available_tickets| -> Result<_, ContractError> { let mut new_available_tickets = available_tickets.clone(); - for i in tickets { - new_available_tickets.push(i); - } - + new_available_tickets.extend(tickets); Ok(new_available_tickets) })?; Ok(()) @@ -834,7 +852,6 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul let xrpl_signers: Vec = axelar_signers .iter() .map(|(axelar_signer, signature)| -> Result { - let xrpl_address = public_key_to_xrpl_address(&axelar_signer.pub_key); let txn_signature = match signature { // TODO: use unwrapped signature instead of ignoring it multisig::key::Signature::Ecdsa(_) | @@ -846,7 +863,7 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul }; Ok(XRPLSigner { - account: xrpl_address, + account: XRPLAccountId::try_from(&axelar_signer.pub_key)?, signing_pub_key: axelar_signer.pub_key.clone().into(), txn_signature, }) @@ -865,7 +882,7 @@ pub fn update_tx_status( unsigned_tx_hash: TxHash, new_status: TransactionStatus ) -> Result, ContractError> { - let mut tx_info = TRANSACTION_INFO.load(storage, unsigned_tx_hash.clone())?; + let mut tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; if tx_info.status != TransactionStatus::Pending { return Err(ContractError::TransactionStatusAlreadyUpdated); } @@ -880,17 +897,17 @@ pub fn update_tx_status( } if new_status == TransactionStatus::Succeeded || new_status == TransactionStatus::FailedOnChain { - CONFIRMED_TRANSACTIONS.save(storage, tx_sequence_number, &unsigned_tx_hash)?; + CONFIRMED_TRANSACTIONS.save(storage, &tx_sequence_number, &unsigned_tx_hash)?; mark_ticket_unavailable(storage, tx_sequence_number)?; } - TRANSACTION_INFO.save(storage, unsigned_tx_hash.clone(), &tx_info)?; + TRANSACTION_INFO.save(storage, &unsigned_tx_hash, &tx_info)?; if tx_info.status != TransactionStatus::Succeeded { return Ok(None); } - let res = match tx_info.unsigned_contents.clone() { + let res = match &tx_info.unsigned_contents { XRPLUnsignedTx::TicketCreate(tx) => { mark_tickets_available( storage, @@ -899,9 +916,9 @@ pub fn update_tx_status( None }, XRPLUnsignedTx::SignerListSet(_tx) => { - let next_worker_set = NEXT_WORKER_SET.load(storage, unsigned_tx_hash.clone())?; + let next_worker_set = NEXT_WORKER_SET.load(storage, &unsigned_tx_hash)?; CURRENT_WORKER_SET.save(storage, &next_worker_set)?; - NEXT_WORKER_SET.remove(storage, unsigned_tx_hash); + NEXT_WORKER_SET.remove(storage, &unsigned_tx_hash); let msg = wasm_execute( axelar_multisig_address, @@ -921,20 +938,16 @@ pub fn update_tx_status( // A message ID can be ticketed a different ticket number // only if the previous ticket number has been consumed // by a TX that doesn't correspond to this message. -pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: CrossChainId) -> Result { +pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId) -> Result { // If this message ID has already been ticketed, // then use the same ticket number as before, - if let Some(ticket_number) = MESSAGE_ID_TO_TICKET.may_load(storage, message_id.clone())? { + if let Some(ticket_number) = MESSAGE_ID_TO_TICKET.may_load(storage, &message_id)? { + let confirmed_tx_hash = CONFIRMED_TRANSACTIONS.may_load(storage, &ticket_number)?; // as long as it has not already been consumed - let confirmed_tx_hash = CONFIRMED_TRANSACTIONS.may_load(storage, ticket_number)?; - if confirmed_tx_hash.is_none() { - return Ok(ticket_number) - } - + if confirmed_tx_hash.is_none() // or if it has been consumed by the same message. - let tx_info = TRANSACTION_INFO.load(storage, confirmed_tx_hash.unwrap())?; - if tx_info.message_id.map_or(false, |id| id == message_id) { - return Ok(ticket_number) + || TRANSACTION_INFO.load(storage, &confirmed_tx_hash.unwrap())?.message_id.as_ref() == Some(message_id) { + return Ok(ticket_number); } } @@ -1238,12 +1251,12 @@ mod tests { multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), } @@ -1268,12 +1281,12 @@ mod tests { multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), }, XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), }, @@ -1298,12 +1311,12 @@ mod tests { multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ - account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".to_string(), + account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ed25519, HexBinary::from(hex::decode("45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f").unwrap()))).unwrap(), }, XRPLSigner{ - account: "rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5".to_string(), + account: "rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("76CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E0106").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ed25519, HexBinary::from(hex::decode("dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b").unwrap()))).unwrap(), }, @@ -1328,12 +1341,12 @@ mod tests { multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("3045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C58").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), }, @@ -1367,12 +1380,12 @@ mod tests { multisig_session_id: Uint64::from(5461264u64) }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("3045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B39").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into().unwrap(), txn_signature: HexBinary::from(hex::decode("3044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A").unwrap()), signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), }, From c8fdb54e3d6b69639531ff30bf6c5a015823d5d3 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Sat, 17 Feb 2024 12:23:01 +0100 Subject: [PATCH 49/80] xrpl: remove bigdecimal/bigint dependency --- Cargo.lock | 14 ---- contracts/xrpl-multisig-prover/Cargo.toml | 1 - contracts/xrpl-multisig-prover/src/querier.rs | 2 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 76 +++++++++++++++---- 4 files changed, 63 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf3a79ef1..5a8f752d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -854,19 +854,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "bigdecimal" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06619be423ea5bb86c95f087d5707942791a08a85530df0db2209a3ecfb8bc9" -dependencies = [ - "autocfg", - "libm", - "num-bigint 0.4.4", - "num-integer", - "num-traits", -] - [[package]] name = "bincode" version = "1.3.3" @@ -9295,7 +9282,6 @@ dependencies = [ "ascii", "axelar-wasm-std", "axelar-wasm-std-derive", - "bigdecimal", "bs58 0.5.0", "connection-router", "cosmwasm-schema", diff --git a/contracts/xrpl-multisig-prover/Cargo.toml b/contracts/xrpl-multisig-prover/Cargo.toml index 6e8658d63..541369602 100644 --- a/contracts/xrpl-multisig-prover/Cargo.toml +++ b/contracts/xrpl-multisig-prover/Cargo.toml @@ -49,7 +49,6 @@ ripemd = "0.1.3" hex = { version = "0.4.3", default-features = false, features = [] } voting-verifier = { workspace = true, features = ["library"] } k256 = { version = "0.13.1", features = ["ecdsa"] } -bigdecimal = "0.4.2" ascii = { version = "1.1.0", features = ["serde"] } [dev-dependencies] diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index cb947590b..b39d6d9c3 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -63,7 +63,7 @@ impl<'a> Querier<'a> { message_ids: vec![message_id.clone()], } )?; - Ok(messages[0]) + messages.first().cloned().ok_or(ContractError::InvalidMessageID(message_id.id.to_string())) } pub fn get_message_status(&self, message: Message) -> Result { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 8a4068585..1dca6b7ff 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,15 +1,14 @@ -use std::collections::BTreeSet; +use std::{collections::BTreeSet, ops::MulAssign}; -use axelar_wasm_std::{nonempty, FnExt}; +use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; +use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint256, Uint64, WasmMsg}; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use multisig::key::PublicKey; use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; -use bigdecimal::{BigDecimal, Signed, ToPrimitive, Zero}; use std::str::FromStr; use crate::{ @@ -349,19 +348,17 @@ const MAX_EXPONENT: i64 = 80; // see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 pub fn amount_to_bytes(amount: &XRPLTokenAmount) -> Result, ContractError> { - let decimal = BigDecimal::from_str(amount.0.trim()).map_err(|e| { ContractError::InvalidAmount { amount: amount.clone().0, reason: e.to_string() } })?; - - let is_negative = decimal.is_negative(); - + let (sign, mut mantissa, mut exponent) = parse_decimal(amount.0.trim())?; + let mut serial: u64 = 0x8000000000000000; - if decimal.is_zero() { + if mantissa.is_zero() { return Ok(Vec::from(serial.to_be_bytes())) } - let (mut mantissa, mut exponent) = decimal.into_bigint_and_exponent().then(|(m, e)| (m.abs().to_biguint().unwrap(), e * -1)); + let ten = Uint256::from_u128(10u128); while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { - mantissa *= 10u8; + mantissa *= ten; exponent -= 1; } @@ -369,7 +366,7 @@ pub fn amount_to_bytes(amount: &XRPLTokenAmount) -> Result, ContractErro if exponent > MAX_EXPONENT { return Err(ContractError::InvalidAmount { amount: amount.clone().0, reason: "overflow 1".to_string() }); } - mantissa /= 10u8; + mantissa /= ten; exponent += 1; } @@ -381,17 +378,68 @@ pub fn amount_to_bytes(amount: &XRPLTokenAmount) -> Result, ContractErro return Err(ContractError::InvalidAmount { amount: amount.clone().0, reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); } - if !is_negative { + if sign == Sign::Plus { serial |= 0x4000000000000000; // set positive bit } serial |= ((exponent+97) as u64) << 54; // next 8 bits are exponent - serial |= mantissa.to_u64().unwrap(); // last 54 bits are mantissa + serial |= u64::from_be_bytes(mantissa.to_be_bytes()[24..].try_into().unwrap()); // last 54 bits are mantissa Ok(Vec::from(serial.to_be_bytes())) } +#[derive(Debug, PartialEq)] +enum Sign { + Plus, + Minus +} + +fn parse_decimal(s: &str) -> Result<(Sign, Uint256, i64), ContractError> { + let exp_separator: &[_] = &['e', 'E']; + + let (base_part, exponent_value) = match s.find(exp_separator) { + None => (s, 0), + Some(loc) => { + let (base, exp) = (&s[..loc], &s[loc + 1..]); + (base, i64::from_str(exp).map_err(|e| ContractError::InvalidAmount { reason: "invalid exponent".to_string(), amount: s.to_string() })?) + } + }; + + if base_part.is_empty() { + return Err(ContractError::InvalidAmount { reason: "base part empty".to_string(), amount: s.to_string()}); + } + + let (digits, decimal_offset): (String, _) = match base_part.find('.') { + None => (base_part.to_string(), 0), + Some(loc) => { + let (lead, trail) = (&base_part[..loc], &base_part[loc + 1..]); + let mut digits = String::from(lead); + digits.push_str(trail); + let trail_digits = trail.chars().filter(|c| *c != '_').count(); + (digits, trail_digits as i64) + } + }; + + let exponent = match decimal_offset.checked_sub(exponent_value) { + Some(exponent) => exponent, + None => { + return Err(ContractError::InvalidAmount { reason: "overflow".to_string(), amount: s.to_string() }); + } + }; + + let (sign, digits) = if digits.starts_with('-') { + (Sign::Minus, &digits[1..]) + } else if digits.starts_with('+') { + (Sign::Plus, &digits[1..]) + } else { + (Sign::Plus, digits.as_str()) + }; + + let mantissa = Uint256::from_str(digits).map_err(|e| ContractError::InvalidAmount { reason: e.to_string(), amount: s.to_string()})?; + + Ok((sign, mantissa, exponent*-1)) +} pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { if currency.len() != 3 || !currency.is_ascii() || currency == "XRP" { From dc90b25d46c5e3f35f1c3cfc125e7b27aa7d0f3b Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Sat, 17 Feb 2024 16:50:08 +0100 Subject: [PATCH 50/80] xrpl: custom decimal type --- .../xrpl-multisig-prover/src/contract.rs | 4 +- contracts/xrpl-multisig-prover/src/error.rs | 4 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 222 ++++++++---------- 3 files changed, 108 insertions(+), 122 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 5a54dbd22..5afeaecac 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -183,13 +183,13 @@ fn construct_payment_proof( let message = querier.get_message(&message_id)?; let xrpl_payment_amount = if coin.denom == config.xrp_denom { // TODO: handle decimal precision conversion - let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { amount: coin.amount.to_string(), reason: "overflow".to_string() })?; + let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { reason: "overflow".to_string() })?; XRPLPaymentAmount::Drops(drops) } else { let xrpl_token = TOKENS.load(storage, &coin.denom)?; XRPLPaymentAmount::Token( xrpl_token, - XRPLTokenAmount(coin.amount.to_string()), + coin.amount.to_string().try_into()?, ) }; diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 16dac4af7..a7c2bd363 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -8,8 +8,8 @@ pub enum ContractError { #[error(transparent)] Std(#[from] StdError), - #[error("invalid amount {amount}: {reason}")] - InvalidAmount { reason: String, amount: String }, + #[error("invalid amount: {reason}")] + InvalidAmount { reason: String }, #[error("serialization failed")] SerializationFailed, diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 1dca6b7ff..727088791 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -25,7 +25,7 @@ pub enum XRPLPaymentAmount { Drops( u64, ), - Token(XRPLToken, XRPLTokenAmount), + Token(XRPLToken, Decimal), } #[cw_serde] @@ -345,100 +345,116 @@ const MAX_MANTISSA: u64 = 10_000_000_000_000_000 - 1; const MIN_EXPONENT: i64 = -96; const MAX_EXPONENT: i64 = 80; +// Decimal always in canonicalized XRPL mantissa-exponent format, +// such that MIN_MANTISSA <= mantissa <= MAX_MANTISSA, MIN_EXPONENT <= exponent <= MAX_EXPONENT, +// unless value is considered zero. +#[cw_serde] +pub struct Decimal { + mantissa: Uint256, + exponent: i64 +} -// see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 -pub fn amount_to_bytes(amount: &XRPLTokenAmount) -> Result, ContractError> { - let (sign, mut mantissa, mut exponent) = parse_decimal(amount.0.trim())?; - - let mut serial: u64 = 0x8000000000000000; - if mantissa.is_zero() { - return Ok(Vec::from(serial.to_be_bytes())) +impl Decimal { + pub fn is_zero(&self) -> bool { + return self.mantissa.is_zero() || self.exponent < MIN_EXPONENT || self.mantissa < MIN_MANTISSA.into(); } - let ten = Uint256::from_u128(10u128); + // always called when Decimal instantiated + // see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 + pub fn canonicalize(&mut self) -> Result<(), ContractError> { + let ten = Uint256::from_u128(10u128); - while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { - mantissa *= ten; - exponent -= 1; - } + while self.mantissa < MIN_MANTISSA.into() && self.exponent > MIN_EXPONENT { + self.mantissa *= ten; + self.exponent -= 1; + } - while mantissa > MAX_MANTISSA.into() && exponent > MIN_EXPONENT { - if exponent > MAX_EXPONENT { - return Err(ContractError::InvalidAmount { amount: amount.clone().0, reason: "overflow 1".to_string() }); + while self.mantissa > MAX_MANTISSA.into() && self.exponent > MIN_EXPONENT { + if self.exponent > MAX_EXPONENT { + return Err(ContractError::InvalidAmount { reason: "overflow 1".to_string() }); + } + self.mantissa /= ten; + self.exponent += 1; } - mantissa /= ten; - exponent += 1; - } - if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA.into() { - return Ok(Vec::from(serial.to_be_bytes())); - } + if self.exponent < MIN_EXPONENT || self.mantissa < MIN_MANTISSA.into() { + return Ok(()); + } - if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA.into() { - return Err(ContractError::InvalidAmount { amount: amount.clone().0, reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); + if self.exponent > MAX_EXPONENT || self.mantissa > MAX_MANTISSA.into() { + return Err(ContractError::InvalidAmount { reason: format!("overflow exponent {} mantissa {}", self.exponent, self.mantissa).to_string() }); + } + + return Ok(()); } - if sign == Sign::Plus { + pub fn to_bytes(&self) -> [u8; 8] { + let mut serial: u64 = 0x8000000000000000; + + if self.clone().is_zero() { + return serial.to_be_bytes(); + } + serial |= 0x4000000000000000; // set positive bit - } - serial |= ((exponent+97) as u64) << 54; // next 8 bits are exponent + serial |= ((self.exponent+97) as u64) << 54; // next 8 bits are exponent - serial |= u64::from_be_bytes(mantissa.to_be_bytes()[24..].try_into().unwrap()); // last 54 bits are mantissa + serial |= u64::from_be_bytes(self.mantissa.to_be_bytes()[24..].try_into().unwrap()); // last 54 bits are mantissa - Ok(Vec::from(serial.to_be_bytes())) + serial.to_be_bytes() + } } -#[derive(Debug, PartialEq)] -enum Sign { - Plus, - Minus -} +impl TryFrom for Decimal { + type Error = ContractError; + + fn try_from(s: String) -> Result { + let exp_separator: &[_] = &['e', 'E']; -fn parse_decimal(s: &str) -> Result<(Sign, Uint256, i64), ContractError> { - let exp_separator: &[_] = &['e', 'E']; + let (base_part, exponent_value) = match s.find(exp_separator) { + None => (s.as_str(), 0), + Some(loc) => { + let (base, exp) = (&s[..loc], &s[loc + 1..]); + (base, i64::from_str(exp).map_err(|e| ContractError::InvalidAmount { reason: "invalid exponent".to_string() })?) + } + }; - let (base_part, exponent_value) = match s.find(exp_separator) { - None => (s, 0), - Some(loc) => { - let (base, exp) = (&s[..loc], &s[loc + 1..]); - (base, i64::from_str(exp).map_err(|e| ContractError::InvalidAmount { reason: "invalid exponent".to_string(), amount: s.to_string() })?) + if base_part.is_empty() { + return Err(ContractError::InvalidAmount { reason: "base part empty".to_string() }); } - }; - if base_part.is_empty() { - return Err(ContractError::InvalidAmount { reason: "base part empty".to_string(), amount: s.to_string()}); - } + let (mut digits, decimal_offset): (String, _) = match base_part.find('.') { + None => (base_part.to_string(), 0), + Some(loc) => { + let (lead, trail) = (&base_part[..loc], &base_part[loc + 1..]); + let mut digits = String::from(lead); + digits.push_str(trail); + let trail_digits = trail.chars().filter(|c| *c != '_').count(); + (digits, trail_digits as i64) + } + }; - let (digits, decimal_offset): (String, _) = match base_part.find('.') { - None => (base_part.to_string(), 0), - Some(loc) => { - let (lead, trail) = (&base_part[..loc], &base_part[loc + 1..]); - let mut digits = String::from(lead); - digits.push_str(trail); - let trail_digits = trail.chars().filter(|c| *c != '_').count(); - (digits, trail_digits as i64) - } - }; + let exponent = match decimal_offset.checked_sub(exponent_value) { + Some(exponent) => exponent, + None => { + return Err(ContractError::InvalidAmount { reason: "overflow".to_string() }); + } + }; - let exponent = match decimal_offset.checked_sub(exponent_value) { - Some(exponent) => exponent, - None => { - return Err(ContractError::InvalidAmount { reason: "overflow".to_string(), amount: s.to_string() }); + if digits.starts_with('-') { + return Err(ContractError::InvalidAmount { reason: "negative amount".to_string() }); } - }; - let (sign, digits) = if digits.starts_with('-') { - (Sign::Minus, &digits[1..]) - } else if digits.starts_with('+') { - (Sign::Plus, &digits[1..]) - } else { - (Sign::Plus, digits.as_str()) - }; + if digits.starts_with('+') { + digits = digits[1..].to_string(); + } - let mantissa = Uint256::from_str(digits).map_err(|e| ContractError::InvalidAmount { reason: e.to_string(), amount: s.to_string()})?; + let mantissa = Uint256::from_str(digits.as_str()).map_err(|e| ContractError::InvalidAmount { reason: e.to_string() })?; - Ok((sign, mantissa, exponent*-1)) + let mut d = Decimal { mantissa, exponent: exponent * -1 }; + d.canonicalize()?; + Ok(d) + } } pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { @@ -531,12 +547,12 @@ impl XRPLSerialize for XRPLPaymentAmount { if *value <= 10u64.pow(17) { Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) } else { - Err(ContractError::InvalidAmount { reason: "more than maximum amount of drops".to_string(), amount: value.to_string() }) + Err(ContractError::InvalidAmount { reason: "more than maximum amount of drops".to_string() }) } }, XRPLPaymentAmount::Token(token, amount) => { let mut result = Vec::new(); - result.extend(amount_to_bytes(amount)?); + result.extend(amount.to_bytes()); result.extend(currency_to_bytes(&token.currency)?); result.extend(decode_address(&token.issuer)?); Ok(result) @@ -1049,7 +1065,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, XRPLTokenAmount("0".to_string())) + }, "0".to_string().try_into()?) .xrpl_serialize()? ); assert_hex_eq!( @@ -1057,34 +1073,18 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, XRPLTokenAmount("1".to_string())) - .xrpl_serialize()? - ); - // minimum amount - assert_hex_eq!( - "AC6386F26FC0FFFF00000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, XRPLTokenAmount("-9999999999999999e80".to_string())) + }, "1".to_string().try_into()?) .xrpl_serialize()? ); - // less than minimum amount fails - assert!( - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, XRPLTokenAmount("-10000000000000000e80".to_string())) - .xrpl_serialize() - .is_err() - ); + // negative amount fails + assert!(Decimal::try_from("-1".to_string()).is_err()); // minimum absolute amount assert_hex_eq!( "C0438D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, XRPLTokenAmount("1000000000000000e-96".to_string())) + }, "1000000000000000e-96".to_string().try_into()?) .xrpl_serialize()? ); // less than minimum absolute positive amount serializes to 0 @@ -1093,16 +1093,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, XRPLTokenAmount("999999999999999e-96".to_string())) - .xrpl_serialize()? - ); - // less than minimum absolute negative amount serializes to 0 - assert_hex_eq!( - "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, XRPLTokenAmount("-999999999999999e-96".to_string())) + }, "999999999999999e-96".to_string().try_into()?) .xrpl_serialize()? ); // maximum amount @@ -1111,25 +1102,18 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, XRPLTokenAmount("9999999999999999e80".to_string())) + }, "9999999999999999e80".to_string().try_into()?) .xrpl_serialize()? ); // more than maximum amount fails - assert!( - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, XRPLTokenAmount("10000000000000000e80".to_string())) - .xrpl_serialize() - .is_err() - ); + assert!(Decimal::try_from("10000000000000000e80".to_string()).is_err()); // test integer and fractional part with zeroes assert_hex_eq!( "D58462510B02ED1500000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, XRPLTokenAmount("0012340.0012345678900".to_string())) + }, "0012340.0012345678900".to_string().try_into()?) .xrpl_serialize()? ); // currency can contain non-alphanumeric ascii letters @@ -1138,7 +1122,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "${;".to_string(), - }, XRPLTokenAmount("42".to_string())) + }, "42".to_string().try_into()?) .xrpl_serialize()? ); // TODO: these could be enforced on a type level: @@ -1150,7 +1134,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "XRP".to_string(), - }, XRPLTokenAmount("42".to_string())) + }, "42".to_string().try_into()?) .xrpl_serialize() .is_err() ); @@ -1234,7 +1218,7 @@ mod tests { } #[test] - fn serialize_xrpl_unsigned_token_payment_transaction() { + fn serialize_xrpl_unsigned_token_payment_transaction() -> Result<(), ContractError> { let unsigned_tx = XRPLPaymentTx { account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), fee: 12, @@ -1244,7 +1228,7 @@ mod tests { currency: "JPY".to_string(), issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".to_string(), }, - XRPLTokenAmount("0.3369568318".to_string()), + "0.3369568318".to_string().try_into()?, ), destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), multisig_session_id: Uint64::from(1u8), @@ -1254,6 +1238,7 @@ mod tests { "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D0101E1F1", hex::encode_upper(encoded_unsigned_tx) ); + Ok(()) } #[test] @@ -1348,13 +1333,13 @@ mod tests { } #[test] - fn tx_serialization_ed25519_signers() { + fn tx_serialization_ed25519_signers() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(), fee: 30, sequence: Sequence::Ticket(45205896), - amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string() }, XRPLTokenAmount("100000000".to_string())), + amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string() }, "100000000".to_string().try_into()?), destination: nonempty::String::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo").unwrap(), multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ @@ -1375,6 +1360,7 @@ mod tests { "12000022000000002400000000202902B1C98861D6838D7EA4C680000000000000000000000000004554480000000000EC792533BC26024CFAA5DDC2D04128E59581309C68400000000000001E73008114EC792533BC26024CFAA5DDC2D04128E59581309C831439659AAAD4DC8603798352FCF954419A67977536F3E0107321EDDD9822C7FA239DDA9913EBEE813ECBE69E35D88FF651548D5CC42C033A8A667B744076CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E010681142B3CF7B1986F5CB4EFEF11F933F40EC3106412C2E1E0107321ED45E67EAF446E6C26EB3A2B55B64339ECF3A4D1D03180BEE20EB5AFDD23FA644F7440531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09811472C14C0DB6CEF64A87CC3D152D7B0E917D372BE7E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); + Ok(()) } From e1f231fb503304a13461c36dab9ef03cd9b894dc Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Sat, 17 Feb 2024 17:48:05 +0100 Subject: [PATCH 51/80] xrpl: token amount type instead of decimal --- .../xrpl-multisig-prover/src/contract.rs | 2 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 181 ++++++------------ 2 files changed, 56 insertions(+), 127 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 5afeaecac..1a91f1647 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -189,7 +189,7 @@ fn construct_payment_proof( let xrpl_token = TOKENS.load(storage, &coin.denom)?; XRPLPaymentAmount::Token( xrpl_token, - coin.amount.to_string().try_into()?, + coin.amount.try_into()?, ) }; diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 727088791..5f92f96ce 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,15 +1,14 @@ -use std::{collections::BTreeSet, ops::MulAssign}; +use std::collections::BTreeSet; use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint256, Uint64, WasmMsg}; +use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint128, Uint256, Uint64, WasmMsg}; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use multisig::key::PublicKey; use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; -use std::str::FromStr; use crate::{ error::ContractError, @@ -17,15 +16,12 @@ use crate::{ types::*, axelar_workers::{WorkerSet, AxelarSigner}, }; -#[cw_serde] -pub struct XRPLTokenAmount(pub String); - #[cw_serde] pub enum XRPLPaymentAmount { Drops( u64, ), - Token(XRPLToken, Decimal), + Token(XRPLToken, XRPLTokenAmount), } #[cw_serde] @@ -345,116 +341,71 @@ const MAX_MANTISSA: u64 = 10_000_000_000_000_000 - 1; const MIN_EXPONENT: i64 = -96; const MAX_EXPONENT: i64 = 80; -// Decimal always in canonicalized XRPL mantissa-exponent format, -// such that MIN_MANTISSA <= mantissa <= MAX_MANTISSA, MIN_EXPONENT <= exponent <= MAX_EXPONENT, -// unless value is considered zero. +// XRPLTokenAmount always in canonicalized XRPL mantissa-exponent format, +// such that MIN_MANTISSA <= mantissa <= MAX_MANTISSA (or equal to zero), MIN_EXPONENT <= exponent <= MAX_EXPONENT, +// In XRPL generally it can be decimal and even negative (!) but in our case that doesn't apply. #[cw_serde] -pub struct Decimal { - mantissa: Uint256, +pub struct XRPLTokenAmount { + mantissa: u64, exponent: i64 } -impl Decimal { - pub fn is_zero(&self) -> bool { - return self.mantissa.is_zero() || self.exponent < MIN_EXPONENT || self.mantissa < MIN_MANTISSA.into(); - } - - // always called when Decimal instantiated - // see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 - pub fn canonicalize(&mut self) -> Result<(), ContractError> { - let ten = Uint256::from_u128(10u128); - - while self.mantissa < MIN_MANTISSA.into() && self.exponent > MIN_EXPONENT { - self.mantissa *= ten; - self.exponent -= 1; - } - - while self.mantissa > MAX_MANTISSA.into() && self.exponent > MIN_EXPONENT { - if self.exponent > MAX_EXPONENT { - return Err(ContractError::InvalidAmount { reason: "overflow 1".to_string() }); - } - self.mantissa /= ten; - self.exponent += 1; - } - - if self.exponent < MIN_EXPONENT || self.mantissa < MIN_MANTISSA.into() { - return Ok(()); - } - - if self.exponent > MAX_EXPONENT || self.mantissa > MAX_MANTISSA.into() { - return Err(ContractError::InvalidAmount { reason: format!("overflow exponent {} mantissa {}", self.exponent, self.mantissa).to_string() }); - } - - return Ok(()); +impl XRPLTokenAmount { + pub fn new(mantissa: u64, exponent: i64) -> Self { + assert!(mantissa == 0 || (MIN_MANTISSA <= mantissa && mantissa <= MAX_MANTISSA && MIN_EXPONENT <= exponent && exponent <= MAX_EXPONENT)); + Self { mantissa, exponent } } pub fn to_bytes(&self) -> [u8; 8] { - let mut serial: u64 = 0x8000000000000000; - - if self.clone().is_zero() { - return serial.to_be_bytes(); + if self.mantissa == 0 { + 0x8000000000000000u64.to_be_bytes() + } else { + // not xrp-bit | positive bit | 8 bits exponent | 54 bits mantissa + (0xC000000000000000u64 | ((self.exponent + 97) as u64) << 54 | self.mantissa).to_be_bytes() } - - serial |= 0x4000000000000000; // set positive bit - - serial |= ((self.exponent+97) as u64) << 54; // next 8 bits are exponent - - serial |= u64::from_be_bytes(self.mantissa.to_be_bytes()[24..].try_into().unwrap()); // last 54 bits are mantissa - - serial.to_be_bytes() } } -impl TryFrom for Decimal { +impl TryFrom for XRPLTokenAmount { type Error = ContractError; - fn try_from(s: String) -> Result { - let exp_separator: &[_] = &['e', 'E']; - - let (base_part, exponent_value) = match s.find(exp_separator) { - None => (s.as_str(), 0), - Some(loc) => { - let (base, exp) = (&s[..loc], &s[loc + 1..]); - (base, i64::from_str(exp).map_err(|e| ContractError::InvalidAmount { reason: "invalid exponent".to_string() })?) - } - }; - - if base_part.is_empty() { - return Err(ContractError::InvalidAmount { reason: "base part empty".to_string() }); - } + fn try_from(amount: Uint128) -> Result { + let (mantissa, exponent) = canonicalize_mantissa(amount)?; + Ok(XRPLTokenAmount::new(mantissa, exponent)) + } +} - let (mut digits, decimal_offset): (String, _) = match base_part.find('.') { - None => (base_part.to_string(), 0), - Some(loc) => { - let (lead, trail) = (&base_part[..loc], &base_part[loc + 1..]); - let mut digits = String::from(lead); - digits.push_str(trail); - let trail_digits = trail.chars().filter(|c| *c != '_').count(); - (digits, trail_digits as i64) - } - }; +// always called when XRPLTokenAmount instantiated +// see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 +pub fn canonicalize_mantissa(mut mantissa: Uint128) -> Result<(u64, i64), ContractError> { + let mut exponent = 0i64; - let exponent = match decimal_offset.checked_sub(exponent_value) { - Some(exponent) => exponent, - None => { - return Err(ContractError::InvalidAmount { reason: "overflow".to_string() }); - } - }; + let ten = Uint128::from(10u128); - if digits.starts_with('-') { - return Err(ContractError::InvalidAmount { reason: "negative amount".to_string() }); - } + while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { + mantissa *= ten; + exponent -= 1; + } - if digits.starts_with('+') { - digits = digits[1..].to_string(); + while mantissa > MAX_MANTISSA.into() && exponent > MIN_EXPONENT { + if exponent > MAX_EXPONENT { + return Err(ContractError::InvalidAmount { reason: "overflow".to_string() }); } + mantissa /= ten; + exponent += 1; + } - let mantissa = Uint256::from_str(digits.as_str()).map_err(|e| ContractError::InvalidAmount { reason: e.to_string() })?; + if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA.into() { + return Ok((0, 1)); + } - let mut d = Decimal { mantissa, exponent: exponent * -1 }; - d.canonicalize()?; - Ok(d) + if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA.into() { + return Err(ContractError::InvalidAmount { reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); } + + let mantissa = u64::from_be_bytes(mantissa.to_be_bytes()[8..].try_into().unwrap()); + + return Ok((mantissa, exponent)); } pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { @@ -1065,7 +1016,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, "0".to_string().try_into()?) + }, Uint128::zero().try_into()?) .xrpl_serialize()? ); assert_hex_eq!( @@ -1073,27 +1024,16 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, "1".to_string().try_into()?) + }, Uint128::from(1u128).try_into()?) .xrpl_serialize()? ); - // negative amount fails - assert!(Decimal::try_from("-1".to_string()).is_err()); // minimum absolute amount assert_hex_eq!( "C0438D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, "1000000000000000e-96".to_string().try_into()?) - .xrpl_serialize()? - ); - // less than minimum absolute positive amount serializes to 0 - assert_hex_eq!( - "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, "999999999999999e-96".to_string().try_into()?) + }, XRPLTokenAmount { mantissa: MIN_MANTISSA, exponent: MIN_EXPONENT }) .xrpl_serialize()? ); // maximum amount @@ -1102,18 +1042,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "USD".to_string(), - }, "9999999999999999e80".to_string().try_into()?) - .xrpl_serialize()? - ); - // more than maximum amount fails - assert!(Decimal::try_from("10000000000000000e80".to_string()).is_err()); - // test integer and fractional part with zeroes - assert_hex_eq!( - "D58462510B02ED1500000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, "0012340.0012345678900".to_string().try_into()?) + }, XRPLTokenAmount { mantissa: MAX_MANTISSA, exponent: MAX_EXPONENT }) .xrpl_serialize()? ); // currency can contain non-alphanumeric ascii letters @@ -1122,7 +1051,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "${;".to_string(), - }, "42".to_string().try_into()?) + }, Uint128::from(42u128).try_into()?) .xrpl_serialize()? ); // TODO: these could be enforced on a type level: @@ -1134,7 +1063,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), currency: "XRP".to_string(), - }, "42".to_string().try_into()?) + }, Uint128::from(42u128).try_into()?) .xrpl_serialize() .is_err() ); @@ -1228,7 +1157,7 @@ mod tests { currency: "JPY".to_string(), issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".to_string(), }, - "0.3369568318".to_string().try_into()?, + XRPLTokenAmount { mantissa: 3369568318000000u64, exponent: -16 } ), destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), multisig_session_id: Uint64::from(1u8), @@ -1339,7 +1268,7 @@ mod tests { account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(), fee: 30, sequence: Sequence::Ticket(45205896), - amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string() }, "100000000".to_string().try_into()?), + amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string() }, Uint128::from(100000000u128).try_into()?), destination: nonempty::String::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo").unwrap(), multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ From 58a22dc01f4ebadb4ed27f4af0e84804cc582f4b Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Sun, 18 Feb 2024 14:51:37 +0100 Subject: [PATCH 52/80] xrpl: accountid type for addresses --- ampd/src/handlers/xrpl_multisig.rs | 4 +- .../src/axelar_workers.rs | 7 +- .../xrpl-multisig-prover/src/contract.rs | 13 +- contracts/xrpl-multisig-prover/src/query.rs | 8 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 345 +++++++++--------- integration-tests/tests/test_utils/mod.rs | 4 +- 6 files changed, 188 insertions(+), 193 deletions(-) diff --git a/ampd/src/handlers/xrpl_multisig.rs b/ampd/src/handlers/xrpl_multisig.rs index 7707689fb..32d032e91 100644 --- a/ampd/src/handlers/xrpl_multisig.rs +++ b/ampd/src/handlers/xrpl_multisig.rs @@ -16,6 +16,7 @@ use events::Error::EventTypeMismatch; use events_derive; use events_derive::try_from; use multisig::msg::ExecuteMsg; +use xrpl_multisig_prover::xrpl_multisig::XRPLAccountId; use crate::event_processor::EventHandler; use crate::handlers::errors::Error::{self, DeserializeEvent}; @@ -24,7 +25,6 @@ use crate::tofnd::grpc::SharableEcdsaClient; use crate::tofnd::MessageDigest; use crate::types::PublicKey; use crate::types::TMAddress; -use xrpl_multisig_prover::xrpl_multisig::public_key_to_xrpl_address; #[derive(Debug, Deserialize)] #[try_from("wasm-signing_started")] @@ -168,7 +168,7 @@ where Some(pub_key) => { let pub_key_hex = HexBinary::from(pub_key.to_bytes()); let multisig_pub_key = multisig::key::PublicKey::try_from((multisig::key::KeyType::Ecdsa, pub_key_hex)).map_err(|_e| Error::PublicKey)?; - let xrpl_address = public_key_to_xrpl_address(&multisig_pub_key); + let xrpl_address = XRPLAccountId::from(&multisig_pub_key); let msg_digest = MessageDigest::from(xrpl_multisig_prover::xrpl_multisig::message_to_sign(&unsigned_tx, &xrpl_address).map_err(|_e| Error::MessageToSign)?); diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index 50e8fab5b..1b47fe56d 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -101,10 +101,9 @@ pub fn get_active_worker_set( let workers: Vec = querier.get_active_workers()?; let participants: Vec = workers - .iter() - .map(|worker| Participant::try_from(worker.clone())) - .filter(|result| result.is_ok()) - .map(|result| result.unwrap()) + .into_iter() + .map(|worker| Participant::try_from(worker)) + .filter_map(|result| result.ok()) .collect(); let weights = convert_or_scale_weights(participants diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 1a91f1647..5ba5e61d5 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -54,7 +54,7 @@ pub fn instantiate( let service_registry_address = deps.api.addr_validate(&msg.service_registry_address)?; let governance_address = deps.api.addr_validate(&msg.governance_address)?; - if msg.signing_threshold.numerator() > u32::MAX.into() { + if msg.signing_threshold.numerator() > u32::MAX.into() || msg.signing_threshold.denominator() == Uint64::zero() { return Err(ContractError::InvalidSigningThreshold.into()); } @@ -318,13 +318,16 @@ fn update_tx_status( let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; let multisig_session = querier.get_multisig_session(&multisig_session_id)?; + + let destination_str = match &tx_info.unsigned_contents { + xrpl_multisig::XRPLUnsignedTx::Payment(p) => p.destination.to_string(), + _ => xrpl_multisig_address.to_string(), + }; + let message = Message { destination_chain: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), source_address: Address::from_str(&xrpl_multisig_address).map_err(|_| ContractError::InvalidAddress)?, - destination_address: Address::from_str(match &tx_info.unsigned_contents { - xrpl_multisig::XRPLUnsignedTx::Payment(p) => p.destination.as_str(), - _ => &xrpl_multisig_address, - }).map_err(|_| ContractError::InvalidAddress)?, + destination_address: Address::from_str(destination_str.as_ref()).map_err(|_| ContractError::InvalidAddress)?, cc_id: message_id.clone(), payload_hash: [0; 32], }; diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 9e398a170..1cffa66c8 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -3,11 +3,12 @@ use cosmwasm_std::{StdResult, Uint64, HexBinary, Storage}; use multisig::{key::Signature, types::MultisigState}; use multisig::key::PublicKey; +use crate::xrpl_multisig::XRPLAccountId; use crate::{ state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, XRPLSerialize, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, }; -pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &String) -> StdResult { +pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &XRPLAccountId) -> StdResult { let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; @@ -16,15 +17,14 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, } let serialized_unsigned_tx = tx_info.unsigned_contents.xrpl_serialize()?; - let serialized_signer_xrpl_address = xrpl_multisig::decode_address(signer_xrpl_address)?; - let serialized_tx = &[serialized_unsigned_tx, serialized_signer_xrpl_address.to_vec()].concat(); + let serialized_tx = &[serialized_unsigned_tx, signer_xrpl_address.to_bytes().to_vec()].concat(); Ok(xrpl_multisig::xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, serialized_tx).into()) } pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: &PublicKey, signature: &Signature) -> StdResult { - let signer_xrpl_address = xrpl_multisig::public_key_to_xrpl_address(&public_key); + let signer_xrpl_address = XRPLAccountId::from(public_key); let tx_hash = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; // m.tx_hash is going to be over 32 bytes due to inclusion of the signer address, so it has to be passed unchecked diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 5f92f96ce..eb9f5a8c1 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -41,8 +41,23 @@ impl Into for Sequence { #[cw_serde] pub struct XRPLSignerEntry { - pub account: String, + pub account: XRPLAccountId, pub signer_weight: u16, +} + +impl XRPLSerialize for XRPLSignerEntry { + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut obj = XRPLObject::new(); + obj.add_field(1, &self.account)?; + obj.add_field(3, &self.signer_weight)?; + let mut result = obj.xrpl_serialize()?; + result.extend(field_id(OBJECT_TYPE_CODE, 1)); + Ok(result) + } +} + +impl XRPLTypedSerialize for XRPLSignerEntry { + const TYPE_CODE: u8 = OBJECT_TYPE_CODE; } #[cw_serde] @@ -137,11 +152,11 @@ impl XRPLTypedSerialize for XRPLMemo { #[cw_serde] pub struct XRPLPaymentTx { - pub account: String, + pub account: XRPLAccountId, pub fee: u64, pub sequence: Sequence, pub amount: XRPLPaymentAmount, - pub destination: nonempty::String, + pub destination: XRPLAccountId, pub multisig_session_id: Uint64 } @@ -158,8 +173,8 @@ impl TryFrom<&XRPLPaymentTx> for XRPLObject { // type: Amount, type_code: 6, nth: 8, !isVLEncoded obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(1, &XRPLAddress(tx.account.clone()))?; - obj.add_field(3, &XRPLAddress(tx.destination.to_string()))?; + obj.add_field(1, &tx.account)?; + obj.add_field(3, &tx.destination)?; let memo_data: Vec = tx.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; @@ -171,7 +186,7 @@ impl TryFrom<&XRPLPaymentTx> for XRPLObject { #[cw_serde] pub struct XRPLSignerListSetTx { - pub account: String, + pub account: XRPLAccountId, pub fee: u64, pub sequence: Sequence, pub signer_quorum: u32, @@ -190,7 +205,7 @@ impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { add_sequence(&mut obj, &tx.sequence)?; obj.add_field(35, &tx.signer_quorum)?; obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; - obj.add_field(1, &XRPLAddress(tx.account.clone()))?; + obj.add_field(1, &tx.account)?; obj.add_field(3, &HexBinary::from_hex("")?)?; obj.add_field(4, &XRPLArray{ field_code: 11, items: tx.signer_entries.clone() })?; @@ -206,7 +221,7 @@ impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { #[cw_serde] pub struct XRPLTicketCreateTx { - pub account: String, + pub account: XRPLAccountId, pub fee: u64, pub sequence: Sequence, pub ticket_count: u32, @@ -225,7 +240,7 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { obj.add_field(40, &tx.ticket_count)?; // 202800000000a obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; // 68400000000000001e obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(1, &XRPLAddress(tx.account.clone()))?; + obj.add_field(1, &tx.account)?; let memo_data: Vec = tx.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; @@ -238,6 +253,25 @@ impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { #[cw_serde] pub struct XRPLAccountId([u8; 20]); +impl XRPLAccountId { + pub fn to_bytes(&self) -> [u8; 20] { + return self.0; + } + + pub fn to_string(&self) -> String { + let address_type_prefix: &[u8] = &[0x00]; + let payload = [address_type_prefix, &self.to_bytes()].concat(); + + let checksum_hash1 = Sha256::digest(payload.clone()); + let checksum_hash2 = Sha256::digest(checksum_hash1); + let checksum = &checksum_hash2[0..4]; + + bs58::encode([payload, checksum.to_vec()].concat()) + .with_alphabet(bs58::Alphabet::RIPPLE) + .into_string() + } +} + impl XRPLSerialize for XRPLAccountId { fn xrpl_serialize(&self) -> Result, ContractError> { let mut result: Vec = Vec::new(); @@ -251,11 +285,16 @@ impl XRPLTypedSerialize for XRPLAccountId { const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; } -impl TryFrom<&PublicKey> for XRPLAccountId { - type Error = ContractError; +impl From<&PublicKey> for XRPLAccountId { + fn from(pub_key: &PublicKey) -> Self { + let public_key_hex: HexBinary = pub_key.clone().into(); + + assert!(public_key_hex.len() == 33); + + let public_key_inner_hash = Sha256::digest(public_key_hex); + let account_id = Ripemd160::digest(public_key_inner_hash); - fn try_from(pub_key: &PublicKey) -> Result { - Ok(XRPLAccountId(decode_address(&public_key_to_xrpl_address(&pub_key))?)) + return XRPLAccountId(account_id.into()); } } @@ -263,7 +302,7 @@ impl TryFrom<&str> for XRPLAccountId { type Error = ContractError; fn try_from(address: &str) -> Result { - Ok(XRPLAccountId(decode_address(&address)?)) + Ok(XRPLAccountId(decode_address(address)?)) } } @@ -418,7 +457,7 @@ pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { } pub fn decode_address(address: &str) -> Result<[u8; 20], ContractError> { - let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().unwrap(); + let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().map_err(|_| ContractError::InvalidAddress)?; // .map_err(|_| ContractError::InvalidAddress)?; if res.len() != 25 { return Err(ContractError::InvalidAddress); @@ -442,27 +481,11 @@ pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result Result<[u8; 32], ContractError> { - let serialized_signer_xrpl_address = decode_address(&signer_xrpl_address)?; - let msg = &[encoded_unsigned_tx.to_vec(), serialized_signer_xrpl_address.into()].concat(); +pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_address: &XRPLAccountId) -> Result<[u8; 32], ContractError> { + let msg = &[encoded_unsigned_tx.to_vec(), signer_address.to_bytes().into()].concat(); Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)) } -pub struct XRPLAddress(String); - -impl XRPLSerialize for XRPLAddress { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut result: Vec = Vec::new(); - result.extend(vec![20]); // 0x14, length-encoding - result.extend(decode_address(&self.0)?); - Ok(result) - } -} - -impl XRPLTypedSerialize for XRPLAddress { - const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; -} - pub trait XRPLSerialize { fn xrpl_serialize(&self) -> Result, ContractError>; } @@ -545,36 +568,32 @@ impl XRPLTypedSerialize for XRPLArray { // see https://github.com/XRPLF/xrpl-dev-portal/blob/master/content/_code-samples/tx-serialization/py/serialize.py#L92 -// returns None if length too big -pub fn encode_length(mut length: usize) -> Option> { +// may error if length too big +pub fn encode_length(mut length: usize) -> Result, ContractError> { if length <= 192 { - return Some(vec![length as u8]); + Ok(vec![length as u8]) } else if length <= 12480 { length -= 193; - return Some(vec![193 + (length >> 8) as u8, (length & 0xff) as u8]); + Ok(vec![193 + (length >> 8) as u8, (length & 0xff) as u8]) } else if length <= 918744 { length -= 12481; - return Some(vec![ + Ok(vec![ 241 + (length >> 16) as u8, ((length >> 8) & 0xff) as u8, (length & 0xff) as u8 ]) } else { - return None + Err(ContractError::InvalidBlob) } } impl XRPLSerialize for HexBinary { fn xrpl_serialize(&self) -> Result, ContractError> { - match encode_length(self.len()) { - Some(encoded_length) => { - let mut result = Vec::new(); - result.extend(encoded_length); - result.extend(self.to_vec()); - Ok(result) - } - None => Err(ContractError::InvalidBlob) - } + let encoded_length = encode_length(self.len())?; + let mut result = Vec::new(); + result.extend(encoded_length); + result.extend(self.to_vec()); + Ok(result) } } @@ -613,21 +632,6 @@ impl XRPLTypedSerialize for XRPLSigner { const TYPE_CODE: u8 = OBJECT_TYPE_CODE; } -impl XRPLSerialize for XRPLSignerEntry { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut obj = XRPLObject::new(); - obj.add_field(1, &XRPLAddress(self.account.clone()))?; - obj.add_field(3, &self.signer_weight)?; - let mut result = obj.xrpl_serialize()?; - result.extend(field_id(OBJECT_TYPE_CODE, 1)); - Ok(result) - } -} - -impl XRPLTypedSerialize for XRPLSignerEntry { - const TYPE_CODE: u8 = OBJECT_TYPE_CODE; -} - #[derive(Debug, Clone)] pub struct XRPLObject { @@ -728,12 +732,12 @@ pub fn issue_payment( let ticket_number = assign_ticket_number(storage, message_id)?; let tx = XRPLPaymentTx { - account: config.xrpl_multisig_address.to_string(), + account: config.xrpl_multisig_address.as_str().try_into()?, fee: config.xrpl_fee, sequence: Sequence::Ticket(ticket_number), multisig_session_id: multisig_session_id.clone(), amount: amount.clone(), - destination + destination: XRPLAccountId::try_from(destination.as_str())? }; issue_tx( @@ -752,7 +756,7 @@ pub fn issue_ticket_create( let sequence_number = get_next_sequence_number(storage)?; let tx = XRPLTicketCreateTx { - account: config.xrpl_multisig_address.to_string(), + account: config.xrpl_multisig_address.as_str().try_into()?, fee: config.xrpl_fee, sequence: Sequence::Plain(sequence_number.clone()), ticket_count, @@ -775,11 +779,11 @@ pub fn issue_signer_list_set( let sequence_number = get_next_sequence_number(storage)?; let tx = XRPLSignerListSetTx { - account: config.xrpl_multisig_address.to_string(), + account: config.xrpl_multisig_address.as_str().try_into()?, fee: config.xrpl_fee, sequence: Sequence::Plain(sequence_number.clone()), signer_quorum: workers.quorum, - signer_entries: make_xrpl_signer_entries(workers.signers), + signer_entries: make_xrpl_signer_entries(workers.signers)?, multisig_session_id, }; @@ -790,42 +794,19 @@ pub fn issue_signer_list_set( ) } -fn make_xrpl_signer_entries(signers: BTreeSet) -> Vec { +fn make_xrpl_signer_entries(signers: BTreeSet) -> Result, ContractError> { signers .into_iter() .map( - |worker| { - XRPLSignerEntry { - account: public_key_to_xrpl_address(&worker.pub_key), + |worker| -> Result { + Ok(XRPLSignerEntry { + account: XRPLAccountId::from(&worker.pub_key), signer_weight: worker.weight, - } + }) } ).collect() } -pub fn public_key_to_xrpl_address(public_key: &multisig::key::PublicKey) -> String { - let public_key_hex: HexBinary = public_key.clone().into(); - - assert!(public_key_hex.len() == 33); - - let public_key_inner_hash = Sha256::digest(public_key_hex); - let account_id = Ripemd160::digest(public_key_inner_hash); - return account_id_bytes_to_address(&account_id); -} - -pub fn account_id_bytes_to_address(account_id: &[u8]) -> String { - let address_type_prefix: &[u8] = &[0x00]; - let payload = [address_type_prefix, &account_id].concat(); - - let checksum_hash1 = Sha256::digest(payload.clone()); - let checksum_hash2 = Sha256::digest(checksum_hash1); - let checksum = &checksum_hash2[0..4]; - - bs58::encode([payload, checksum.to_vec()].concat()) - .with_alphabet(bs58::Alphabet::RIPPLE) - .into_string() -} - fn get_next_sequence_number(storage: &dyn Storage) -> Result { match load_latest_sequential_tx_info(storage)? { @@ -878,7 +859,7 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul }; Ok(XRPLSigner { - account: XRPLAccountId::try_from(&axelar_signer.pub_key)?, + account: XRPLAccountId::from(&axelar_signer.pub_key), signing_pub_key: axelar_signer.pub_key.clone().into(), txn_signature, }) @@ -986,19 +967,20 @@ mod tests { } #[test] - fn test_encode_length() { - assert_hex_eq!("00", encode_length(0).unwrap()); - assert_hex_eq!("0A", encode_length(10).unwrap()); - assert_hex_eq!("C100", encode_length(193).unwrap()); - assert_hex_eq!("F10000", encode_length(12481).unwrap()); - assert_hex_eq!("FED417", encode_length(918744).unwrap()); - assert_eq!(None, encode_length(918745)); + fn test_encode_length() -> Result<(), ContractError> { + assert_hex_eq!("00", encode_length(0)?); + assert_hex_eq!("0A", encode_length(10)?); + assert_hex_eq!("C100", encode_length(193)?); + assert_hex_eq!("F10000", encode_length(12481)?); + assert_hex_eq!("FED417", encode_length(918744)?); + assert!(encode_length(918745).is_err()); + Ok(()) } #[test] fn test_account_id_to_bytes_address() { - assert_eq!("rrrrrrrrrrrrrrrrrrrrrhoLvTp", account_id_bytes_to_address(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); - assert_eq!("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi", account_id_bytes_to_address(&[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255])); + assert_eq!("rrrrrrrrrrrrrrrrrrrrrhoLvTp", XRPLAccountId([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).to_string()); + assert_eq!("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi", XRPLAccountId([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]).to_string()); } #[test] @@ -1009,8 +991,8 @@ mod tests { assert_hex_eq!("00000000", 0u32.xrpl_serialize()?); assert_hex_eq!("00000005", 5u32.xrpl_serialize()?); assert_hex_eq!("FFFFFFFF", 0xffffffffu32.xrpl_serialize()?); - assert_hex_eq!("00", HexBinary::from_hex("").unwrap().xrpl_serialize()?); - assert_hex_eq!("04DEADBEEF", HexBinary::from_hex("DEADBEEF").unwrap().xrpl_serialize()?); + assert_hex_eq!("00", HexBinary::from_hex("")?.xrpl_serialize()?); + assert_hex_eq!("04DEADBEEF", HexBinary::from_hex("DEADBEEF")?.xrpl_serialize()?); assert_hex_eq!( "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { @@ -1098,25 +1080,25 @@ mod tests { // account "0" (with length prefix) assert_hex_eq!( "140000000000000000000000000000000000000000", - XRPLAddress("rrrrrrrrrrrrrrrrrrrrrhoLvTp".to_string()) + XRPLAccountId::try_from("rrrrrrrrrrrrrrrrrrrrrhoLvTp")? .xrpl_serialize()? ); // account "1" (with length prefix) assert_hex_eq!( "140000000000000000000000000000000000000001", - XRPLAddress("rrrrrrrrrrrrrrrrrrrrBZbvji".to_string()) + XRPLAccountId::try_from("rrrrrrrrrrrrrrrrrrrrBZbvji")? .xrpl_serialize()? ); // max acccount assert_hex_eq!( "14FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - XRPLAddress("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi".to_string()) + XRPLAccountId::try_from("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi")? .xrpl_serialize()? ); assert_hex_eq!( "13000081140000000000000000000000000000000000000000E1", XRPLSignerEntry{ - account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".to_string(), + account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, signer_weight: 0 }.xrpl_serialize()? ); @@ -1137,7 +1119,7 @@ mod tests { "EA13000081140000000000000000000000000000000000000000E1F1", XRPLArray::{ field_code: 10, items: vec![ XRPLSignerEntry{ - account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".to_string(), + account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, signer_weight: 0 }, ] } @@ -1149,7 +1131,7 @@ mod tests { #[test] fn serialize_xrpl_unsigned_token_payment_transaction() -> Result<(), ContractError> { let unsigned_tx = XRPLPaymentTx { - account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, fee: 12, sequence: Sequence::Plain(1), amount: XRPLPaymentAmount::Token( @@ -1159,10 +1141,10 @@ mod tests { }, XRPLTokenAmount { mantissa: 3369568318000000u64, exponent: -16 } ), - destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), + destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, multisig_session_id: Uint64::from(1u8), }; - let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize().unwrap(); + let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize()?; assert_eq!( "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D0101E1F1", hex::encode_upper(encoded_unsigned_tx) @@ -1171,120 +1153,127 @@ mod tests { } #[test] - fn serialize_xrpl_unsigned_xrp_payment_transaction() { + fn serialize_xrpl_unsigned_xrp_payment_transaction() -> Result<(), ContractError> { let tx = XRPLPaymentTx { - account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), + account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, fee: 10, sequence: Sequence::Plain(1), amount: XRPLPaymentAmount::Drops(1000), - destination: nonempty::String::try_from("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").unwrap(), + destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, multisig_session_id: Uint64::from(0u8), }; - let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); + let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; assert_eq!( "120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D00E1F1", hex::encode_upper(encoded_unsigned_tx) ); let tx = XRPLPaymentTx { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".to_string(), + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".try_into()?, fee: 3, sequence: Sequence::Plain(43497363), amount: XRPLPaymentAmount::Drops(1000000000), - destination: nonempty::String::try_from("rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq").unwrap(), + destination: "rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq".try_into()?, multisig_session_id: Uint64::from(1337u16), }; - let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize().unwrap(); + let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; assert_eq!( "1200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6F9EA7D020539E1F1", hex::encode_upper(encoded_unsigned_tx) ); + Ok(()) + } + + fn pub_key_from_hex(hex: &str) -> Result { + Ok(PublicKey::Ecdsa(HexBinary::from_hex(hex)?)) } #[test] - fn serialize_xrpl_signed_xrp_payment_transaction() { + fn serialize_xrpl_signed_xrp_payment_transaction() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, fee: 30, sequence: Sequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), - destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), + destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + txn_signature: HexBinary::from_hex("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045")?, + signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + txn_signature: HexBinary::from_hex("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9")?, + signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? } ], }; - let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); + let encoded_signed_tx = &signed_tx.xrpl_serialize()?; assert_eq!( "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); + Ok(()) } #[test] - fn tx_serialization_sort_signers() { + fn tx_serialization_sort_signers() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, fee: 30, sequence: Sequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), - destination: nonempty::String::try_from("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG").unwrap(), + destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + txn_signature: HexBinary::from_hex("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9")?, + signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? }, XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + txn_signature: HexBinary::from_hex("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045")?, + signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? }, ], }; - let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); + let encoded_signed_tx = &signed_tx.xrpl_serialize()?; assert_eq!( "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); + Ok(()) } #[test] fn tx_serialization_ed25519_signers() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string(), + account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()?, fee: 30, sequence: Sequence::Ticket(45205896), amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string() }, Uint128::from(100000000u128).try_into()?), - destination: nonempty::String::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo").unwrap(), + destination: "raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".try_into()?, multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ - account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ed25519, HexBinary::from(hex::decode("45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f").unwrap()))).unwrap(), + account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".try_into()?, + txn_signature: HexBinary::from_hex("531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09")?, + signing_pub_key: PublicKey::Ed25519(HexBinary::from_hex("45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f")?) }, XRPLSigner{ - account: "rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("76CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E0106").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ed25519, HexBinary::from(hex::decode("dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b").unwrap()))).unwrap(), + account: "rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5".try_into()?, + txn_signature: HexBinary::from_hex("76CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E0106")?, + signing_pub_key: PublicKey::Ed25519(HexBinary::from_hex("dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b")?) }, ], }; - let encoded_signed_tx = &signed_tx.xrpl_serialize().unwrap(); + let encoded_signed_tx = &signed_tx.xrpl_serialize()?; assert_eq!( "12000022000000002400000000202902B1C98861D6838D7EA4C680000000000000000000000000004554480000000000EC792533BC26024CFAA5DDC2D04128E59581309C68400000000000001E73008114EC792533BC26024CFAA5DDC2D04128E59581309C831439659AAAD4DC8603798352FCF954419A67977536F3E0107321EDDD9822C7FA239DDA9913EBEE813ECBE69E35D88FF651548D5CC42C033A8A667B744076CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E010681142B3CF7B1986F5CB4EFEF11F933F40EC3106412C2E1E0107321ED45E67EAF446E6C26EB3A2B55B64339ECF3A4D1D03180BEE20EB5AFDD23FA644F7440531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09811472C14C0DB6CEF64A87CC3D152D7B0E917D372BE7E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) @@ -1294,86 +1283,90 @@ mod tests { #[test] - fn serialize_xrpl_signed_xrp_ticket_create_transaction() { + fn serialize_xrpl_signed_xrp_ticket_create_transaction() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::TicketCreate(XRPLTicketCreateTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, fee: 30, sequence: Sequence::Plain(44218194), ticket_count: 3, multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + txn_signature: HexBinary::from_hex("304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F")?, + signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("3045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C58").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + txn_signature: HexBinary::from_hex("3045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C58")?, + signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? }, ] }; - let encoded_signed_tx = signed_tx.xrpl_serialize().unwrap(); + let encoded_signed_tx = signed_tx.xrpl_serialize()?; assert_eq!( "12000A22000000002402A2B75220280000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC0088567446304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F8114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C588114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); + Ok(()) } #[test] - fn serialize_xrpl_signed_signer_list_set_transaction() { + fn serialize_xrpl_signed_signer_list_set_transaction() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::SignerListSet(XRPLSignerListSetTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".to_string(), + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, fee: 30, sequence: Sequence::Plain(44218445), signer_quorum: 3, signer_entries: vec![ XRPLSignerEntry{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".to_string(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, signer_weight: 2 }, XRPLSignerEntry{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".to_string(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, signer_weight: 1 } ], multisig_session_id: Uint64::from(5461264u64) }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("3045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B39").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856").unwrap()))).unwrap(), + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + txn_signature: HexBinary::from_hex("3045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B39")?, + signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")?, }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into().unwrap(), - txn_signature: HexBinary::from(hex::decode("3044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A").unwrap()), - signing_pub_key: PublicKey::try_from((KeyType::Ecdsa, HexBinary::from(hex::decode("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE").unwrap()))).unwrap(), + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + txn_signature: HexBinary::from_hex("3044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A")?, + signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")?, }, ], }; - let encoded_signed_tx = signed_tx.xrpl_serialize().unwrap(); + let encoded_signed_tx = signed_tx.xrpl_serialize()?; assert_eq!( "12000C22000000002402A2B84D20230000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674473045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B398114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74463044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A8114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F4EB1300028114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1EB1300018114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", hex::encode_upper(encoded_signed_tx) ); + Ok(()) } #[test] - fn ed25519_public_key_to_xrpl_address() { + fn ed25519_public_key_to_xrpl_address() -> Result<(), ContractError> { assert_eq!( - public_key_to_xrpl_address(&PublicKey::Ed25519(HexBinary::from(hex::decode("ED9434799226374926EDA3B54B1B461B4ABF7237962EAE18528FEA67595397FA32").unwrap()))), + XRPLAccountId::from(&PublicKey::Ed25519(HexBinary::from_hex("ED9434799226374926EDA3B54B1B461B4ABF7237962EAE18528FEA67595397FA32")?)).to_string(), "rDTXLQ7ZKZVKz33zJbHjgVShjsBnqMBhmN" ); + Ok(()) } #[test] - fn secp256k1_public_key_to_xrpl_address() { + fn secp256k1_public_key_to_xrpl_address() -> Result<(), ContractError> { assert_eq!( - public_key_to_xrpl_address(&PublicKey::Ecdsa(HexBinary::from(hex::decode("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D").unwrap()))), + XRPLAccountId::from(&PublicKey::Ecdsa(HexBinary::from_hex("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D")?)).to_string(), "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H" ); + Ok(()) } } diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 8487316bd..0e551c3d3 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -19,7 +19,7 @@ use multisig::{ worker_set::WorkerSet, }; use multisig_prover::encoding::{make_operators, Encoder}; -use xrpl_multisig_prover::types::XRPLToken; +use xrpl_multisig_prover::{types::XRPLToken, xrpl_multisig::XRPLAccountId}; use sha3::{Digest, Keccak256}; use tofn::ecdsa::KeyPair; @@ -294,7 +294,7 @@ pub fn sign_xrpl_proof( .expect("couldn't get unsigned_tx"); for worker in workers { - let xrpl_signer_address = xrpl_multisig_prover::xrpl_multisig::public_key_to_xrpl_address( + let xrpl_signer_address = XRPLAccountId::from( &multisig::key::PublicKey::Ecdsa(worker.key_pair.encoded_verifying_key().into()) ); From 3392a95fc3ab2103894202add2039fe876c5c3eb Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Sun, 18 Feb 2024 16:14:40 +0100 Subject: [PATCH 53/80] xrpl: remove unnecessary imports --- contracts/xrpl-multisig-prover/src/reply.rs | 6 +----- contracts/xrpl-multisig-prover/src/xrpl_multisig.rs | 5 ++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index bd37515cf..a1b294357 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -1,9 +1,5 @@ -use std::collections::HashMap; - -use cosmwasm_std::{from_binary, Attribute, DepsMut, HexBinary, Reply, Response, SubMsgResult, Uint64}; +use cosmwasm_std::{from_binary, Attribute, DepsMut, HexBinary, Reply, Response, Uint64}; use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; -use serde_json::from_str; -use std::str::FromStr; use crate::{ error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, xrpl_multisig::XRPLSerialize diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index eb9f5a8c1..e1fdb0269 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,10 +1,9 @@ use std::collections::BTreeSet; - use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint128, Uint256, Uint64, WasmMsg}; +use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint128, Uint64, WasmMsg}; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use multisig::key::PublicKey; use ripemd::Ripemd160; @@ -1369,4 +1368,4 @@ mod tests { ); Ok(()) } -} +} \ No newline at end of file From 5bf2c821242d7dda63916dccc36ce9dd491ca8f6 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Sun, 18 Feb 2024 19:17:19 +0100 Subject: [PATCH 54/80] xrpl: separate serialization logic to separate module --- .../xrpl-multisig-prover/src/contract.rs | 3 +- contracts/xrpl-multisig-prover/src/lib.rs | 3 +- contracts/xrpl-multisig-prover/src/query.rs | 3 +- contracts/xrpl-multisig-prover/src/reply.rs | 3 +- contracts/xrpl-multisig-prover/src/types.rs | 4 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 865 +----------------- .../src/xrpl_serialize.rs | 821 +++++++++++++++++ integration-tests/tests/test_utils/mod.rs | 2 +- 8 files changed, 841 insertions(+), 863 deletions(-) create mode 100644 contracts/xrpl-multisig-prover/src/xrpl_serialize.rs diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 5ba5e61d5..ac0f57d02 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -17,7 +17,8 @@ use crate::{ msg::{ExecuteMsg, QueryMsg}, reply, types::*, - xrpl_multisig::{self, XRPLPaymentAmount, XRPLTokenAmount, XRPLSerialize}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query, + xrpl_multisig::{self, XRPLPaymentAmount}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query, + xrpl_serialize::XRPLSerialize, }; pub const START_MULTISIG_REPLY_ID: u64 = 1; diff --git a/contracts/xrpl-multisig-prover/src/lib.rs b/contracts/xrpl-multisig-prover/src/lib.rs index 893575092..cc40eed3d 100644 --- a/contracts/xrpl-multisig-prover/src/lib.rs +++ b/contracts/xrpl-multisig-prover/src/lib.rs @@ -8,4 +8,5 @@ pub mod query; pub mod xrpl_multisig; pub mod axelar_workers; pub mod querier; -pub mod msg; \ No newline at end of file +pub mod msg; +pub mod xrpl_serialize; \ No newline at end of file diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 1cffa66c8..294ac6c01 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -5,7 +5,8 @@ use multisig::key::PublicKey; use crate::xrpl_multisig::XRPLAccountId; use crate::{ - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, XRPLSerialize, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, + state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, + xrpl_serialize::XRPLSerialize }; pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &XRPLAccountId) -> StdResult { diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index a1b294357..8e5fcaa24 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -2,7 +2,8 @@ use cosmwasm_std::{from_binary, Attribute, DepsMut, HexBinary, Reply, Response, use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; use crate::{ - error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, xrpl_multisig::XRPLSerialize + error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, + xrpl_serialize::XRPLSerialize }; pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 990f1d622..239d8bda6 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256}; use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; use multisig::key::Signature; -use crate::{xrpl_multisig::XRPLUnsignedTx, error::ContractError}; +use crate::{error::ContractError, xrpl_multisig::{XRPLAccountId, XRPLUnsignedTx}}; #[cw_serde] pub enum TransactionStatus { @@ -122,6 +122,6 @@ impl Operator { #[cw_serde] pub struct XRPLToken { - pub issuer: String, + pub issuer: XRPLAccountId, pub currency: String, } diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index e1fdb0269..b7308c89b 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -10,9 +10,7 @@ use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; use crate::{ - error::ContractError, - state::{Config, LAST_ASSIGNED_TICKET_NUMBER, AVAILABLE_TICKETS, TRANSACTION_INFO, NEXT_SEQUENCE_NUMBER, CONFIRMED_TRANSACTIONS, MESSAGE_ID_TO_TICKET, LATEST_SEQUENTIAL_TX_HASH, NEXT_WORKER_SET, CURRENT_WORKER_SET}, - types::*, axelar_workers::{WorkerSet, AxelarSigner}, + axelar_workers::{AxelarSigner, WorkerSet}, error::ContractError, state::{Config, AVAILABLE_TICKETS, CONFIRMED_TRANSACTIONS, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, LATEST_SEQUENTIAL_TX_HASH, MESSAGE_ID_TO_TICKET, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, TRANSACTION_INFO}, types::*, xrpl_serialize::XRPLTokenAmount }; #[cw_serde] @@ -44,21 +42,6 @@ pub struct XRPLSignerEntry { pub signer_weight: u16, } -impl XRPLSerialize for XRPLSignerEntry { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut obj = XRPLObject::new(); - obj.add_field(1, &self.account)?; - obj.add_field(3, &self.signer_weight)?; - let mut result = obj.xrpl_serialize()?; - result.extend(field_id(OBJECT_TYPE_CODE, 1)); - Ok(result) - } -} - -impl XRPLTypedSerialize for XRPLSignerEntry { - const TYPE_CODE: u8 = OBJECT_TYPE_CODE; -} - #[cw_serde] pub enum XRPLUnsignedTx { Payment(XRPLPaymentTx), @@ -110,45 +93,6 @@ impl XRPLUnsignedTx { } } -impl TryFrom<&XRPLUnsignedTx> for XRPLObject { - type Error = ContractError; - - fn try_from(tx: &XRPLUnsignedTx) -> Result { - match tx { - XRPLUnsignedTx::Payment(tx) => tx.try_into(), - XRPLUnsignedTx::TicketCreate(tx) => tx.try_into(), - XRPLUnsignedTx::SignerListSet(tx) => tx.try_into() - } - } -} - -pub struct XRPLMemo(HexBinary); - -impl TryFrom<&XRPLMemo> for XRPLObject { - type Error = ContractError; - - fn try_from(memo: &XRPLMemo) -> Result { - let mut obj = XRPLObject::new(); - obj.add_field(13, &memo.0)?; - Ok(obj) - } -} - -impl XRPLSerialize for XRPLMemo { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut obj = XRPLObject::new(); - obj.add_field(13, &self.0)?; - let mut result = obj.xrpl_serialize()?; - result.extend(field_id(OBJECT_TYPE_CODE, 1)); - Ok(result) - - } -} - -impl XRPLTypedSerialize for XRPLMemo { - const TYPE_CODE: u8 = OBJECT_TYPE_CODE; -} - #[cw_serde] pub struct XRPLPaymentTx { pub account: XRPLAccountId, @@ -159,30 +103,6 @@ pub struct XRPLPaymentTx { pub multisig_session_id: Uint64 } -impl TryFrom<&XRPLPaymentTx> for XRPLObject { - type Error = ContractError; - - fn try_from(tx: &XRPLPaymentTx) -> Result { - let mut obj = XRPLObject::new(); - obj.add_field(2, &PAYMENT_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags - add_sequence(&mut obj, &tx.sequence)?; - // type: Amount, type_code: 6, nth: 1, !isVLEncoded - obj.add_field(1, &tx.amount)?; - // type: Amount, type_code: 6, nth: 8, !isVLEncoded - obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; - obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(1, &tx.account)?; - obj.add_field(3, &tx.destination)?; - - let memo_data: Vec = tx.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; - - Ok(obj) - } -} - #[cw_serde] pub struct XRPLSignerListSetTx { pub account: XRPLAccountId, @@ -193,31 +113,6 @@ pub struct XRPLSignerListSetTx { pub multisig_session_id: Uint64 } -impl TryFrom<&XRPLSignerListSetTx> for XRPLObject { - type Error = ContractError; - - fn try_from(tx: &XRPLSignerListSetTx) -> Result { - let mut obj = XRPLObject::new(); - - obj.add_field(2, &SIGNER_LIST_SET_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags - add_sequence(&mut obj, &tx.sequence)?; - obj.add_field(35, &tx.signer_quorum)?; - obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; - obj.add_field(1, &tx.account)?; - obj.add_field(3, &HexBinary::from_hex("")?)?; - - obj.add_field(4, &XRPLArray{ field_code: 11, items: tx.signer_entries.clone() })?; - - let memo_data: Vec = tx.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; - - Ok(obj) - } -} - - #[cw_serde] pub struct XRPLTicketCreateTx { pub account: XRPLAccountId, @@ -227,28 +122,6 @@ pub struct XRPLTicketCreateTx { pub multisig_session_id: Uint64 } -impl TryFrom<&XRPLTicketCreateTx> for XRPLObject { - type Error = ContractError; - - fn try_from(tx: &XRPLTicketCreateTx) -> Result { - let mut obj = XRPLObject::new(); - // type_code: 1, nth: 2, !isVLEncoded - obj.add_field(2, &TICKET_CREATE_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags - add_sequence(&mut obj, &tx.sequence)?; - obj.add_field(40, &tx.ticket_count)?; // 202800000000a - obj.add_field(8, &XRPLPaymentAmount::Drops(tx.fee))?; // 68400000000000001e - obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(1, &tx.account)?; - - let memo_data: Vec = tx.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; - - Ok(obj) - } -} - #[cw_serde] pub struct XRPLAccountId([u8; 20]); @@ -271,19 +144,6 @@ impl XRPLAccountId { } } -impl XRPLSerialize for XRPLAccountId { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut result: Vec = Vec::new(); - result.extend(vec![20]); // 0x14, length-encoding - result.extend(self.0); - Ok(result) - } -} - -impl XRPLTypedSerialize for XRPLAccountId { - const TYPE_CODE: u8 = ACCOUNT_ID_TYPE_CODE; -} - impl From<&PublicKey> for XRPLAccountId { fn from(pub_key: &PublicKey) -> Self { let public_key_hex: HexBinary = pub_key.clone().into(); @@ -301,7 +161,14 @@ impl TryFrom<&str> for XRPLAccountId { type Error = ContractError; fn try_from(address: &str) -> Result { - Ok(XRPLAccountId(decode_address(address)?)) + let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().map_err(|_| ContractError::InvalidAddress)?; + // .map_err(|_| ContractError::InvalidAddress)?; + if res.len() != 25 { + return Err(ContractError::InvalidAddress); + } + let mut buffer = [0u8; 20]; + buffer.copy_from_slice(&res[1..21]); + return Ok(XRPLAccountId(buffer)) } } @@ -318,20 +185,6 @@ pub struct XRPLSignedTransaction { pub signers: Vec } -impl XRPLSerialize for XRPLSignedTransaction { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut sorted_signers = self.signers.clone(); - sorted_signers.sort_by(|a, b| { - // the Signers array must be sorted based on the numeric value of the signer addresses - // https://xrpl.org/multi-signing.html#sending-multi-signed-transactions - a.account.0.cmp(&b.account.0) - }); - let mut obj = XRPLObject::try_from(&self.unsigned_tx)?; - obj.add_field(3, &XRPLArray{ field_code: 16, items: sorted_signers })?; - obj.xrpl_serialize() - } -} - pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { let last_assigned_ticket_number: u32 = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; // TODO: handle no available tickets @@ -349,122 +202,6 @@ pub fn available_ticket_count(storage: &mut dyn Storage) -> Result Vec { - assert!(type_code < 16); - if field_code < 16 { - vec![type_code << 4 | field_code] - } else { - vec![type_code << 4, field_code] - } -} - -const POSITIVE_BIT: u64 = 0x4000000000000000; - -const MIN_MANTISSA: u64 = 1_000_000_000_000_000; -const MAX_MANTISSA: u64 = 10_000_000_000_000_000 - 1; -const MIN_EXPONENT: i64 = -96; -const MAX_EXPONENT: i64 = 80; - -// XRPLTokenAmount always in canonicalized XRPL mantissa-exponent format, -// such that MIN_MANTISSA <= mantissa <= MAX_MANTISSA (or equal to zero), MIN_EXPONENT <= exponent <= MAX_EXPONENT, -// In XRPL generally it can be decimal and even negative (!) but in our case that doesn't apply. -#[cw_serde] -pub struct XRPLTokenAmount { - mantissa: u64, - exponent: i64 -} - -impl XRPLTokenAmount { - pub fn new(mantissa: u64, exponent: i64) -> Self { - assert!(mantissa == 0 || (MIN_MANTISSA <= mantissa && mantissa <= MAX_MANTISSA && MIN_EXPONENT <= exponent && exponent <= MAX_EXPONENT)); - Self { mantissa, exponent } - } - - pub fn to_bytes(&self) -> [u8; 8] { - if self.mantissa == 0 { - 0x8000000000000000u64.to_be_bytes() - } else { - // not xrp-bit | positive bit | 8 bits exponent | 54 bits mantissa - (0xC000000000000000u64 | ((self.exponent + 97) as u64) << 54 | self.mantissa).to_be_bytes() - } - } -} - -impl TryFrom for XRPLTokenAmount { - type Error = ContractError; - - fn try_from(amount: Uint128) -> Result { - let (mantissa, exponent) = canonicalize_mantissa(amount)?; - Ok(XRPLTokenAmount::new(mantissa, exponent)) - } -} - -// always called when XRPLTokenAmount instantiated -// see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 -pub fn canonicalize_mantissa(mut mantissa: Uint128) -> Result<(u64, i64), ContractError> { - let mut exponent = 0i64; - - let ten = Uint128::from(10u128); - - while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { - mantissa *= ten; - exponent -= 1; - } - - while mantissa > MAX_MANTISSA.into() && exponent > MIN_EXPONENT { - if exponent > MAX_EXPONENT { - return Err(ContractError::InvalidAmount { reason: "overflow".to_string() }); - } - mantissa /= ten; - exponent += 1; - } - - if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA.into() { - return Ok((0, 1)); - } - - if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA.into() { - return Err(ContractError::InvalidAmount { reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); - } - - let mantissa = u64::from_be_bytes(mantissa.to_be_bytes()[8..].try_into().unwrap()); - - return Ok((mantissa, exponent)); -} - -pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { - if currency.len() != 3 || !currency.is_ascii() || currency == "XRP" { - return Err(ContractError::InvalidCurrency); - } - let mut buffer = [0u8; 20]; - buffer[12..15].copy_from_slice(currency.as_bytes()); - Ok(buffer) -} - -pub fn decode_address(address: &str) -> Result<[u8; 20], ContractError> { - let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().map_err(|_| ContractError::InvalidAddress)?; - // .map_err(|_| ContractError::InvalidAddress)?; - if res.len() != 25 { - return Err(ContractError::InvalidAddress); - } - let mut buffer = [0u8; 20]; - buffer.copy_from_slice(&res[1..21]); - return Ok(buffer) -} pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; pub const HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; @@ -485,200 +222,6 @@ pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_address: &XRPLAcc Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)) } -pub trait XRPLSerialize { - fn xrpl_serialize(&self) -> Result, ContractError>; -} - -pub trait XRPLTypedSerialize: XRPLSerialize { - const TYPE_CODE: u8; -} - -impl XRPLSerialize for u16 { - fn xrpl_serialize(&self) -> Result, ContractError> { - Ok(self.to_be_bytes().to_vec()) - } -} - -impl XRPLTypedSerialize for u16 { - const TYPE_CODE: u8 = UINT16_TYPE_CODE; -} - -impl XRPLSerialize for u32 { - fn xrpl_serialize(&self) -> Result, ContractError> { - Ok(self.to_be_bytes().to_vec()) - } -} - -impl XRPLTypedSerialize for u32 { - const TYPE_CODE: u8 = UINT32_TYPE_CODE; -} - -impl XRPLSerialize for XRPLPaymentAmount { - fn xrpl_serialize(&self) -> Result, ContractError> { - match self { - XRPLPaymentAmount::Drops(value) => { - if *value <= 10u64.pow(17) { - Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) - } else { - Err(ContractError::InvalidAmount { reason: "more than maximum amount of drops".to_string() }) - } - }, - XRPLPaymentAmount::Token(token, amount) => { - let mut result = Vec::new(); - result.extend(amount.to_bytes()); - result.extend(currency_to_bytes(&token.currency)?); - result.extend(decode_address(&token.issuer)?); - Ok(result) - } - } - } -} - -impl XRPLTypedSerialize for XRPLPaymentAmount { - const TYPE_CODE: u8 = AMOUNT_TYPE_CODE; -} - -impl XRPLSerialize for XRPLUnsignedTx { - fn xrpl_serialize(self: &XRPLUnsignedTx) -> Result, ContractError> { - XRPLObject::try_from(self)?.xrpl_serialize() - } -} - -struct XRPLArray { - field_code: u8, - items: Vec -} - -impl XRPLSerialize for XRPLArray { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut result: Vec = Vec::new(); - for item in &self.items { - result.extend(field_id(T::TYPE_CODE, self.field_code)); - result.extend(item.xrpl_serialize()?); - } - result.extend(field_id(ARRAY_TYPE_CODE, 1)); - Ok(result) - } -} - -impl XRPLTypedSerialize for XRPLArray { - const TYPE_CODE: u8 = ARRAY_TYPE_CODE; -} - - -// see https://github.com/XRPLF/xrpl-dev-portal/blob/master/content/_code-samples/tx-serialization/py/serialize.py#L92 -// may error if length too big -pub fn encode_length(mut length: usize) -> Result, ContractError> { - if length <= 192 { - Ok(vec![length as u8]) - } else if length <= 12480 { - length -= 193; - Ok(vec![193 + (length >> 8) as u8, (length & 0xff) as u8]) - } else if length <= 918744 { - length -= 12481; - Ok(vec![ - 241 + (length >> 16) as u8, - ((length >> 8) & 0xff) as u8, - (length & 0xff) as u8 - ]) - } else { - Err(ContractError::InvalidBlob) - } -} - -impl XRPLSerialize for HexBinary { - fn xrpl_serialize(&self) -> Result, ContractError> { - let encoded_length = encode_length(self.len())?; - let mut result = Vec::new(); - result.extend(encoded_length); - result.extend(self.to_vec()); - Ok(result) - } -} - -impl XRPLTypedSerialize for HexBinary { - const TYPE_CODE: u8 = BLOB_TYPE_CODE; -} - -impl XRPLSerialize for PublicKey { - fn xrpl_serialize(&self) -> Result, ContractError> { - match self.clone() { - // rippled prefixes Ed25519 public keys with the byte 0xED so both types of public key are 33 bytes. - // https://xrpl.org/cryptographic-keys.html - Self::Ed25519(hex) => HexBinary::from_hex(format!("ED{}", hex.to_hex()).as_str())?.xrpl_serialize(), - Self::Ecdsa(hex) => hex.xrpl_serialize(), - } - } -} - -impl XRPLTypedSerialize for PublicKey { - const TYPE_CODE: u8 = BLOB_TYPE_CODE; -} - -impl XRPLSerialize for XRPLSigner { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut obj = XRPLObject::new(); - obj.add_field(3, &self.signing_pub_key)?; - obj.add_field(4, &self.txn_signature)?; - obj.add_field(1, &self.account)?; - let mut result = obj.xrpl_serialize()?; - result.extend(field_id(OBJECT_TYPE_CODE, 1)); - Ok(result) - } -} - -impl XRPLTypedSerialize for XRPLSigner { - const TYPE_CODE: u8 = OBJECT_TYPE_CODE; -} - - -#[derive(Debug, Clone)] -pub struct XRPLObject { - fields: Vec<(u8, u8, Vec)> -} - -impl XRPLObject { - pub fn new() -> XRPLObject { - Self { - fields: Vec::new() - } - } - - pub fn add_field(&mut self, field_code: u8, value: &T) -> Result<(), ContractError> { - self.fields.push((T::TYPE_CODE, field_code, value.xrpl_serialize()?)); - Ok(()) - } -} - -pub fn add_sequence(obj: &mut XRPLObject, sequence: &Sequence) -> Result<(), ContractError> { - match sequence { - Sequence::Plain(seq) => { - obj.add_field(4, seq) - }, - Sequence::Ticket(seq) => { - obj.add_field(4, &0u32)?; - obj.add_field(41, seq) - } - } -} - -impl XRPLSerialize for XRPLObject { - fn xrpl_serialize(&self) -> Result, ContractError> { - let mut tmp: Vec<(u8, u8, Vec)> = self.fields.clone(); - tmp.sort_by(|a, b| { (a.0, a.1).cmp(&(b.0, b.1)) }); - let res = tmp.into_iter() - .map(|f| { - let mut res = Vec::new(); - res.extend(field_id(f.0, f.1)); - res.extend(f.2); - return res; - }) - .collect::>>() - .concat(); - Ok(res) - } -} - pub fn xrpl_hash( prefix: [u8; 4], tx_blob: &[u8], @@ -954,403 +497,13 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId #[cfg(test)] mod tests { - use multisig::key::{KeyType, PublicKey}; - use super::*; - #[macro_export] - macro_rules! assert_hex_eq { - ($expected:expr, $actual:expr) => { - assert_eq!($expected, hex::encode_upper($actual)); - }; - } - - #[test] - fn test_encode_length() -> Result<(), ContractError> { - assert_hex_eq!("00", encode_length(0)?); - assert_hex_eq!("0A", encode_length(10)?); - assert_hex_eq!("C100", encode_length(193)?); - assert_hex_eq!("F10000", encode_length(12481)?); - assert_hex_eq!("FED417", encode_length(918744)?); - assert!(encode_length(918745).is_err()); - Ok(()) - } - #[test] fn test_account_id_to_bytes_address() { assert_eq!("rrrrrrrrrrrrrrrrrrrrrhoLvTp", XRPLAccountId([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).to_string()); assert_eq!("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi", XRPLAccountId([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]).to_string()); } - - #[test] - fn test_xrpl_serialize() -> Result<(), ContractError> { - assert_hex_eq!("0000", 0u16.xrpl_serialize()?); - assert_hex_eq!("0001", 1u16.xrpl_serialize()?); - assert_hex_eq!("FFFF", 0xffffu16.xrpl_serialize()?); - assert_hex_eq!("00000000", 0u32.xrpl_serialize()?); - assert_hex_eq!("00000005", 5u32.xrpl_serialize()?); - assert_hex_eq!("FFFFFFFF", 0xffffffffu32.xrpl_serialize()?); - assert_hex_eq!("00", HexBinary::from_hex("")?.xrpl_serialize()?); - assert_hex_eq!("04DEADBEEF", HexBinary::from_hex("DEADBEEF")?.xrpl_serialize()?); - assert_hex_eq!( - "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, Uint128::zero().try_into()?) - .xrpl_serialize()? - ); - assert_hex_eq!( - "D4838D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, Uint128::from(1u128).try_into()?) - .xrpl_serialize()? - ); - // minimum absolute amount - assert_hex_eq!( - "C0438D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, XRPLTokenAmount { mantissa: MIN_MANTISSA, exponent: MIN_EXPONENT }) - .xrpl_serialize()? - ); - // maximum amount - assert_hex_eq!( - "EC6386F26FC0FFFF00000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "USD".to_string(), - }, XRPLTokenAmount { mantissa: MAX_MANTISSA, exponent: MAX_EXPONENT }) - .xrpl_serialize()? - ); - // currency can contain non-alphanumeric ascii letters - assert_hex_eq!( - "D4CEEBE0B40E8000000000000000000000000000247B3B00000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "${;".to_string(), - }, Uint128::from(42u128).try_into()?) - .xrpl_serialize()? - ); - // TODO: these could be enforced on a type level: - // - currency cannot contain non-ascii letters - // - currency must not be more than 3 ascii letters - // - currency must not be less than 3 ascii letters - // XRP currency code is not allowed - assert!( - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".to_string(), - currency: "XRP".to_string(), - }, Uint128::from(42u128).try_into()?) - .xrpl_serialize() - .is_err() - ); - // minimum XRP - assert_hex_eq!( - "4000000000000000", - XRPLPaymentAmount::Drops(0) - .xrpl_serialize()? - ); - assert_hex_eq!( - "4000000000000001", - XRPLPaymentAmount::Drops(1) - .xrpl_serialize()? - ); - assert_hex_eq!( - "40000000499602D2", - XRPLPaymentAmount::Drops(1234567890) - .xrpl_serialize()? - ); - // maximum XRP - assert_hex_eq!( - "416345785D8A0000", - XRPLPaymentAmount::Drops(100_000_000_000_000_000) - .xrpl_serialize()? - ); - // more than maximum XRP fails - assert!( - XRPLPaymentAmount::Drops(100_000_000_000_000_001) - .xrpl_serialize() - .is_err() - ); - // account "0" (with length prefix) - assert_hex_eq!( - "140000000000000000000000000000000000000000", - XRPLAccountId::try_from("rrrrrrrrrrrrrrrrrrrrrhoLvTp")? - .xrpl_serialize()? - ); - // account "1" (with length prefix) - assert_hex_eq!( - "140000000000000000000000000000000000000001", - XRPLAccountId::try_from("rrrrrrrrrrrrrrrrrrrrBZbvji")? - .xrpl_serialize()? - ); - // max acccount - assert_hex_eq!( - "14FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - XRPLAccountId::try_from("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi")? - .xrpl_serialize()? - ); - assert_hex_eq!( - "13000081140000000000000000000000000000000000000000E1", - XRPLSignerEntry{ - account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, - signer_weight: 0 - }.xrpl_serialize()? - ); - // { "NetworkID": 0 } - assert_hex_eq!( - "2100000000", - XRPLObject { fields: vec![(2, 1, 0u32.xrpl_serialize()?)]} - .xrpl_serialize()? - ); - // empty array - assert_hex_eq!( - "F1", - XRPLArray::{ field_code: 10, items: vec![] } - .xrpl_serialize()? - ); - // array with 1 element - assert_hex_eq!( - "EA13000081140000000000000000000000000000000000000000E1F1", - XRPLArray::{ field_code: 10, items: vec![ - XRPLSignerEntry{ - account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, - signer_weight: 0 - }, - ] } - .xrpl_serialize()? - ); - Ok(()) - } - - #[test] - fn serialize_xrpl_unsigned_token_payment_transaction() -> Result<(), ContractError> { - let unsigned_tx = XRPLPaymentTx { - account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, - fee: 12, - sequence: Sequence::Plain(1), - amount: XRPLPaymentAmount::Token( - XRPLToken { - currency: "JPY".to_string(), - issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".to_string(), - }, - XRPLTokenAmount { mantissa: 3369568318000000u64, exponent: -16 } - ), - destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, - multisig_session_id: Uint64::from(1u8), - }; - let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize()?; - assert_eq!( - "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D0101E1F1", - hex::encode_upper(encoded_unsigned_tx) - ); - Ok(()) - } - - #[test] - fn serialize_xrpl_unsigned_xrp_payment_transaction() -> Result<(), ContractError> { - let tx = XRPLPaymentTx { - account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, - fee: 10, - sequence: Sequence::Plain(1), - amount: XRPLPaymentAmount::Drops(1000), - destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, - multisig_session_id: Uint64::from(0u8), - }; - let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; - assert_eq!( - "120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D00E1F1", - hex::encode_upper(encoded_unsigned_tx) - ); - - let tx = XRPLPaymentTx { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".try_into()?, - fee: 3, - sequence: Sequence::Plain(43497363), - amount: XRPLPaymentAmount::Drops(1000000000), - destination: "rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq".try_into()?, - multisig_session_id: Uint64::from(1337u16), - }; - let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; - assert_eq!( - "1200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6F9EA7D020539E1F1", - hex::encode_upper(encoded_unsigned_tx) - ); - Ok(()) - } - - fn pub_key_from_hex(hex: &str) -> Result { - Ok(PublicKey::Ecdsa(HexBinary::from_hex(hex)?)) - } - - #[test] - fn serialize_xrpl_signed_xrp_payment_transaction() -> Result<(), ContractError> { - let signed_tx = XRPLSignedTransaction { - unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, - fee: 30, - sequence: Sequence::Ticket(44218193), - amount: XRPLPaymentAmount::Drops(100000000), - destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, - multisig_session_id: Uint64::from(5461264u64), - }), signers: vec![ - XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, - txn_signature: HexBinary::from_hex("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045")?, - signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? - }, - XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, - txn_signature: HexBinary::from_hex("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9")?, - signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? - } - ], - }; - let encoded_signed_tx = &signed_tx.xrpl_serialize()?; - assert_eq!( - "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", - hex::encode_upper(encoded_signed_tx) - ); - Ok(()) - } - - #[test] - fn tx_serialization_sort_signers() -> Result<(), ContractError> { - let signed_tx = XRPLSignedTransaction { - unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, - fee: 30, - sequence: Sequence::Ticket(44218193), - amount: XRPLPaymentAmount::Drops(100000000), - destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, - multisig_session_id: Uint64::from(5461264u64), - }), signers: vec![ - XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, - txn_signature: HexBinary::from_hex("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9")?, - signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? - }, - XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, - txn_signature: HexBinary::from_hex("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045")?, - signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? - }, - ], - }; - let encoded_signed_tx = &signed_tx.xrpl_serialize()?; - assert_eq!( - "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", - hex::encode_upper(encoded_signed_tx) - ); - Ok(()) - } - - #[test] - fn tx_serialization_ed25519_signers() -> Result<(), ContractError> { - let signed_tx = XRPLSignedTransaction { - unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()?, - fee: 30, - sequence: Sequence::Ticket(45205896), - amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".to_string() }, Uint128::from(100000000u128).try_into()?), - destination: "raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".try_into()?, - multisig_session_id: Uint64::from(5461264u64), - }), signers: vec![ - XRPLSigner{ - account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".try_into()?, - txn_signature: HexBinary::from_hex("531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09")?, - signing_pub_key: PublicKey::Ed25519(HexBinary::from_hex("45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f")?) - }, - XRPLSigner{ - account: "rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5".try_into()?, - txn_signature: HexBinary::from_hex("76CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E0106")?, - signing_pub_key: PublicKey::Ed25519(HexBinary::from_hex("dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b")?) - }, - ], - }; - let encoded_signed_tx = &signed_tx.xrpl_serialize()?; - assert_eq!( - "12000022000000002400000000202902B1C98861D6838D7EA4C680000000000000000000000000004554480000000000EC792533BC26024CFAA5DDC2D04128E59581309C68400000000000001E73008114EC792533BC26024CFAA5DDC2D04128E59581309C831439659AAAD4DC8603798352FCF954419A67977536F3E0107321EDDD9822C7FA239DDA9913EBEE813ECBE69E35D88FF651548D5CC42C033A8A667B744076CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E010681142B3CF7B1986F5CB4EFEF11F933F40EC3106412C2E1E0107321ED45E67EAF446E6C26EB3A2B55B64339ECF3A4D1D03180BEE20EB5AFDD23FA644F7440531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09811472C14C0DB6CEF64A87CC3D152D7B0E917D372BE7E1F1F9EA7D03535510E1F1", - hex::encode_upper(encoded_signed_tx) - ); - Ok(()) - } - - - #[test] - fn serialize_xrpl_signed_xrp_ticket_create_transaction() -> Result<(), ContractError> { - let signed_tx = XRPLSignedTransaction { - unsigned_tx: XRPLUnsignedTx::TicketCreate(XRPLTicketCreateTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, - fee: 30, - sequence: Sequence::Plain(44218194), - ticket_count: 3, - multisig_session_id: Uint64::from(5461264u64), - }), signers: vec![ - XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, - txn_signature: HexBinary::from_hex("304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F")?, - signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? - }, - XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, - txn_signature: HexBinary::from_hex("3045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C58")?, - signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? - }, - ] - }; - let encoded_signed_tx = signed_tx.xrpl_serialize()?; - assert_eq!( - "12000A22000000002402A2B75220280000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC0088567446304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F8114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C588114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", - hex::encode_upper(encoded_signed_tx) - ); - Ok(()) - } - - #[test] - fn serialize_xrpl_signed_signer_list_set_transaction() -> Result<(), ContractError> { - let signed_tx = XRPLSignedTransaction { - unsigned_tx: XRPLUnsignedTx::SignerListSet(XRPLSignerListSetTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, - fee: 30, - sequence: Sequence::Plain(44218445), - signer_quorum: 3, - signer_entries: vec![ - XRPLSignerEntry{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, - signer_weight: 2 - }, - XRPLSignerEntry{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, - signer_weight: 1 - } - ], - multisig_session_id: Uint64::from(5461264u64) - }), signers: vec![ - XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, - txn_signature: HexBinary::from_hex("3045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B39")?, - signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")?, - }, - XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, - txn_signature: HexBinary::from_hex("3044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A")?, - signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")?, - }, - ], - }; - let encoded_signed_tx = signed_tx.xrpl_serialize()?; - assert_eq!( - "12000C22000000002402A2B84D20230000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674473045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B398114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74463044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A8114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F4EB1300028114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1EB1300018114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", - hex::encode_upper(encoded_signed_tx) - ); - Ok(()) - } - #[test] fn ed25519_public_key_to_xrpl_address() -> Result<(), ContractError> { assert_eq!( diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs new file mode 100644 index 000000000..b026ef06a --- /dev/null +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -0,0 +1,821 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, Uint128}; +use multisig::key::PublicKey; + +use crate::{error::ContractError, xrpl_multisig::{Sequence, XRPLAccountId, XRPLPaymentAmount, XRPLPaymentTx, XRPLSignedTransaction, XRPLSigner, XRPLSignerEntry, XRPLSignerListSetTx, XRPLTicketCreateTx, XRPLUnsignedTx}}; + +const PAYMENT_TX_TYPE: u16 = 0; +const TICKET_CREATE_TX_TYPE: u16 = 10; +const SIGNER_LIST_SET_TX_TYPE: u16 = 12; + +const POSITIVE_BIT: u64 = 0x4000000000000000; + +pub trait XRPLSerialize { + const TYPE_CODE: u8; + fn xrpl_serialize(&self) -> Result, ContractError>; +} + +impl XRPLSerialize for u16 { + const TYPE_CODE: u8 = 1; + + fn xrpl_serialize(&self) -> Result, ContractError> { + Ok(self.to_be_bytes().to_vec()) + } +} + +impl XRPLSerialize for u32 { + const TYPE_CODE: u8 = 2; + fn xrpl_serialize(&self) -> Result, ContractError> { + Ok(self.to_be_bytes().to_vec()) + } +} + +impl XRPLSerialize for XRPLPaymentAmount { + const TYPE_CODE: u8 = 6; + + fn xrpl_serialize(&self) -> Result, ContractError> { + match self { + XRPLPaymentAmount::Drops(value) => { + if *value <= 10u64.pow(17) { + Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) + } else { + Err(ContractError::InvalidAmount { reason: "more than maximum amount of drops".to_string() }) + } + }, + XRPLPaymentAmount::Token(token, amount) => { + let mut result = Vec::new(); + result.extend(amount.to_bytes()); + result.extend(currency_to_bytes(&token.currency)?); + result.extend(token.issuer.to_bytes()); + Ok(result) + } + } + } +} + +pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { + if currency.len() != 3 || !currency.is_ascii() || currency == "XRP" { + return Err(ContractError::InvalidCurrency); + } + let mut buffer = [0u8; 20]; + buffer[12..15].copy_from_slice(currency.as_bytes()); + Ok(buffer) +} + +impl XRPLSerialize for HexBinary { + const TYPE_CODE: u8 = 7; + fn xrpl_serialize(&self) -> Result, ContractError> { + let encoded_length = encode_length(self.len())?; + let mut result = Vec::new(); + result.extend(encoded_length); + result.extend(self.to_vec()); + Ok(result) + } +} + +// see https://github.com/XRPLF/xrpl-dev-portal/blob/master/content/_code-samples/tx-serialization/py/serialize.py#L92 +// may error if length too big +pub fn encode_length(mut length: usize) -> Result, ContractError> { + if length <= 192 { + Ok(vec![length as u8]) + } else if length <= 12480 { + length -= 193; + Ok(vec![193 + (length >> 8) as u8, (length & 0xff) as u8]) + } else if length <= 918744 { + length -= 12481; + Ok(vec![ + 241 + (length >> 16) as u8, + ((length >> 8) & 0xff) as u8, + (length & 0xff) as u8 + ]) + } else { + Err(ContractError::InvalidBlob) + } +} + +impl XRPLSerialize for PublicKey { + const TYPE_CODE: u8 = 7; + fn xrpl_serialize(&self) -> Result, ContractError> { + match self.clone() { + // rippled prefixes Ed25519 public keys with the byte 0xED so both types of public key are 33 bytes. + // https://xrpl.org/cryptographic-keys.html + Self::Ed25519(hex) => HexBinary::from_hex(format!("ED{}", hex.to_hex()).as_str())?.xrpl_serialize(), + Self::Ecdsa(hex) => hex.xrpl_serialize(), + } + } +} + +impl TryInto for XRPLSigner { + type Error = ContractError; + + fn try_into(self) -> Result { + let mut obj = XRPLObject::new(); + obj.add_field(3, &self.signing_pub_key)?; + obj.add_field(4, &self.txn_signature)?; + obj.add_field(1, &self.account)?; + Ok(obj) + } +} + +impl TryInto for XRPLSignerEntry { + type Error = ContractError; + + fn try_into(self) -> Result { + let mut obj = XRPLObject::new(); + obj.add_field(1, &self.account)?; + obj.add_field(3, &self.signer_weight)?; + Ok(obj) + } +} + +#[derive(Clone)] +pub struct XRPLMemo(HexBinary); + +impl Into for XRPLMemo { + fn into(self) -> HexBinary { + return self.0; + } +} + +impl TryInto for XRPLMemo { + type Error = ContractError; + + fn try_into(self) -> Result { + let mut obj = XRPLObject::new(); + let hex: HexBinary = self.into(); + obj.add_field(13, &hex)?; + Ok(obj) + } +} + +impl XRPLSerialize for XRPLAccountId { + const TYPE_CODE: u8 = 8; + + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut result: Vec = Vec::new(); + result.extend(vec![20]); // 0x14, length-encoding + result.extend(self.to_bytes()); + Ok(result) + } +} + +impl TryInto for XRPLPaymentTx { + type Error = ContractError; + + fn try_into(self) -> Result { + let mut obj = XRPLObject::new(); + obj.add_field(2, &PAYMENT_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags + obj.add_sequence(&self.sequence)?; + // type: Amount, type_code: 6, nth: 1, !isVLEncoded + obj.add_field(1, &self.amount)?; + // type: Amount, type_code: 6, nth: 8, !isVLEncoded + obj.add_field(8, &XRPLPaymentAmount::Drops(self.fee))?; + obj.add_field(3, &HexBinary::from_hex("")?)?; + obj.add_field(1, &self.account)?; + obj.add_field(3, &self.destination)?; + + let memo_data: Vec = self.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + + Ok(obj) + } +} + +impl TryInto for XRPLSignerListSetTx { + type Error = ContractError; + + fn try_into(self) -> Result { + let mut obj = XRPLObject::new(); + + obj.add_field(2, &SIGNER_LIST_SET_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags + obj.add_sequence(&self.sequence)?; + obj.add_field(35, &self.signer_quorum)?; + obj.add_field(8, &XRPLPaymentAmount::Drops(self.fee))?; + obj.add_field(1, &self.account)?; + obj.add_field(3, &HexBinary::from_hex("")?)?; + + obj.add_field(4, &XRPLArray{ field_code: 11, items: self.signer_entries.clone() })?; + + let memo_data: Vec = self.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + + Ok(obj) + } +} + +impl TryInto for XRPLTicketCreateTx { + type Error = ContractError; + + fn try_into(self) -> Result { + let mut obj = XRPLObject::new(); + // type_code: 1, nth: 2, !isVLEncoded + obj.add_field(2, &TICKET_CREATE_TX_TYPE)?; + obj.add_field(2, &0u32)?; // flags + obj.add_sequence(&self.sequence)?; + obj.add_field(40, &self.ticket_count)?; + obj.add_field(8, &XRPLPaymentAmount::Drops(self.fee))?; + obj.add_field(3, &HexBinary::from_hex("")?)?; + obj.add_field(1, &self.account)?; + + let memo_data: Vec = self.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); + let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; + obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + + Ok(obj) + } +} + +impl TryInto for XRPLUnsignedTx { + type Error = ContractError; + + fn try_into(self) -> Result { + match self { + XRPLUnsignedTx::Payment(tx) => tx.try_into(), + XRPLUnsignedTx::TicketCreate(tx) => tx.try_into(), + XRPLUnsignedTx::SignerListSet(tx) => tx.try_into() + } + } +} + +impl TryInto for XRPLSignedTransaction { + type Error = ContractError; + + fn try_into(self) -> Result { + let mut sorted_signers = self.signers.clone(); + sorted_signers.sort_by(|a, b| { + // the Signers array must be sorted based on the numeric value of the signer addresses + // https://xrpl.org/multi-signing.html#sending-multi-signed-transactions + a.account.to_bytes().cmp(&b.account.to_bytes()) + }); + let mut obj: XRPLObject = self.unsigned_tx.clone().try_into()?; + obj.add_field(3, &XRPLArray{ field_code: 16, items: sorted_signers })?; + Ok(obj) + } +} + +struct XRPLArray { + field_code: u8, + items: Vec +} + +impl XRPLSerialize for XRPLArray { + const TYPE_CODE: u8 = 15; + + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut result: Vec = Vec::new(); + for item in &self.items { + result.extend(field_id(T::TYPE_CODE, self.field_code)); + result.extend(item.xrpl_serialize()?); + result.extend(field_id(T::TYPE_CODE, 1)); + } + result.extend(field_id(Self::TYPE_CODE, 1)); + Ok(result) + } +} + +#[derive(Debug, Clone)] +pub struct XRPLObject { + fields: Vec<(u8, u8, Vec)> +} + +impl XRPLObject { + pub fn new() -> XRPLObject { + Self { + fields: Vec::new() + } + } + + pub fn add_field(&mut self, field_code: u8, value: &T) -> Result<(), ContractError> { + self.fields.push((T::TYPE_CODE, field_code, value.xrpl_serialize()?)); + Ok(()) + } + + pub fn add_sequence(&mut self, sequence: &Sequence) -> Result<(), ContractError> { + match sequence { + Sequence::Plain(seq) => { + self.add_field(4, seq) + }, + Sequence::Ticket(seq) => { + self.add_field(4, &0u32)?; + self.add_field(41, seq) + } + } + } +} + +impl XRPLSerialize for XRPLObject { + const TYPE_CODE: u8 = 14; + + fn xrpl_serialize(&self) -> Result, ContractError> { + let mut tmp: Vec<(u8, u8, Vec)> = self.fields.clone(); + tmp.sort_by(|a, b| { (a.0, a.1).cmp(&(b.0, b.1)) }); + let res = tmp.into_iter() + .map(|f| { + let mut res = Vec::new(); + res.extend(field_id(f.0, f.1)); + res.extend(f.2); + return res; + }) + .collect::>>() + .concat(); + Ok(res) + } +} + +// Implementing XRPLSerialize for any type T that can be converted into XRPLObject +impl XRPLSerialize for T +where + T: TryInto + Clone, +{ + const TYPE_CODE: u8 = XRPLObject::TYPE_CODE; + + fn xrpl_serialize(&self) -> Result, ContractError> { + let obj: XRPLObject = self.clone().try_into()?; + obj.xrpl_serialize() + } +} + +// field ids and type codes from here +// https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/enums/definitions.json +pub fn field_id(type_code: u8, field_code: u8) -> Vec { + assert!(type_code < 16); + if field_code < 16 { + vec![type_code << 4 | field_code] + } else { + vec![type_code << 4, field_code] + } +} + +const MIN_MANTISSA: u64 = 1_000_000_000_000_000; +const MAX_MANTISSA: u64 = 10_000_000_000_000_000 - 1; +const MIN_EXPONENT: i64 = -96; +const MAX_EXPONENT: i64 = 80; + +// XRPLTokenAmount always in canonicalized XRPL mantissa-exponent format, +// such that MIN_MANTISSA <= mantissa <= MAX_MANTISSA (or equal to zero), MIN_EXPONENT <= exponent <= MAX_EXPONENT, +// In XRPL generally it can be decimal and even negative (!) but in our case that doesn't apply. +#[cw_serde] +pub struct XRPLTokenAmount { + mantissa: u64, + exponent: i64 +} + +impl XRPLTokenAmount { + pub fn new(mantissa: u64, exponent: i64) -> Self { + assert!(mantissa == 0 || (MIN_MANTISSA <= mantissa && mantissa <= MAX_MANTISSA && MIN_EXPONENT <= exponent && exponent <= MAX_EXPONENT)); + Self { mantissa, exponent } + } + + pub fn to_bytes(&self) -> [u8; 8] { + if self.mantissa == 0 { + 0x8000000000000000u64.to_be_bytes() + } else { + // not xrp-bit | positive bit | 8 bits exponent | 54 bits mantissa + (0xC000000000000000u64 | ((self.exponent + 97) as u64) << 54 | self.mantissa).to_be_bytes() + } + } +} + +impl TryFrom for XRPLTokenAmount { + type Error = ContractError; + + fn try_from(amount: Uint128) -> Result { + let (mantissa, exponent) = canonicalize_mantissa(amount)?; + Ok(XRPLTokenAmount::new(mantissa, exponent)) + } +} + +// always called when XRPLTokenAmount instantiated +// see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 +pub fn canonicalize_mantissa(mut mantissa: Uint128) -> Result<(u64, i64), ContractError> { + let mut exponent = 0i64; + + let ten = Uint128::from(10u128); + + while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { + mantissa *= ten; + exponent -= 1; + } + + while mantissa > MAX_MANTISSA.into() && exponent > MIN_EXPONENT { + if exponent > MAX_EXPONENT { + return Err(ContractError::InvalidAmount { reason: "overflow".to_string() }); + } + mantissa /= ten; + exponent += 1; + } + + if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA.into() { + return Ok((0, 1)); + } + + if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA.into() { + return Err(ContractError::InvalidAmount { reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); + } + + let mantissa = u64::from_be_bytes(mantissa.to_be_bytes()[8..].try_into().unwrap()); + + return Ok((mantissa, exponent)); +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{Uint128, Uint64}; + use multisig::key::PublicKey; + + use crate::types::XRPLToken; + + use super::*; + + #[macro_export] + macro_rules! assert_hex_eq { + ($expected:expr, $actual:expr) => { + assert_eq!($expected, hex::encode_upper($actual)); + }; + } + + #[test] + fn test_encode_length() -> Result<(), ContractError> { + assert_hex_eq!("00", encode_length(0)?); + assert_hex_eq!("0A", encode_length(10)?); + assert_hex_eq!("C100", encode_length(193)?); + assert_hex_eq!("F10000", encode_length(12481)?); + assert_hex_eq!("FED417", encode_length(918744)?); + assert!(encode_length(918745).is_err()); + Ok(()) + } + + #[test] + fn test_xrpl_serialize() -> Result<(), ContractError> { + assert_hex_eq!("0000", 0u16.xrpl_serialize()?); + assert_hex_eq!("0001", 1u16.xrpl_serialize()?); + assert_hex_eq!("FFFF", 0xffffu16.xrpl_serialize()?); + assert_hex_eq!("00000000", 0u32.xrpl_serialize()?); + assert_hex_eq!("00000005", 5u32.xrpl_serialize()?); + assert_hex_eq!("FFFFFFFF", 0xffffffffu32.xrpl_serialize()?); + assert_hex_eq!("00", HexBinary::from_hex("")?.xrpl_serialize()?); + assert_hex_eq!("04DEADBEEF", HexBinary::from_hex("DEADBEEF")?.xrpl_serialize()?); + assert_hex_eq!( + "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + currency: "USD".to_string(), + }, Uint128::zero().try_into()?) + .xrpl_serialize()? + ); + assert_hex_eq!( + "D4838D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + currency: "USD".to_string(), + }, Uint128::from(1u128).try_into()?) + .xrpl_serialize()? + ); + // minimum absolute amount + assert_hex_eq!( + "C0438D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + currency: "USD".to_string(), + }, XRPLTokenAmount { mantissa: MIN_MANTISSA, exponent: MIN_EXPONENT }) + .xrpl_serialize()? + ); + // maximum amount + assert_hex_eq!( + "EC6386F26FC0FFFF00000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + currency: "USD".to_string(), + }, XRPLTokenAmount { mantissa: MAX_MANTISSA, exponent: MAX_EXPONENT }) + .xrpl_serialize()? + ); + // currency can contain non-alphanumeric ascii letters + assert_hex_eq!( + "D4CEEBE0B40E8000000000000000000000000000247B3B00000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + currency: "${;".to_string(), + }, Uint128::from(42u128).try_into()?) + .xrpl_serialize()? + ); + // TODO: these could be enforced on a type level: + // - currency cannot contain non-ascii letters + // - currency must not be more than 3 ascii letters + // - currency must not be less than 3 ascii letters + // XRP currency code is not allowed + assert!( + XRPLPaymentAmount::Token(XRPLToken { + issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + currency: "XRP".to_string(), + }, Uint128::from(42u128).try_into()?) + .xrpl_serialize() + .is_err() + ); + // minimum XRP + assert_hex_eq!( + "4000000000000000", + XRPLPaymentAmount::Drops(0) + .xrpl_serialize()? + ); + assert_hex_eq!( + "4000000000000001", + XRPLPaymentAmount::Drops(1) + .xrpl_serialize()? + ); + assert_hex_eq!( + "40000000499602D2", + XRPLPaymentAmount::Drops(1234567890) + .xrpl_serialize()? + ); + // maximum XRP + assert_hex_eq!( + "416345785D8A0000", + XRPLPaymentAmount::Drops(100_000_000_000_000_000) + .xrpl_serialize()? + ); + // more than maximum XRP fails + assert!( + XRPLPaymentAmount::Drops(100_000_000_000_000_001) + .xrpl_serialize() + .is_err() + ); + // account "0" (with length prefix) + assert_hex_eq!( + "140000000000000000000000000000000000000000", + XRPLAccountId::try_from("rrrrrrrrrrrrrrrrrrrrrhoLvTp")? + .xrpl_serialize()? + ); + // account "1" (with length prefix) + assert_hex_eq!( + "140000000000000000000000000000000000000001", + XRPLAccountId::try_from("rrrrrrrrrrrrrrrrrrrrBZbvji")? + .xrpl_serialize()? + ); + // max acccount + assert_hex_eq!( + "14FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + XRPLAccountId::try_from("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi")? + .xrpl_serialize()? + ); + assert_hex_eq!( + "13000081140000000000000000000000000000000000000000", + XRPLSignerEntry{ + account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, + signer_weight: 0 + }.xrpl_serialize()? + ); + // { "NetworkID": 0 } + assert_hex_eq!( + "2100000000", + XRPLObject { fields: vec![(2, 1, 0u32.xrpl_serialize()?)]} + .xrpl_serialize()? + ); + // empty array + assert_hex_eq!( + "F1", + XRPLArray::{ field_code: 10, items: vec![] } + .xrpl_serialize()? + ); + // array with 1 element + assert_hex_eq!( + "EA13000081140000000000000000000000000000000000000000E1F1", + XRPLArray::{ field_code: 10, items: vec![ + XRPLSignerEntry{ + account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, + signer_weight: 0 + }, + ] } + .xrpl_serialize()? + ); + Ok(()) + } + + #[test] + fn serialize_xrpl_unsigned_token_payment_transaction() -> Result<(), ContractError> { + let unsigned_tx = XRPLPaymentTx { + account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + fee: 12, + sequence: Sequence::Plain(1), + amount: XRPLPaymentAmount::Token( + XRPLToken { + currency: "JPY".to_string(), + issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".try_into()?, + }, + XRPLTokenAmount { mantissa: 3369568318000000u64, exponent: -16 } + ), + destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, + multisig_session_id: Uint64::from(1u8), + }; + let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize()?; + assert_eq!( + "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D0101E1F1", + hex::encode_upper(encoded_unsigned_tx) + ); + Ok(()) + } + + #[test] + fn serialize_xrpl_unsigned_xrp_payment_transaction() -> Result<(), ContractError> { + let tx = XRPLPaymentTx { + account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + fee: 10, + sequence: Sequence::Plain(1), + amount: XRPLPaymentAmount::Drops(1000), + destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, + multisig_session_id: Uint64::from(0u8), + }; + let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; + assert_eq!( + "120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D00E1F1", + hex::encode_upper(encoded_unsigned_tx) + ); + + let tx = XRPLPaymentTx { + account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".try_into()?, + fee: 3, + sequence: Sequence::Plain(43497363), + amount: XRPLPaymentAmount::Drops(1000000000), + destination: "rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq".try_into()?, + multisig_session_id: Uint64::from(1337u16), + }; + let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; + assert_eq!( + "1200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6F9EA7D020539E1F1", + hex::encode_upper(encoded_unsigned_tx) + ); + Ok(()) + } + + fn pub_key_from_hex(hex: &str) -> Result { + Ok(PublicKey::Ecdsa(HexBinary::from_hex(hex)?)) + } + + #[test] + fn serialize_xrpl_signed_xrp_payment_transaction() -> Result<(), ContractError> { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, + fee: 30, + sequence: Sequence::Ticket(44218193), + amount: XRPLPaymentAmount::Drops(100000000), + destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, + multisig_session_id: Uint64::from(5461264u64), + }), signers: vec![ + XRPLSigner{ + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + txn_signature: HexBinary::from_hex("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045")?, + signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? + }, + XRPLSigner{ + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + txn_signature: HexBinary::from_hex("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9")?, + signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? + } + ], + }; + let encoded_signed_tx = &signed_tx.xrpl_serialize()?; + assert_eq!( + "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + hex::encode_upper(encoded_signed_tx) + ); + Ok(()) + } + + #[test] + fn tx_serialization_sort_signers() -> Result<(), ContractError> { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, + fee: 30, + sequence: Sequence::Ticket(44218193), + amount: XRPLPaymentAmount::Drops(100000000), + destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, + multisig_session_id: Uint64::from(5461264u64), + }), signers: vec![ + XRPLSigner{ + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + txn_signature: HexBinary::from_hex("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9")?, + signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? + }, + XRPLSigner{ + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + txn_signature: HexBinary::from_hex("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045")?, + signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? + }, + ], + }; + let encoded_signed_tx = &signed_tx.xrpl_serialize()?; + assert_eq!( + "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + hex::encode_upper(encoded_signed_tx) + ); + Ok(()) + } + + #[test] + fn tx_serialization_ed25519_signers() -> Result<(), ContractError> { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { + account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()?, + fee: 30, + sequence: Sequence::Ticket(45205896), + amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()? }, Uint128::from(100000000u128).try_into()?), + destination: "raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".try_into()?, + multisig_session_id: Uint64::from(5461264u64), + }), signers: vec![ + XRPLSigner{ + account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".try_into()?, + txn_signature: HexBinary::from_hex("531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09")?, + signing_pub_key: PublicKey::Ed25519(HexBinary::from_hex("45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f")?) + }, + XRPLSigner{ + account: "rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5".try_into()?, + txn_signature: HexBinary::from_hex("76CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E0106")?, + signing_pub_key: PublicKey::Ed25519(HexBinary::from_hex("dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b")?) + }, + ], + }; + let encoded_signed_tx = &signed_tx.xrpl_serialize()?; + assert_eq!( + "12000022000000002400000000202902B1C98861D6838D7EA4C680000000000000000000000000004554480000000000EC792533BC26024CFAA5DDC2D04128E59581309C68400000000000001E73008114EC792533BC26024CFAA5DDC2D04128E59581309C831439659AAAD4DC8603798352FCF954419A67977536F3E0107321EDDD9822C7FA239DDA9913EBEE813ECBE69E35D88FF651548D5CC42C033A8A667B744076CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E010681142B3CF7B1986F5CB4EFEF11F933F40EC3106412C2E1E0107321ED45E67EAF446E6C26EB3A2B55B64339ECF3A4D1D03180BEE20EB5AFDD23FA644F7440531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09811472C14C0DB6CEF64A87CC3D152D7B0E917D372BE7E1F1F9EA7D03535510E1F1", + hex::encode_upper(encoded_signed_tx) + ); + Ok(()) + } + + + #[test] + fn serialize_xrpl_signed_xrp_ticket_create_transaction() -> Result<(), ContractError> { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx::TicketCreate(XRPLTicketCreateTx { + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, + fee: 30, + sequence: Sequence::Plain(44218194), + ticket_count: 3, + multisig_session_id: Uint64::from(5461264u64), + }), signers: vec![ + XRPLSigner{ + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + txn_signature: HexBinary::from_hex("304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F")?, + signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? + }, + XRPLSigner{ + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + txn_signature: HexBinary::from_hex("3045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C58")?, + signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? + }, + ] + }; + let encoded_signed_tx = signed_tx.xrpl_serialize()?; + assert_eq!( + "12000A22000000002402A2B75220280000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC0088567446304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F8114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C588114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + hex::encode_upper(encoded_signed_tx) + ); + Ok(()) + } + + #[test] + fn serialize_xrpl_signed_signer_list_set_transaction() -> Result<(), ContractError> { + let signed_tx = XRPLSignedTransaction { + unsigned_tx: XRPLUnsignedTx::SignerListSet(XRPLSignerListSetTx { + account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, + fee: 30, + sequence: Sequence::Plain(44218445), + signer_quorum: 3, + signer_entries: vec![ + XRPLSignerEntry{ + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + signer_weight: 2 + }, + XRPLSignerEntry{ + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + signer_weight: 1 + } + ], + multisig_session_id: Uint64::from(5461264u64) + }), signers: vec![ + XRPLSigner{ + account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + txn_signature: HexBinary::from_hex("3045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B39")?, + signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")?, + }, + XRPLSigner{ + account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + txn_signature: HexBinary::from_hex("3044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A")?, + signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")?, + }, + ], + }; + let encoded_signed_tx = signed_tx.xrpl_serialize()?; + assert_eq!( + "12000C22000000002402A2B84D20230000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674473045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B398114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74463044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A8114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F4EB1300028114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1EB1300018114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + hex::encode_upper(encoded_signed_tx) + ); + Ok(()) + } + +} \ No newline at end of file diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 0e551c3d3..8c5e7b77d 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1052,7 +1052,7 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { multisig_prover_address.clone(), ETH_DENOMINATION.to_string(), XRPLToken { - issuer: xrpl_multisig_address, + issuer: xrpl_multisig_address.as_str().try_into().unwrap(), currency: "ETH".to_string(), }, ); From 4ec69368b9f40f85cf62ff8f574457cb3ccace7b Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Mon, 19 Feb 2024 13:23:50 +0100 Subject: [PATCH 55/80] xrpl: statically typed fields to improve code clarity --- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 2 +- .../src/xrpl_serialize.rs | 134 ++++++++++++------ 2 files changed, 92 insertions(+), 44 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index b7308c89b..e5a728f40 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -126,7 +126,7 @@ pub struct XRPLTicketCreateTx { pub struct XRPLAccountId([u8; 20]); impl XRPLAccountId { - pub fn to_bytes(&self) -> [u8; 20] { + pub const fn to_bytes(&self) -> [u8; 20] { return self.0; } diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index b026ef06a..19ba2f429 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -105,14 +105,65 @@ impl XRPLSerialize for PublicKey { } } +#[derive(Clone)] +pub enum Field { + SigningPubKey, + TxnSignature, + Account, + SignerWeight, + MemoData, + TransactionType, + Flags, + Sequence, + TicketSequence, + Amount, + Fee, + Destination, + SignerQuorum, + SignerEntries, + SignerEntry, + Memos, + Memo, + TicketCount, + Signers, + Signer +} + +impl Field { + const fn to_u8(self) -> u8 { + match self { + Field::SigningPubKey => 3, + Field::TxnSignature => 4, + Field::Account => 1, + Field::SignerWeight => 3, + Field::MemoData => 13, + Field::TransactionType => 2, + Field::Flags => 2, + Field::Amount => 1, + Field::Fee => 8, + Field::Destination => 3, + Field::Sequence => 4, + Field::TicketSequence => 41, + Field::SignerQuorum => 35, + Field::SignerEntries => 4, + Field::SignerEntry => 11, + Field::Memos => 9, + Field::Memo => 10, + Field::TicketCount => 40, + Field::Signers => 3, + Field::Signer => 16 + } + } +} + impl TryInto for XRPLSigner { type Error = ContractError; fn try_into(self) -> Result { let mut obj = XRPLObject::new(); - obj.add_field(3, &self.signing_pub_key)?; - obj.add_field(4, &self.txn_signature)?; - obj.add_field(1, &self.account)?; + obj.add_field(Field::SigningPubKey, &self.signing_pub_key)?; + obj.add_field(Field::TxnSignature, &self.txn_signature)?; + obj.add_field(Field::Account, &self.account)?; Ok(obj) } } @@ -122,8 +173,8 @@ impl TryInto for XRPLSignerEntry { fn try_into(self) -> Result { let mut obj = XRPLObject::new(); - obj.add_field(1, &self.account)?; - obj.add_field(3, &self.signer_weight)?; + obj.add_field(Field::Account, &self.account)?; + obj.add_field(Field::SignerWeight, &self.signer_weight)?; Ok(obj) } } @@ -143,7 +194,7 @@ impl TryInto for XRPLMemo { fn try_into(self) -> Result { let mut obj = XRPLObject::new(); let hex: HexBinary = self.into(); - obj.add_field(13, &hex)?; + obj.add_field(Field::MemoData, &hex)?; Ok(obj) } } @@ -153,7 +204,7 @@ impl XRPLSerialize for XRPLAccountId { fn xrpl_serialize(&self) -> Result, ContractError> { let mut result: Vec = Vec::new(); - result.extend(vec![20]); // 0x14, length-encoding + result.extend(vec![self.to_bytes().len() as u8]); result.extend(self.to_bytes()); Ok(result) } @@ -164,20 +215,18 @@ impl TryInto for XRPLPaymentTx { fn try_into(self) -> Result { let mut obj = XRPLObject::new(); - obj.add_field(2, &PAYMENT_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags + obj.add_field(Field::TransactionType, &PAYMENT_TX_TYPE)?; + obj.add_field(Field::Flags, &0u32)?; obj.add_sequence(&self.sequence)?; - // type: Amount, type_code: 6, nth: 1, !isVLEncoded - obj.add_field(1, &self.amount)?; - // type: Amount, type_code: 6, nth: 8, !isVLEncoded - obj.add_field(8, &XRPLPaymentAmount::Drops(self.fee))?; - obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(1, &self.account)?; - obj.add_field(3, &self.destination)?; + obj.add_field(Field::Amount, &self.amount)?; + obj.add_field(Field::Fee, &XRPLPaymentAmount::Drops(self.fee))?; + obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; + obj.add_field(Field::Account, &self.account)?; + obj.add_field(Field::Destination, &self.destination)?; let memo_data: Vec = self.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(memo)]})?; Ok(obj) } @@ -189,19 +238,19 @@ impl TryInto for XRPLSignerListSetTx { fn try_into(self) -> Result { let mut obj = XRPLObject::new(); - obj.add_field(2, &SIGNER_LIST_SET_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags + obj.add_field(Field::TransactionType, &SIGNER_LIST_SET_TX_TYPE)?; + obj.add_field(Field::Flags, &0u32)?; // flags obj.add_sequence(&self.sequence)?; - obj.add_field(35, &self.signer_quorum)?; - obj.add_field(8, &XRPLPaymentAmount::Drops(self.fee))?; - obj.add_field(1, &self.account)?; - obj.add_field(3, &HexBinary::from_hex("")?)?; + obj.add_field(Field::SignerQuorum, &self.signer_quorum)?; + obj.add_field(Field::Fee, &XRPLPaymentAmount::Drops(self.fee))?; + obj.add_field(Field::Account, &self.account)?; + obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; - obj.add_field(4, &XRPLArray{ field_code: 11, items: self.signer_entries.clone() })?; + obj.add_field(Field::SignerEntries, &XRPLArray{ field: Field::SignerEntry, items: self.signer_entries.clone() })?; let memo_data: Vec = self.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(memo)]})?; Ok(obj) } @@ -212,18 +261,17 @@ impl TryInto for XRPLTicketCreateTx { fn try_into(self) -> Result { let mut obj = XRPLObject::new(); - // type_code: 1, nth: 2, !isVLEncoded - obj.add_field(2, &TICKET_CREATE_TX_TYPE)?; - obj.add_field(2, &0u32)?; // flags + obj.add_field(Field::TransactionType, &TICKET_CREATE_TX_TYPE)?; + obj.add_field(Field::Flags, &0u32)?; obj.add_sequence(&self.sequence)?; - obj.add_field(40, &self.ticket_count)?; - obj.add_field(8, &XRPLPaymentAmount::Drops(self.fee))?; - obj.add_field(3, &HexBinary::from_hex("")?)?; - obj.add_field(1, &self.account)?; + obj.add_field(Field::TicketCount, &self.ticket_count)?; + obj.add_field(Field::Fee, &XRPLPaymentAmount::Drops(self.fee))?; + obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; + obj.add_field(Field::Account, &self.account)?; let memo_data: Vec = self.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(9, &XRPLArray{field_code: 10, items: vec![XRPLMemo(memo)]})?; + obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(memo)]})?; Ok(obj) } @@ -252,13 +300,13 @@ impl TryInto for XRPLSignedTransaction { a.account.to_bytes().cmp(&b.account.to_bytes()) }); let mut obj: XRPLObject = self.unsigned_tx.clone().try_into()?; - obj.add_field(3, &XRPLArray{ field_code: 16, items: sorted_signers })?; + obj.add_field(Field::Signers, &XRPLArray{ field: Field::Signer, items: sorted_signers })?; Ok(obj) } } struct XRPLArray { - field_code: u8, + field: Field, items: Vec } @@ -268,7 +316,7 @@ impl XRPLSerialize for XRPLArray { fn xrpl_serialize(&self) -> Result, ContractError> { let mut result: Vec = Vec::new(); for item in &self.items { - result.extend(field_id(T::TYPE_CODE, self.field_code)); + result.extend(field_id(T::TYPE_CODE, self.field.clone().to_u8())); result.extend(item.xrpl_serialize()?); result.extend(field_id(T::TYPE_CODE, 1)); } @@ -289,19 +337,19 @@ impl XRPLObject { } } - pub fn add_field(&mut self, field_code: u8, value: &T) -> Result<(), ContractError> { - self.fields.push((T::TYPE_CODE, field_code, value.xrpl_serialize()?)); + pub fn add_field(&mut self, field: Field, value: &T) -> Result<(), ContractError> { + self.fields.push((T::TYPE_CODE, field.to_u8(), value.xrpl_serialize()?)); Ok(()) } pub fn add_sequence(&mut self, sequence: &Sequence) -> Result<(), ContractError> { match sequence { Sequence::Plain(seq) => { - self.add_field(4, seq) + self.add_field(Field::Sequence, seq) }, Sequence::Ticket(seq) => { - self.add_field(4, &0u32)?; - self.add_field(41, seq) + self.add_field(Field::Sequence, &0u32)?; + self.add_field(Field::TicketSequence, seq) } } } @@ -577,13 +625,13 @@ mod tests { // empty array assert_hex_eq!( "F1", - XRPLArray::{ field_code: 10, items: vec![] } + XRPLArray::{ field: Field::Memo, items: vec![] } .xrpl_serialize()? ); // array with 1 element assert_hex_eq!( "EA13000081140000000000000000000000000000000000000000E1F1", - XRPLArray::{ field_code: 10, items: vec![ + XRPLArray::{ field: Field::Memo, items: vec![ XRPLSignerEntry{ account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, signer_weight: 0 From 1e300825820fdf46ea2fcb5d053fbf1e8bcdfcbe Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Mon, 19 Feb 2024 14:56:54 +0100 Subject: [PATCH 56/80] xrpl multisig prover: improver code structure --- ampd/src/handlers/xrpl_multisig.rs | 5 +- .../xrpl-multisig-prover/src/contract.rs | 4 +- contracts/xrpl-multisig-prover/src/query.rs | 2 +- contracts/xrpl-multisig-prover/src/types.rs | 256 +++++++++++++++++- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 183 +------------ .../src/xrpl_serialize.rs | 96 +------ integration-tests/tests/test_utils/mod.rs | 2 +- 7 files changed, 276 insertions(+), 272 deletions(-) diff --git a/ampd/src/handlers/xrpl_multisig.rs b/ampd/src/handlers/xrpl_multisig.rs index 32d032e91..812e00441 100644 --- a/ampd/src/handlers/xrpl_multisig.rs +++ b/ampd/src/handlers/xrpl_multisig.rs @@ -16,15 +16,14 @@ use events::Error::EventTypeMismatch; use events_derive; use events_derive::try_from; use multisig::msg::ExecuteMsg; -use xrpl_multisig_prover::xrpl_multisig::XRPLAccountId; +use crate::types::*; +use xrpl_multisig_prover::types::*; use crate::event_processor::EventHandler; use crate::handlers::errors::Error::{self, DeserializeEvent}; use crate::queue::queued_broadcaster::BroadcasterClient; use crate::tofnd::grpc::SharableEcdsaClient; use crate::tofnd::MessageDigest; -use crate::types::PublicKey; -use crate::types::TMAddress; #[derive(Debug, Deserialize)] #[try_from("wasm-signing_started")] diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index ac0f57d02..7a2a7f6ec 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -17,7 +17,7 @@ use crate::{ msg::{ExecuteMsg, QueryMsg}, reply, types::*, - xrpl_multisig::{self, XRPLPaymentAmount}, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query, + xrpl_multisig, axelar_workers, querier::{Querier, XRPL_CHAIN_NAME}, query, xrpl_serialize::XRPLSerialize, }; @@ -321,7 +321,7 @@ fn update_tx_status( let multisig_session = querier.get_multisig_session(&multisig_session_id)?; let destination_str = match &tx_info.unsigned_contents { - xrpl_multisig::XRPLUnsignedTx::Payment(p) => p.destination.to_string(), + XRPLUnsignedTx::Payment(p) => p.destination.to_string(), _ => xrpl_multisig_address.to_string(), }; diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 294ac6c01..d96eec8c7 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -3,8 +3,8 @@ use cosmwasm_std::{StdResult, Uint64, HexBinary, Storage}; use multisig::{key::Signature, types::MultisigState}; use multisig::key::PublicKey; -use crate::xrpl_multisig::XRPLAccountId; use crate::{ + types::*, state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, xrpl_serialize::XRPLSerialize }; diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 239d8bda6..48dc4aaeb 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -1,11 +1,14 @@ use axelar_wasm_std::{nonempty, VerificationStatus}; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256}; +use cosmwasm_std::{from_binary, HexBinary, StdResult, Uint256, Uint128, Uint64}; use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; use multisig::key::Signature; +use multisig::key::PublicKey; +use ripemd::Ripemd160; +use sha2::{Digest, Sha256}; -use crate::{error::ContractError, xrpl_multisig::{XRPLAccountId, XRPLUnsignedTx}}; +use crate::error::ContractError; #[cw_serde] pub enum TransactionStatus { @@ -125,3 +128,252 @@ pub struct XRPLToken { pub issuer: XRPLAccountId, pub currency: String, } + +#[cw_serde] +pub enum XRPLPaymentAmount { + Drops( + u64, + ), + Token(XRPLToken, XRPLTokenAmount), +} + +#[cw_serde] +pub enum Sequence { + Plain(u32), + Ticket(u32), +} + +impl Into for Sequence { + fn into(self) -> u32 { + match self { + Sequence::Plain(sequence) => sequence, + Sequence::Ticket(ticket) => ticket, + } + } +} + +#[cw_serde] +pub struct XRPLSignerEntry { + pub account: XRPLAccountId, + pub signer_weight: u16, +} + +#[cw_serde] +pub enum XRPLUnsignedTx { + Payment(XRPLPaymentTx), + SignerListSet(XRPLSignerListSetTx), + TicketCreate(XRPLTicketCreateTx), +} + +impl XRPLUnsignedTx { + pub fn sequence(&self) -> &Sequence { + match self { + XRPLUnsignedTx::Payment(tx) => { + &tx.sequence + }, + XRPLUnsignedTx::TicketCreate(tx) => { + &tx.sequence + }, + XRPLUnsignedTx::SignerListSet(tx) => { + &tx.sequence + } + } + } + pub fn sequence_number_increment(&self, status: TransactionStatus) -> u32 { + if status == TransactionStatus::Pending || status == TransactionStatus::Inconclusive { + return 0; + } + + match self { + XRPLUnsignedTx::Payment(tx ) => { + match tx.sequence { + Sequence::Plain(_) => 1, + Sequence::Ticket(_) => 0, + } + } + XRPLUnsignedTx::SignerListSet(tx) => { + match tx.sequence { + Sequence::Plain(_) => 1, + Sequence::Ticket(_) => 0, + } + }, + XRPLUnsignedTx::TicketCreate(tx) => { + match status { + TransactionStatus::Succeeded => tx.ticket_count + 1, + TransactionStatus::FailedOnChain => 1, + TransactionStatus::Inconclusive | + TransactionStatus::Pending => unreachable!(), + } + }, + } + } +} + +#[cw_serde] +pub struct XRPLPaymentTx { + pub account: XRPLAccountId, + pub fee: u64, + pub sequence: Sequence, + pub amount: XRPLPaymentAmount, + pub destination: XRPLAccountId, + pub multisig_session_id: Uint64 +} + +#[cw_serde] +pub struct XRPLSignerListSetTx { + pub account: XRPLAccountId, + pub fee: u64, + pub sequence: Sequence, + pub signer_quorum: u32, + pub signer_entries: Vec, + pub multisig_session_id: Uint64 +} + +#[cw_serde] +pub struct XRPLTicketCreateTx { + pub account: XRPLAccountId, + pub fee: u64, + pub sequence: Sequence, + pub ticket_count: u32, + pub multisig_session_id: Uint64 +} + +#[cw_serde] +pub struct XRPLAccountId([u8; 20]); + +impl XRPLAccountId { + pub const fn to_bytes(&self) -> [u8; 20] { + return self.0; + } + + pub fn from_bytes(bytes: [u8; 20]) -> Self { + Self(bytes) + } + + pub fn to_string(&self) -> String { + let address_type_prefix: &[u8] = &[0x00]; + let payload = [address_type_prefix, &self.to_bytes()].concat(); + + let checksum_hash1 = Sha256::digest(payload.clone()); + let checksum_hash2 = Sha256::digest(checksum_hash1); + let checksum = &checksum_hash2[0..4]; + + bs58::encode([payload, checksum.to_vec()].concat()) + .with_alphabet(bs58::Alphabet::RIPPLE) + .into_string() + } +} + +impl From<&PublicKey> for XRPLAccountId { + fn from(pub_key: &PublicKey) -> Self { + let public_key_hex: HexBinary = pub_key.clone().into(); + + assert!(public_key_hex.len() == 33); + + let public_key_inner_hash = Sha256::digest(public_key_hex); + let account_id = Ripemd160::digest(public_key_inner_hash); + + return XRPLAccountId(account_id.into()); + } +} + +impl TryFrom<&str> for XRPLAccountId { + type Error = ContractError; + + fn try_from(address: &str) -> Result { + let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().map_err(|_| ContractError::InvalidAddress)?; + // .map_err(|_| ContractError::InvalidAddress)?; + if res.len() != 25 { + return Err(ContractError::InvalidAddress); + } + let mut buffer = [0u8; 20]; + buffer.copy_from_slice(&res[1..21]); + return Ok(XRPLAccountId(buffer)) + } +} + +#[cw_serde] +pub struct XRPLSigner { + pub account: XRPLAccountId, + pub txn_signature: HexBinary, + pub signing_pub_key: PublicKey, +} + +#[cw_serde] +pub struct XRPLSignedTransaction { + pub unsigned_tx: XRPLUnsignedTx, + pub signers: Vec +} + + +pub const MIN_MANTISSA: u64 = 1_000_000_000_000_000; +pub const MAX_MANTISSA: u64 = 10_000_000_000_000_000 - 1; +pub const MIN_EXPONENT: i64 = -96; +pub const MAX_EXPONENT: i64 = 80; + +// XRPLTokenAmount always in canonicalized XRPL mantissa-exponent format, +// such that MIN_MANTISSA <= mantissa <= MAX_MANTISSA (or equal to zero), MIN_EXPONENT <= exponent <= MAX_EXPONENT, +// In XRPL generally it can be decimal and even negative (!) but in our case that doesn't apply. +#[cw_serde] +pub struct XRPLTokenAmount { + mantissa: u64, + exponent: i64 +} + +impl XRPLTokenAmount { + pub fn new(mantissa: u64, exponent: i64) -> Self { + assert!(mantissa == 0 || (MIN_MANTISSA <= mantissa && mantissa <= MAX_MANTISSA && MIN_EXPONENT <= exponent && exponent <= MAX_EXPONENT)); + Self { mantissa, exponent } + } + + pub fn to_bytes(&self) -> [u8; 8] { + if self.mantissa == 0 { + 0x8000000000000000u64.to_be_bytes() + } else { + // not xrp-bit | positive bit | 8 bits exponent | 54 bits mantissa + (0xC000000000000000u64 | ((self.exponent + 97) as u64) << 54 | self.mantissa).to_be_bytes() + } + } +} + +impl TryFrom for XRPLTokenAmount { + type Error = ContractError; + + fn try_from(amount: Uint128) -> Result { + let (mantissa, exponent) = canonicalize_mantissa(amount)?; + Ok(XRPLTokenAmount::new(mantissa, exponent)) + } +} + +// always called when XRPLTokenAmount instantiated +// see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 +pub fn canonicalize_mantissa(mut mantissa: Uint128) -> Result<(u64, i64), ContractError> { + let mut exponent = 0i64; + + let ten = Uint128::from(10u128); + + while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { + mantissa *= ten; + exponent -= 1; + } + + while mantissa > MAX_MANTISSA.into() && exponent > MIN_EXPONENT { + if exponent > MAX_EXPONENT { + return Err(ContractError::InvalidAmount { reason: "overflow".to_string() }); + } + mantissa /= ten; + exponent += 1; + } + + if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA.into() { + return Ok((0, 1)); + } + + if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA.into() { + return Err(ContractError::InvalidAmount { reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); + } + + let mantissa = u64::from_be_bytes(mantissa.to_be_bytes()[8..].try_into().unwrap()); + + return Ok((mantissa, exponent)); +} \ No newline at end of file diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index e5a728f40..bed6bcf9d 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -2,189 +2,14 @@ use std::collections::BTreeSet; use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint128, Uint64, WasmMsg}; +use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; -use multisig::key::PublicKey; -use ripemd::Ripemd160; use sha2::{Sha512, Digest, Sha256}; use crate::{ - axelar_workers::{AxelarSigner, WorkerSet}, error::ContractError, state::{Config, AVAILABLE_TICKETS, CONFIRMED_TRANSACTIONS, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, LATEST_SEQUENTIAL_TX_HASH, MESSAGE_ID_TO_TICKET, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, TRANSACTION_INFO}, types::*, xrpl_serialize::XRPLTokenAmount + axelar_workers::{AxelarSigner, WorkerSet}, error::ContractError, state::{Config, AVAILABLE_TICKETS, CONFIRMED_TRANSACTIONS, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, LATEST_SEQUENTIAL_TX_HASH, MESSAGE_ID_TO_TICKET, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, TRANSACTION_INFO}, types::* }; -#[cw_serde] -pub enum XRPLPaymentAmount { - Drops( - u64, - ), - Token(XRPLToken, XRPLTokenAmount), -} - -#[cw_serde] -pub enum Sequence { - Plain(u32), - Ticket(u32), -} - -impl Into for Sequence { - fn into(self) -> u32 { - match self { - Sequence::Plain(sequence) => sequence, - Sequence::Ticket(ticket) => ticket, - } - } -} - -#[cw_serde] -pub struct XRPLSignerEntry { - pub account: XRPLAccountId, - pub signer_weight: u16, -} - -#[cw_serde] -pub enum XRPLUnsignedTx { - Payment(XRPLPaymentTx), - SignerListSet(XRPLSignerListSetTx), - TicketCreate(XRPLTicketCreateTx), -} - -impl XRPLUnsignedTx { - pub fn sequence(&self) -> &Sequence { - match self { - XRPLUnsignedTx::Payment(tx) => { - &tx.sequence - }, - XRPLUnsignedTx::TicketCreate(tx) => { - &tx.sequence - }, - XRPLUnsignedTx::SignerListSet(tx) => { - &tx.sequence - } - } - } - pub fn sequence_number_increment(&self, status: TransactionStatus) -> u32 { - if status == TransactionStatus::Pending || status == TransactionStatus::Inconclusive { - return 0; - } - - match self { - XRPLUnsignedTx::Payment(tx ) => { - match tx.sequence { - Sequence::Plain(_) => 1, - Sequence::Ticket(_) => 0, - } - } - XRPLUnsignedTx::SignerListSet(tx) => { - match tx.sequence { - Sequence::Plain(_) => 1, - Sequence::Ticket(_) => 0, - } - }, - XRPLUnsignedTx::TicketCreate(tx) => { - match status { - TransactionStatus::Succeeded => tx.ticket_count + 1, - TransactionStatus::FailedOnChain => 1, - TransactionStatus::Inconclusive | - TransactionStatus::Pending => unreachable!(), - } - }, - } - } -} - -#[cw_serde] -pub struct XRPLPaymentTx { - pub account: XRPLAccountId, - pub fee: u64, - pub sequence: Sequence, - pub amount: XRPLPaymentAmount, - pub destination: XRPLAccountId, - pub multisig_session_id: Uint64 -} - -#[cw_serde] -pub struct XRPLSignerListSetTx { - pub account: XRPLAccountId, - pub fee: u64, - pub sequence: Sequence, - pub signer_quorum: u32, - pub signer_entries: Vec, - pub multisig_session_id: Uint64 -} - -#[cw_serde] -pub struct XRPLTicketCreateTx { - pub account: XRPLAccountId, - pub fee: u64, - pub sequence: Sequence, - pub ticket_count: u32, - pub multisig_session_id: Uint64 -} - -#[cw_serde] -pub struct XRPLAccountId([u8; 20]); - -impl XRPLAccountId { - pub const fn to_bytes(&self) -> [u8; 20] { - return self.0; - } - - pub fn to_string(&self) -> String { - let address_type_prefix: &[u8] = &[0x00]; - let payload = [address_type_prefix, &self.to_bytes()].concat(); - - let checksum_hash1 = Sha256::digest(payload.clone()); - let checksum_hash2 = Sha256::digest(checksum_hash1); - let checksum = &checksum_hash2[0..4]; - - bs58::encode([payload, checksum.to_vec()].concat()) - .with_alphabet(bs58::Alphabet::RIPPLE) - .into_string() - } -} - -impl From<&PublicKey> for XRPLAccountId { - fn from(pub_key: &PublicKey) -> Self { - let public_key_hex: HexBinary = pub_key.clone().into(); - - assert!(public_key_hex.len() == 33); - - let public_key_inner_hash = Sha256::digest(public_key_hex); - let account_id = Ripemd160::digest(public_key_inner_hash); - - return XRPLAccountId(account_id.into()); - } -} - -impl TryFrom<&str> for XRPLAccountId { - type Error = ContractError; - - fn try_from(address: &str) -> Result { - let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().map_err(|_| ContractError::InvalidAddress)?; - // .map_err(|_| ContractError::InvalidAddress)?; - if res.len() != 25 { - return Err(ContractError::InvalidAddress); - } - let mut buffer = [0u8; 20]; - buffer.copy_from_slice(&res[1..21]); - return Ok(XRPLAccountId(buffer)) - } -} - -#[cw_serde] -pub struct XRPLSigner { - pub account: XRPLAccountId, - pub txn_signature: HexBinary, - pub signing_pub_key: PublicKey, -} - -#[cw_serde] -pub struct XRPLSignedTransaction { - pub unsigned_tx: XRPLUnsignedTx, - pub signers: Vec -} - pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { let last_assigned_ticket_number: u32 = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; // TODO: handle no available tickets @@ -501,8 +326,8 @@ mod tests { #[test] fn test_account_id_to_bytes_address() { - assert_eq!("rrrrrrrrrrrrrrrrrrrrrhoLvTp", XRPLAccountId([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).to_string()); - assert_eq!("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi", XRPLAccountId([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]).to_string()); + assert_eq!("rrrrrrrrrrrrrrrrrrrrrhoLvTp", XRPLAccountId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).to_string()); + assert_eq!("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi", XRPLAccountId::from_bytes([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]).to_string()); } #[test] fn ed25519_public_key_to_xrpl_address() -> Result<(), ContractError> { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index 19ba2f429..333c08b9b 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -1,13 +1,11 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{HexBinary, Uint128}; +use cosmwasm_std::{HexBinary, Uint64}; use multisig::key::PublicKey; -use crate::{error::ContractError, xrpl_multisig::{Sequence, XRPLAccountId, XRPLPaymentAmount, XRPLPaymentTx, XRPLSignedTransaction, XRPLSigner, XRPLSignerEntry, XRPLSignerListSetTx, XRPLTicketCreateTx, XRPLUnsignedTx}}; +use crate::{error::ContractError, types::*}; const PAYMENT_TX_TYPE: u16 = 0; const TICKET_CREATE_TX_TYPE: u16 = 10; const SIGNER_LIST_SET_TX_TYPE: u16 = 12; - const POSITIVE_BIT: u64 = 0x4000000000000000; pub trait XRPLSerialize { @@ -25,6 +23,7 @@ impl XRPLSerialize for u16 { impl XRPLSerialize for u32 { const TYPE_CODE: u8 = 2; + fn xrpl_serialize(&self) -> Result, ContractError> { Ok(self.to_be_bytes().to_vec()) } @@ -64,6 +63,7 @@ pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { impl XRPLSerialize for HexBinary { const TYPE_CODE: u8 = 7; + fn xrpl_serialize(&self) -> Result, ContractError> { let encoded_length = encode_length(self.len())?; let mut result = Vec::new(); @@ -210,6 +210,10 @@ impl XRPLSerialize for XRPLAccountId { } } +pub fn hex_encode_session_id(session_id: Uint64) -> HexBinary { + HexBinary::from(session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect::>()) +} + impl TryInto for XRPLPaymentTx { type Error = ContractError; @@ -223,11 +227,7 @@ impl TryInto for XRPLPaymentTx { obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; obj.add_field(Field::Account, &self.account)?; obj.add_field(Field::Destination, &self.destination)?; - - let memo_data: Vec = self.multisig_session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(memo)]})?; - + obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]})?; Ok(obj) } } @@ -398,78 +398,6 @@ pub fn field_id(type_code: u8, field_code: u8) -> Vec { } } -const MIN_MANTISSA: u64 = 1_000_000_000_000_000; -const MAX_MANTISSA: u64 = 10_000_000_000_000_000 - 1; -const MIN_EXPONENT: i64 = -96; -const MAX_EXPONENT: i64 = 80; - -// XRPLTokenAmount always in canonicalized XRPL mantissa-exponent format, -// such that MIN_MANTISSA <= mantissa <= MAX_MANTISSA (or equal to zero), MIN_EXPONENT <= exponent <= MAX_EXPONENT, -// In XRPL generally it can be decimal and even negative (!) but in our case that doesn't apply. -#[cw_serde] -pub struct XRPLTokenAmount { - mantissa: u64, - exponent: i64 -} - -impl XRPLTokenAmount { - pub fn new(mantissa: u64, exponent: i64) -> Self { - assert!(mantissa == 0 || (MIN_MANTISSA <= mantissa && mantissa <= MAX_MANTISSA && MIN_EXPONENT <= exponent && exponent <= MAX_EXPONENT)); - Self { mantissa, exponent } - } - - pub fn to_bytes(&self) -> [u8; 8] { - if self.mantissa == 0 { - 0x8000000000000000u64.to_be_bytes() - } else { - // not xrp-bit | positive bit | 8 bits exponent | 54 bits mantissa - (0xC000000000000000u64 | ((self.exponent + 97) as u64) << 54 | self.mantissa).to_be_bytes() - } - } -} - -impl TryFrom for XRPLTokenAmount { - type Error = ContractError; - - fn try_from(amount: Uint128) -> Result { - let (mantissa, exponent) = canonicalize_mantissa(amount)?; - Ok(XRPLTokenAmount::new(mantissa, exponent)) - } -} - -// always called when XRPLTokenAmount instantiated -// see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 -pub fn canonicalize_mantissa(mut mantissa: Uint128) -> Result<(u64, i64), ContractError> { - let mut exponent = 0i64; - - let ten = Uint128::from(10u128); - - while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { - mantissa *= ten; - exponent -= 1; - } - - while mantissa > MAX_MANTISSA.into() && exponent > MIN_EXPONENT { - if exponent > MAX_EXPONENT { - return Err(ContractError::InvalidAmount { reason: "overflow".to_string() }); - } - mantissa /= ten; - exponent += 1; - } - - if exponent < MIN_EXPONENT || mantissa < MIN_MANTISSA.into() { - return Ok((0, 1)); - } - - if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA.into() { - return Err(ContractError::InvalidAmount { reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); - } - - let mantissa = u64::from_be_bytes(mantissa.to_be_bytes()[8..].try_into().unwrap()); - - return Ok((mantissa, exponent)); -} - #[cfg(test)] mod tests { use cosmwasm_std::{Uint128, Uint64}; @@ -529,7 +457,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, currency: "USD".to_string(), - }, XRPLTokenAmount { mantissa: MIN_MANTISSA, exponent: MIN_EXPONENT }) + }, XRPLTokenAmount::new(MIN_MANTISSA, MIN_EXPONENT)) .xrpl_serialize()? ); // maximum amount @@ -538,7 +466,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, currency: "USD".to_string(), - }, XRPLTokenAmount { mantissa: MAX_MANTISSA, exponent: MAX_EXPONENT }) + }, XRPLTokenAmount::new(MAX_MANTISSA, MAX_EXPONENT)) .xrpl_serialize()? ); // currency can contain non-alphanumeric ascii letters @@ -653,7 +581,7 @@ mod tests { currency: "JPY".to_string(), issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".try_into()?, }, - XRPLTokenAmount { mantissa: 3369568318000000u64, exponent: -16 } + XRPLTokenAmount::new(3369568318000000u64, -16) ), destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, multisig_session_id: Uint64::from(1u8), diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 8c5e7b77d..1a05724e9 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -19,7 +19,7 @@ use multisig::{ worker_set::WorkerSet, }; use multisig_prover::encoding::{make_operators, Encoder}; -use xrpl_multisig_prover::{types::XRPLToken, xrpl_multisig::XRPLAccountId}; +use xrpl_multisig_prover::types::{XRPLToken, XRPLAccountId}; use sha3::{Digest, Keccak256}; use tofn::ecdsa::KeyPair; From 6d8bed5daddcf93c9bbb3f6b2a212b289337f766 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Mon, 19 Feb 2024 15:43:36 +0100 Subject: [PATCH 57/80] xrpl: add missing import --- contracts/xrpl-multisig-prover/src/xrpl_multisig.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index bed6bcf9d..bd9c88f18 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -5,6 +5,7 @@ use connection_router::state::CrossChainId; use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use sha2::{Sha512, Digest, Sha256}; +use multisig::key::PublicKey; use crate::{ axelar_workers::{AxelarSigner, WorkerSet}, error::ContractError, state::{Config, AVAILABLE_TICKETS, CONFIRMED_TRANSACTIONS, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, LATEST_SEQUENTIAL_TX_HASH, MESSAGE_ID_TO_TICKET, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, TRANSACTION_INFO}, types::* From 8495fc24bca1a99d52afe5c18fadd07e58f45072 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 20 Feb 2024 14:03:18 +0200 Subject: [PATCH 58/80] xrpl multisig prover: SerializedField struct, from/to_json, code clarity --- .../xrpl-multisig-prover/src/contract.rs | 8 +- contracts/xrpl-multisig-prover/src/querier.rs | 4 +- contracts/xrpl-multisig-prover/src/reply.rs | 4 +- contracts/xrpl-multisig-prover/src/types.rs | 14 +- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 250 +++++++++--------- .../src/xrpl_serialize.rs | 77 +++--- 6 files changed, 178 insertions(+), 179 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 7a2a7f6ec..1f77dc64d 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -6,7 +6,7 @@ use connection_router::{state::{Address, ChainName, CrossChainId}, Message}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ entry_point, Storage, wasm_execute, SubMsg, Reply, - DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_binary, Deps, StdResult, Binary, Addr, HexBinary, + DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_json_binary, Deps, StdResult, Binary, Addr, HexBinary, }; use multisig::types::MultisigState; @@ -382,12 +382,12 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GetProof { multisig_session_id, - } => to_binary(&query::get_proof(deps.storage, querier, &multisig_session_id)?), + } => to_json_binary(&query::get_proof(deps.storage, querier, &multisig_session_id)?), QueryMsg::VerifyMessage { multisig_session_id, public_key, signature - } => to_binary(&query::verify_message(deps.storage, &multisig_session_id, &public_key, &signature)?), - QueryMsg::GetWorkerSet {} => to_binary(&query::get_worker_set(deps.storage)?), + } => to_json_binary(&query::verify_message(deps.storage, &multisig_session_id, &public_key, &signature)?), + QueryMsg::GetWorkerSet {} => to_json_binary(&query::get_worker_set(deps.storage)?), } } diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index b39d6d9c3..baafac693 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -3,7 +3,7 @@ use std::str::FromStr; #[cfg(not(feature = "library"))] use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; use cosmwasm_std::{ - to_binary, QueryRequest, WasmQuery, QuerierWrapper, Uint64, + to_json_binary, QueryRequest, WasmQuery, QuerierWrapper, Uint64, }; use multisig::{key::PublicKey, msg::Multisig}; use axelar_wasm_std::VerificationStatus; @@ -22,7 +22,7 @@ fn query(querier: QuerierWrapper, contract_addr: String, query_msg: &T) -> where U: DeserializeOwned, T: Serialize + ?Sized { querier.query(&QueryRequest::Wasm(WasmQuery::Smart { contract_addr, - msg: to_binary(&query_msg)?, + msg: to_json_binary(&query_msg)?, })).map_err(ContractError::from) } diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 8e5fcaa24..6655b5ca9 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{from_binary, Attribute, DepsMut, HexBinary, Reply, Response, Uint64}; +use cosmwasm_std::{from_json, Attribute, DepsMut, HexBinary, Reply, Response, Uint64}; use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; use crate::{ @@ -12,7 +12,7 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result) -> StdResult { - Ok(from_binary(&value.into()).expect("violated invariant: TxHash is not deserializable")) + Ok(from_json(&Binary::from(value)).expect("violated invariant: TxHash is not deserializable")) } } @@ -158,6 +159,15 @@ pub struct XRPLSignerEntry { pub signer_weight: u16, } +impl From for XRPLSignerEntry { + fn from(signer: AxelarSigner) -> Self { + Self { + account: XRPLAccountId::from(&signer.pub_key), + signer_weight: signer.weight, + } + } +} + #[cw_serde] pub enum XRPLUnsignedTx { Payment(XRPLPaymentTx), diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index bd9c88f18..16abc7f0f 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,65 +1,13 @@ -use std::collections::BTreeSet; - use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use sha2::{Sha512, Digest, Sha256}; -use multisig::key::PublicKey; use crate::{ - axelar_workers::{AxelarSigner, WorkerSet}, error::ContractError, state::{Config, AVAILABLE_TICKETS, CONFIRMED_TRANSACTIONS, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, LATEST_SEQUENTIAL_TX_HASH, MESSAGE_ID_TO_TICKET, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, TRANSACTION_INFO}, types::* + axelar_workers::WorkerSet, error::ContractError, state::{Config, AVAILABLE_TICKETS, CONFIRMED_TRANSACTIONS, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, LATEST_SEQUENTIAL_TX_HASH, MESSAGE_ID_TO_TICKET, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, TRANSACTION_INFO}, types::* }; -pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { - let last_assigned_ticket_number: u32 = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; - // TODO: handle no available tickets - let available_tickets = AVAILABLE_TICKETS.load(storage)?; - - // find next largest in available, otherwise use available_tickets[0] - // TODO: handle IndexOutOfBounds error on available_tickets[0] - let ticket_number = available_tickets.iter().find(|&x| x > &last_assigned_ticket_number).unwrap_or(&available_tickets[0]); - Ok(*ticket_number) -} - -pub fn available_ticket_count(storage: &mut dyn Storage) -> Result { - let available_tickets = AVAILABLE_TICKETS.load(storage)?; - let ticket_count = 250 - (available_tickets.len() as u32); - Ok(ticket_count) -} - - -pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; -pub const HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; - -pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result { - let encoded_unsigned_tx = serde_json::to_vec(unsigned_tx).map_err(|_| ContractError::FailedToSerialize)?; - - let d = Sha256::digest(encoded_unsigned_tx); - Ok(TxHash(HexBinary::from(d.to_vec()))) -} - -pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result { - Ok(TxHash(HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())))) -} - -pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_address: &XRPLAccountId) -> Result<[u8; 32], ContractError> { - let msg = &[encoded_unsigned_tx.to_vec(), signer_address.to_bytes().into()].concat(); - Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)) -} - -pub fn xrpl_hash( - prefix: [u8; 4], - tx_blob: &[u8], -) -> [u8; 32] { - let mut hasher = Sha512::new_with_prefix(prefix); - hasher.update(tx_blob); - let hash: [u8; 64] = hasher.finalize().into(); - let mut half_hash: [u8; 32] = [0; 32]; - half_hash.copy_from_slice(&hash[..32]); - half_hash -} - fn issue_tx( storage: &mut dyn Storage, tx: XRPLUnsignedTx, @@ -151,7 +99,7 @@ pub fn issue_signer_list_set( fee: config.xrpl_fee, sequence: Sequence::Plain(sequence_number.clone()), signer_quorum: workers.quorum, - signer_entries: make_xrpl_signer_entries(workers.signers)?, + signer_entries: workers.signers.into_iter().map(|worker| XRPLSignerEntry::from(worker)).collect(), multisig_session_id, }; @@ -162,84 +110,6 @@ pub fn issue_signer_list_set( ) } -fn make_xrpl_signer_entries(signers: BTreeSet) -> Result, ContractError> { - signers - .into_iter() - .map( - |worker| -> Result { - Ok(XRPLSignerEntry { - account: XRPLAccountId::from(&worker.pub_key), - signer_weight: worker.weight, - }) - } - ).collect() -} - - -fn get_next_sequence_number(storage: &dyn Storage) -> Result { - match load_latest_sequential_tx_info(storage)? { - Some(latest_sequential_tx_info) if latest_sequential_tx_info.status == TransactionStatus::Pending => { - Ok(latest_sequential_tx_info.unsigned_contents.sequence().clone().into()) - }, - _ => NEXT_SEQUENCE_NUMBER.load(storage).map_err(|e| e.into()) - } -} - -fn load_latest_sequential_tx_info( - storage: &dyn Storage, -) -> Result, ContractError> { - LATEST_SEQUENTIAL_TX_HASH - .may_load(storage)? - .map_or(Ok(None), |tx_hash| Ok(TRANSACTION_INFO.may_load(storage, &tx_hash)?)) -} - -fn mark_tickets_available(storage: &mut dyn Storage, tickets: impl Iterator) -> Result<(), ContractError> { - AVAILABLE_TICKETS.update(storage, |available_tickets| -> Result<_, ContractError> { - let mut new_available_tickets = available_tickets.clone(); - new_available_tickets.extend(tickets); - Ok(new_available_tickets) - })?; - Ok(()) -} - -fn mark_ticket_unavailable(storage: &mut dyn Storage, ticket: u32) -> Result<(), ContractError> { - AVAILABLE_TICKETS.update(storage, |available_tickets| -> Result<_, ContractError> { - Ok(available_tickets - .into_iter() - .filter(|&x| x != ticket) - .collect()) - })?; - Ok(()) -} - -pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { - let xrpl_signers: Vec = axelar_signers - .iter() - .map(|(axelar_signer, signature)| -> Result { - let txn_signature = match signature { - // TODO: use unwrapped signature instead of ignoring it - multisig::key::Signature::Ecdsa(_) | - multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( - &ecdsa::Signature::try_from(signature.clone().as_ref()) - .map_err(|_| ContractError::FailedToEncodeSignature)? - ).to_vec()), - _ => unimplemented!("Unsupported signature type"), - }; - - Ok(XRPLSigner { - account: XRPLAccountId::from(&axelar_signer.pub_key), - signing_pub_key: axelar_signer.pub_key.clone().into(), - txn_signature, - }) - }) - .collect::, ContractError>>()?; - - Ok(XRPLSignedTransaction { - unsigned_tx, - signers: xrpl_signers, - }) -} - pub fn update_tx_status( storage: &mut dyn Storage, axelar_multisig_address: impl Into, @@ -299,6 +169,8 @@ pub fn update_tx_status( Ok(res) } +// TICKET / SEQUENCE NUMBER ASSIGNEMENT LOGIC + // A message ID can be ticketed a different ticket number // only if the previous ticket number has been consumed // by a TX that doesn't correspond to this message. @@ -321,9 +193,123 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId Ok(new_ticket_number) } + +pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { + let last_assigned_ticket_number: u32 = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; + // TODO: handle no available tickets + let available_tickets = AVAILABLE_TICKETS.load(storage)?; + + // find next largest in available, otherwise use available_tickets[0] + // TODO: handle IndexOutOfBounds error on available_tickets[0] + let ticket_number = available_tickets.iter().find(|&x| x > &last_assigned_ticket_number).unwrap_or(&available_tickets[0]); + Ok(*ticket_number) +} + +pub fn available_ticket_count(storage: &mut dyn Storage) -> Result { + let available_tickets = AVAILABLE_TICKETS.load(storage)?; + let ticket_count = 250 - (available_tickets.len() as u32); + Ok(ticket_count) +} + +fn get_next_sequence_number(storage: &dyn Storage) -> Result { + match load_latest_sequential_tx_info(storage)? { + Some(latest_sequential_tx_info) if latest_sequential_tx_info.status == TransactionStatus::Pending => { + Ok(latest_sequential_tx_info.unsigned_contents.sequence().clone().into()) + }, + _ => NEXT_SEQUENCE_NUMBER.load(storage).map_err(|e| e.into()) + } +} + +fn load_latest_sequential_tx_info( + storage: &dyn Storage, +) -> Result, ContractError> { + LATEST_SEQUENTIAL_TX_HASH + .may_load(storage)? + .map_or(Ok(None), |tx_hash| Ok(TRANSACTION_INFO.may_load(storage, &tx_hash)?)) +} + +fn mark_tickets_available(storage: &mut dyn Storage, tickets: impl Iterator) -> Result<(), ContractError> { + AVAILABLE_TICKETS.update(storage, |available_tickets| -> Result<_, ContractError> { + let mut new_available_tickets = available_tickets.clone(); + new_available_tickets.extend(tickets); + Ok(new_available_tickets) + })?; + Ok(()) +} + +fn mark_ticket_unavailable(storage: &mut dyn Storage, ticket: u32) -> Result<(), ContractError> { + AVAILABLE_TICKETS.update(storage, |available_tickets| -> Result<_, ContractError> { + Ok(available_tickets + .into_iter() + .filter(|&x| x != ticket) + .collect()) + })?; + Ok(()) +} + +// HASHING LOGIC + +pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; +pub const HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; + +pub fn xrpl_hash( + prefix: [u8; 4], + tx_blob: &[u8], +) -> [u8; 32] { + let mut hasher = Sha512::new_with_prefix(prefix); + hasher.update(tx_blob); + let hash: [u8; 64] = hasher.finalize().into(); + hash[..32].try_into().unwrap() +} + +pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result { + let encoded_unsigned_tx = serde_json::to_vec(unsigned_tx).map_err(|_| ContractError::FailedToSerialize)?; + + let d = Sha256::digest(encoded_unsigned_tx); + Ok(TxHash(HexBinary::from(d.to_vec()))) +} + +pub fn compute_signed_tx_hash(encoded_signed_tx: Vec) -> Result { + Ok(TxHash(HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())))) +} + +pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_address: &XRPLAccountId) -> Result<[u8; 32], ContractError> { + let msg = &[encoded_unsigned_tx.to_vec(), signer_address.to_bytes().into()].concat(); + Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)) +} + +pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { + let xrpl_signers: Vec = axelar_signers + .iter() + .map(|(axelar_signer, signature)| -> Result { + let txn_signature = match signature { + // TODO: use unwrapped signature instead of ignoring it + multisig::key::Signature::Ecdsa(_) | + multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( + &ecdsa::Signature::try_from(signature.clone().as_ref()) + .map_err(|_| ContractError::FailedToEncodeSignature)? + ).to_vec()), + _ => unimplemented!("Unsupported signature type"), + }; + + Ok(XRPLSigner { + account: XRPLAccountId::from(&axelar_signer.pub_key), + signing_pub_key: axelar_signer.pub_key.clone().into(), + txn_signature, + }) + }) + .collect::, ContractError>>()?; + + Ok(XRPLSignedTransaction { + unsigned_tx, + signers: xrpl_signers, + }) +} + #[cfg(test)] mod tests { use super::*; + use multisig::key::PublicKey; #[test] fn test_account_id_to_bytes_address() { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index 333c08b9b..0cb52d395 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use cosmwasm_std::{HexBinary, Uint64}; use multisig::key::PublicKey; @@ -42,11 +44,7 @@ impl XRPLSerialize for XRPLPaymentAmount { } }, XRPLPaymentAmount::Token(token, amount) => { - let mut result = Vec::new(); - result.extend(amount.to_bytes()); - result.extend(currency_to_bytes(&token.currency)?); - result.extend(token.issuer.to_bytes()); - Ok(result) + Ok([amount.to_bytes().as_ref(), currency_to_bytes(&token.currency)?.as_ref(), token.issuer.to_bytes().as_ref()].concat()) } } } @@ -65,11 +63,7 @@ impl XRPLSerialize for HexBinary { const TYPE_CODE: u8 = 7; fn xrpl_serialize(&self) -> Result, ContractError> { - let encoded_length = encode_length(self.len())?; - let mut result = Vec::new(); - result.extend(encoded_length); - result.extend(self.to_vec()); - Ok(result) + Ok([encode_length(self.len())?, self.to_vec()].concat()) } } @@ -237,7 +231,6 @@ impl TryInto for XRPLSignerListSetTx { fn try_into(self) -> Result { let mut obj = XRPLObject::new(); - obj.add_field(Field::TransactionType, &SIGNER_LIST_SET_TX_TYPE)?; obj.add_field(Field::Flags, &0u32)?; // flags obj.add_sequence(&self.sequence)?; @@ -245,13 +238,8 @@ impl TryInto for XRPLSignerListSetTx { obj.add_field(Field::Fee, &XRPLPaymentAmount::Drops(self.fee))?; obj.add_field(Field::Account, &self.account)?; obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; - obj.add_field(Field::SignerEntries, &XRPLArray{ field: Field::SignerEntry, items: self.signer_entries.clone() })?; - - let memo_data: Vec = self.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(memo)]})?; - + obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]})?; Ok(obj) } } @@ -268,11 +256,7 @@ impl TryInto for XRPLTicketCreateTx { obj.add_field(Field::Fee, &XRPLPaymentAmount::Drops(self.fee))?; obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; obj.add_field(Field::Account, &self.account)?; - - let memo_data: Vec = self.multisig_session_id.to_be_bytes().into_iter().skip_while(|&byte| byte == 0).collect(); - let memo = HexBinary::from_hex(hex::encode(memo_data).as_ref())?; - obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(memo)]})?; - + obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]})?; Ok(obj) } } @@ -325,9 +309,37 @@ impl XRPLSerialize for XRPLArray { } } +#[derive(Debug, Clone, PartialEq, Eq)] +struct SerializedField { + type_code: u8, + field_code: u8, + serialized_value: Vec +} + +impl SerializedField { + fn new(type_code: u8, field_code: u8, serialized_value: Vec) -> Self { + Self { type_code, field_code, serialized_value } + } + fn to_vec(&self) -> Vec { + return [field_id(self.type_code, self.field_code), self.clone().serialized_value].concat() + } +} + +impl PartialOrd for SerializedField { + fn partial_cmp(&self, other: &SerializedField) -> Option { + return (self.type_code, self.field_code).partial_cmp(&(other.type_code, other.field_code)) + } +} + +impl Ord for SerializedField { + fn cmp(&self, other: &SerializedField) -> Ordering { + return (self.type_code, self.field_code).cmp(&(other.type_code, other.field_code)) + } +} + #[derive(Debug, Clone)] pub struct XRPLObject { - fields: Vec<(u8, u8, Vec)> + fields: Vec } impl XRPLObject { @@ -338,7 +350,7 @@ impl XRPLObject { } pub fn add_field(&mut self, field: Field, value: &T) -> Result<(), ContractError> { - self.fields.push((T::TYPE_CODE, field.to_u8(), value.xrpl_serialize()?)); + self.fields.push(SerializedField::new(T::TYPE_CODE, field.to_u8(), value.xrpl_serialize()?)); Ok(()) } @@ -359,18 +371,9 @@ impl XRPLSerialize for XRPLObject { const TYPE_CODE: u8 = 14; fn xrpl_serialize(&self) -> Result, ContractError> { - let mut tmp: Vec<(u8, u8, Vec)> = self.fields.clone(); - tmp.sort_by(|a, b| { (a.0, a.1).cmp(&(b.0, b.1)) }); - let res = tmp.into_iter() - .map(|f| { - let mut res = Vec::new(); - res.extend(field_id(f.0, f.1)); - res.extend(f.2); - return res; - }) - .collect::>>() - .concat(); - Ok(res) + let mut fields: Vec = self.fields.clone(); + fields.sort(); + Ok(fields.into_iter().map(|f| { f.to_vec() }).collect::>>().concat()) } } @@ -547,7 +550,7 @@ mod tests { // { "NetworkID": 0 } assert_hex_eq!( "2100000000", - XRPLObject { fields: vec![(2, 1, 0u32.xrpl_serialize()?)]} + XRPLObject { fields: vec![SerializedField::new(2, 1, 0u32.xrpl_serialize()?)]} .xrpl_serialize()? ); // empty array From 63f07aea2af803bab33bd1ada89787ef4c59ac13 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 20 Feb 2024 21:03:41 +0200 Subject: [PATCH 59/80] xrpl multisig prover: improve code quality on serialization --- .../xrpl-multisig-prover/src/contract.rs | 4 - contracts/xrpl-multisig-prover/src/types.rs | 54 +++- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 10 +- .../src/xrpl_serialize.rs | 264 +++++++++--------- integration-tests/tests/test_utils/mod.rs | 2 +- 5 files changed, 179 insertions(+), 155 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 1f77dc64d..e3832bbbb 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -111,10 +111,6 @@ fn register_token( denom: String, token: &XRPLToken, ) -> Result { - if token.currency == "XRP" || token.currency.len() != 3 { - return Err(ContractError::InvalidTokenDenom); - } - require_governance(&config.governance_address, sender)?; TOKENS.save(storage, &denom, token)?; Ok(Response::default()) diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 6632385d0..d15a24ea4 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -127,7 +127,7 @@ impl Operator { #[cw_serde] pub struct XRPLToken { pub issuer: XRPLAccountId, - pub currency: String, + pub currency: XRPLCurrency, } #[cw_serde] @@ -139,16 +139,16 @@ pub enum XRPLPaymentAmount { } #[cw_serde] -pub enum Sequence { +pub enum XRPLSequence { Plain(u32), Ticket(u32), } -impl Into for Sequence { +impl Into for XRPLSequence { fn into(self) -> u32 { match self { - Sequence::Plain(sequence) => sequence, - Sequence::Ticket(ticket) => ticket, + XRPLSequence::Plain(sequence) => sequence, + XRPLSequence::Ticket(ticket) => ticket, } } } @@ -176,7 +176,7 @@ pub enum XRPLUnsignedTx { } impl XRPLUnsignedTx { - pub fn sequence(&self) -> &Sequence { + pub fn sequence(&self) -> &XRPLSequence { match self { XRPLUnsignedTx::Payment(tx) => { &tx.sequence @@ -197,14 +197,14 @@ impl XRPLUnsignedTx { match self { XRPLUnsignedTx::Payment(tx ) => { match tx.sequence { - Sequence::Plain(_) => 1, - Sequence::Ticket(_) => 0, + XRPLSequence::Plain(_) => 1, + XRPLSequence::Ticket(_) => 0, } } XRPLUnsignedTx::SignerListSet(tx) => { match tx.sequence { - Sequence::Plain(_) => 1, - Sequence::Ticket(_) => 0, + XRPLSequence::Plain(_) => 1, + XRPLSequence::Ticket(_) => 0, } }, XRPLUnsignedTx::TicketCreate(tx) => { @@ -223,7 +223,7 @@ impl XRPLUnsignedTx { pub struct XRPLPaymentTx { pub account: XRPLAccountId, pub fee: u64, - pub sequence: Sequence, + pub sequence: XRPLSequence, pub amount: XRPLPaymentAmount, pub destination: XRPLAccountId, pub multisig_session_id: Uint64 @@ -233,7 +233,7 @@ pub struct XRPLPaymentTx { pub struct XRPLSignerListSetTx { pub account: XRPLAccountId, pub fee: u64, - pub sequence: Sequence, + pub sequence: XRPLSequence, pub signer_quorum: u32, pub signer_entries: Vec, pub multisig_session_id: Uint64 @@ -243,7 +243,7 @@ pub struct XRPLSignerListSetTx { pub struct XRPLTicketCreateTx { pub account: XRPLAccountId, pub fee: u64, - pub sequence: Sequence, + pub sequence: XRPLSequence, pub ticket_count: u32, pub multisig_session_id: Uint64 } @@ -315,6 +315,34 @@ pub struct XRPLSignedTransaction { pub signers: Vec } +#[cw_serde] +pub struct XRPLCurrency(String); + +impl XRPLCurrency { + pub fn to_bytes(self) -> [u8; 20] { + let mut buffer = [0u8; 20]; + buffer[12..15].copy_from_slice(self.to_string().as_bytes()); + buffer + } + + // Convert the CurrencyCode to a String + fn to_string(self) -> String { + self.0 + } +} + +const ALLOWED_CURRENCY_CHARS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!@#$%^&*<>(){}[]|"; + +impl TryFrom for XRPLCurrency { + type Error = ContractError; + + fn try_from(s: String) -> Result { + if s.len() != 3 || s == "XRP" || !s.chars().all(|c| ALLOWED_CURRENCY_CHARS.contains(c)) { + return Err(ContractError::InvalidCurrency); + } + Ok(XRPLCurrency(s)) + } +} pub const MIN_MANTISSA: u64 = 1_000_000_000_000_000; pub const MAX_MANTISSA: u64 = 10_000_000_000_000_000 - 1; diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 16abc7f0f..32bef6c53 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -26,10 +26,10 @@ fn issue_tx( )?; match tx.sequence() { - Sequence::Ticket(ticket_number) => { + XRPLSequence::Ticket(ticket_number) => { LAST_ASSIGNED_TICKET_NUMBER.save(storage, &ticket_number)?; }, - Sequence::Plain(_) => { + XRPLSequence::Plain(_) => { LATEST_SEQUENTIAL_TX_HASH.save(storage, &tx_hash)?; }, }; @@ -50,7 +50,7 @@ pub fn issue_payment( let tx = XRPLPaymentTx { account: config.xrpl_multisig_address.as_str().try_into()?, fee: config.xrpl_fee, - sequence: Sequence::Ticket(ticket_number), + sequence: XRPLSequence::Ticket(ticket_number), multisig_session_id: multisig_session_id.clone(), amount: amount.clone(), destination: XRPLAccountId::try_from(destination.as_str())? @@ -74,7 +74,7 @@ pub fn issue_ticket_create( let tx = XRPLTicketCreateTx { account: config.xrpl_multisig_address.as_str().try_into()?, fee: config.xrpl_fee, - sequence: Sequence::Plain(sequence_number.clone()), + sequence: XRPLSequence::Plain(sequence_number.clone()), ticket_count, multisig_session_id, }; @@ -97,7 +97,7 @@ pub fn issue_signer_list_set( let tx = XRPLSignerListSetTx { account: config.xrpl_multisig_address.as_str().try_into()?, fee: config.xrpl_fee, - sequence: Sequence::Plain(sequence_number.clone()), + sequence: XRPLSequence::Plain(sequence_number.clone()), signer_quorum: workers.quorum, signer_entries: workers.signers.into_iter().map(|worker| XRPLSignerEntry::from(worker)).collect(), multisig_session_id, diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index 0cb52d395..e1db6c4ef 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -10,6 +10,74 @@ const TICKET_CREATE_TX_TYPE: u16 = 10; const SIGNER_LIST_SET_TX_TYPE: u16 = 12; const POSITIVE_BIT: u64 = 0x4000000000000000; +#[derive(Clone)] +pub enum Field { + SigningPubKey, + TxnSignature, + Account, + SignerWeight, + MemoData, + TransactionType, + Flags, + Sequence, + TicketSequence, + Amount, + Fee, + Destination, + SignerQuorum, + SignerEntries, + SignerEntry, + Memos, + Memo, + TicketCount, + Signers, + Signer +} + +impl Field { + const fn to_u8(self) -> u8 { + match self { + Field::SigningPubKey => 3, + Field::TxnSignature => 4, + Field::Account => 1, + Field::SignerWeight => 3, + Field::MemoData => 13, + Field::TransactionType => 2, + Field::Flags => 2, + Field::Amount => 1, + Field::Fee => 8, + Field::Destination => 3, + Field::Sequence => 4, + Field::TicketSequence => 41, + Field::SignerQuorum => 35, + Field::SignerEntries => 4, + Field::SignerEntry => 11, + Field::Memos => 9, + Field::Memo => 10, + Field::TicketCount => 40, + Field::Signers => 3, + Field::Signer => 16 + } + } +} + +use Field::*; + +#[macro_export] +macro_rules! xrpl_json { + // Match a JSON-like structure. + ({ $($key:ident: $value:expr),* $(,)? }) => {{ + let mut obj = XRPLObject::new(); + + // Process each key-value pair. + $( + obj.add_field($key, &$value)?; + )* + + obj + }}; +} + pub trait XRPLSerialize { const TYPE_CODE: u8; fn xrpl_serialize(&self) -> Result, ContractError>; @@ -44,21 +112,12 @@ impl XRPLSerialize for XRPLPaymentAmount { } }, XRPLPaymentAmount::Token(token, amount) => { - Ok([amount.to_bytes().as_ref(), currency_to_bytes(&token.currency)?.as_ref(), token.issuer.to_bytes().as_ref()].concat()) + Ok([amount.to_bytes().as_ref(), token.currency.clone().to_bytes().as_ref(), token.issuer.to_bytes().as_ref()].concat()) } } } } -pub fn currency_to_bytes(currency: &String) -> Result<[u8; 20], ContractError> { - if currency.len() != 3 || !currency.is_ascii() || currency == "XRP" { - return Err(ContractError::InvalidCurrency); - } - let mut buffer = [0u8; 20]; - buffer[12..15].copy_from_slice(currency.as_bytes()); - Ok(buffer) -} - impl XRPLSerialize for HexBinary { const TYPE_CODE: u8 = 7; @@ -99,66 +158,15 @@ impl XRPLSerialize for PublicKey { } } -#[derive(Clone)] -pub enum Field { - SigningPubKey, - TxnSignature, - Account, - SignerWeight, - MemoData, - TransactionType, - Flags, - Sequence, - TicketSequence, - Amount, - Fee, - Destination, - SignerQuorum, - SignerEntries, - SignerEntry, - Memos, - Memo, - TicketCount, - Signers, - Signer -} - -impl Field { - const fn to_u8(self) -> u8 { - match self { - Field::SigningPubKey => 3, - Field::TxnSignature => 4, - Field::Account => 1, - Field::SignerWeight => 3, - Field::MemoData => 13, - Field::TransactionType => 2, - Field::Flags => 2, - Field::Amount => 1, - Field::Fee => 8, - Field::Destination => 3, - Field::Sequence => 4, - Field::TicketSequence => 41, - Field::SignerQuorum => 35, - Field::SignerEntries => 4, - Field::SignerEntry => 11, - Field::Memos => 9, - Field::Memo => 10, - Field::TicketCount => 40, - Field::Signers => 3, - Field::Signer => 16 - } - } -} - impl TryInto for XRPLSigner { type Error = ContractError; fn try_into(self) -> Result { - let mut obj = XRPLObject::new(); - obj.add_field(Field::SigningPubKey, &self.signing_pub_key)?; - obj.add_field(Field::TxnSignature, &self.txn_signature)?; - obj.add_field(Field::Account, &self.account)?; - Ok(obj) + Ok(xrpl_json!({ + SigningPubKey: self.signing_pub_key, + TxnSignature: self.txn_signature, + Account: self.account + })) } } @@ -166,10 +174,10 @@ impl TryInto for XRPLSignerEntry { type Error = ContractError; fn try_into(self) -> Result { - let mut obj = XRPLObject::new(); - obj.add_field(Field::Account, &self.account)?; - obj.add_field(Field::SignerWeight, &self.signer_weight)?; - Ok(obj) + Ok(xrpl_json!({ + Account: self.account, + SignerWeight: self.signer_weight + })) } } @@ -186,10 +194,9 @@ impl TryInto for XRPLMemo { type Error = ContractError; fn try_into(self) -> Result { - let mut obj = XRPLObject::new(); - let hex: HexBinary = self.into(); - obj.add_field(Field::MemoData, &hex)?; - Ok(obj) + Ok(xrpl_json!({ + MemoData: self.0 + })) } } @@ -212,16 +219,17 @@ impl TryInto for XRPLPaymentTx { type Error = ContractError; fn try_into(self) -> Result { - let mut obj = XRPLObject::new(); - obj.add_field(Field::TransactionType, &PAYMENT_TX_TYPE)?; - obj.add_field(Field::Flags, &0u32)?; + let mut obj = xrpl_json!({ + TransactionType: PAYMENT_TX_TYPE, + Flags: 0u32, + Amount: self.amount, + Fee: XRPLPaymentAmount::Drops(self.fee), + Account: self.account, + SigningPubKey: HexBinary::from(vec![]), + Destination: self.destination, + Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} + }); obj.add_sequence(&self.sequence)?; - obj.add_field(Field::Amount, &self.amount)?; - obj.add_field(Field::Fee, &XRPLPaymentAmount::Drops(self.fee))?; - obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; - obj.add_field(Field::Account, &self.account)?; - obj.add_field(Field::Destination, &self.destination)?; - obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]})?; Ok(obj) } } @@ -230,16 +238,17 @@ impl TryInto for XRPLSignerListSetTx { type Error = ContractError; fn try_into(self) -> Result { - let mut obj = XRPLObject::new(); - obj.add_field(Field::TransactionType, &SIGNER_LIST_SET_TX_TYPE)?; - obj.add_field(Field::Flags, &0u32)?; // flags + let mut obj = xrpl_json!({ + TransactionType: SIGNER_LIST_SET_TX_TYPE, + Flags: 0u32, + SignerQuorum: self.signer_quorum, + Fee: XRPLPaymentAmount::Drops(self.fee), + Account: self.account, + SigningPubKey: HexBinary::from(vec![]), + SignerEntries: XRPLArray{ field: Field::SignerEntry, items: self.signer_entries.clone() }, + Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} + }); obj.add_sequence(&self.sequence)?; - obj.add_field(Field::SignerQuorum, &self.signer_quorum)?; - obj.add_field(Field::Fee, &XRPLPaymentAmount::Drops(self.fee))?; - obj.add_field(Field::Account, &self.account)?; - obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; - obj.add_field(Field::SignerEntries, &XRPLArray{ field: Field::SignerEntry, items: self.signer_entries.clone() })?; - obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]})?; Ok(obj) } } @@ -248,15 +257,16 @@ impl TryInto for XRPLTicketCreateTx { type Error = ContractError; fn try_into(self) -> Result { - let mut obj = XRPLObject::new(); - obj.add_field(Field::TransactionType, &TICKET_CREATE_TX_TYPE)?; - obj.add_field(Field::Flags, &0u32)?; + let mut obj = xrpl_json!({ + TransactionType: TICKET_CREATE_TX_TYPE, + Flags: 0u32, + TicketCount: self.ticket_count, + Fee: XRPLPaymentAmount::Drops(self.fee), + Account: self.account, + SigningPubKey: HexBinary::from(vec![]), + Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} + }); obj.add_sequence(&self.sequence)?; - obj.add_field(Field::TicketCount, &self.ticket_count)?; - obj.add_field(Field::Fee, &XRPLPaymentAmount::Drops(self.fee))?; - obj.add_field(Field::SigningPubKey, &HexBinary::from_hex("")?)?; - obj.add_field(Field::Account, &self.account)?; - obj.add_field(Field::Memos, &XRPLArray{field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]})?; Ok(obj) } } @@ -354,12 +364,12 @@ impl XRPLObject { Ok(()) } - pub fn add_sequence(&mut self, sequence: &Sequence) -> Result<(), ContractError> { + pub fn add_sequence(&mut self, sequence: &XRPLSequence) -> Result<(), ContractError> { match sequence { - Sequence::Plain(seq) => { + XRPLSequence::Plain(seq) => { self.add_field(Field::Sequence, seq) }, - Sequence::Ticket(seq) => { + XRPLSequence::Ticket(seq) => { self.add_field(Field::Sequence, &0u32)?; self.add_field(Field::TicketSequence, seq) } @@ -442,7 +452,7 @@ mod tests { "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, - currency: "USD".to_string(), + currency: "USD".to_string().try_into()?, }, Uint128::zero().try_into()?) .xrpl_serialize()? ); @@ -450,7 +460,7 @@ mod tests { "D4838D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, - currency: "USD".to_string(), + currency: "USD".to_string().try_into()?, }, Uint128::from(1u128).try_into()?) .xrpl_serialize()? ); @@ -459,7 +469,7 @@ mod tests { "C0438D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, - currency: "USD".to_string(), + currency: "USD".to_string().try_into()? }, XRPLTokenAmount::new(MIN_MANTISSA, MIN_EXPONENT)) .xrpl_serialize()? ); @@ -468,32 +478,22 @@ mod tests { "EC6386F26FC0FFFF00000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, - currency: "USD".to_string(), + currency: "USD".to_string().try_into()? }, XRPLTokenAmount::new(MAX_MANTISSA, MAX_EXPONENT)) .xrpl_serialize()? ); + // currency cannot contain certain characters like ";" + assert!(XRPLCurrency::try_from("${;".to_string()).is_err()); + assert!(XRPLCurrency::try_from("XRP".to_string()).is_err()); // currency can contain non-alphanumeric ascii letters assert_hex_eq!( - "D4CEEBE0B40E8000000000000000000000000000247B3B00000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", + "D4CEEBE0B40E8000000000000000000000000000247B7D00000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, - currency: "${;".to_string(), + currency: "${}".to_string().try_into()?, }, Uint128::from(42u128).try_into()?) .xrpl_serialize()? ); - // TODO: these could be enforced on a type level: - // - currency cannot contain non-ascii letters - // - currency must not be more than 3 ascii letters - // - currency must not be less than 3 ascii letters - // XRP currency code is not allowed - assert!( - XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, - currency: "XRP".to_string(), - }, Uint128::from(42u128).try_into()?) - .xrpl_serialize() - .is_err() - ); // minimum XRP assert_hex_eq!( "4000000000000000", @@ -578,10 +578,10 @@ mod tests { let unsigned_tx = XRPLPaymentTx { account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, fee: 12, - sequence: Sequence::Plain(1), + sequence: XRPLSequence::Plain(1), amount: XRPLPaymentAmount::Token( XRPLToken { - currency: "JPY".to_string(), + currency: "JPY".to_string().try_into()?, issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".try_into()?, }, XRPLTokenAmount::new(3369568318000000u64, -16) @@ -602,7 +602,7 @@ mod tests { let tx = XRPLPaymentTx { account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, fee: 10, - sequence: Sequence::Plain(1), + sequence: XRPLSequence::Plain(1), amount: XRPLPaymentAmount::Drops(1000), destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, multisig_session_id: Uint64::from(0u8), @@ -616,7 +616,7 @@ mod tests { let tx = XRPLPaymentTx { account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".try_into()?, fee: 3, - sequence: Sequence::Plain(43497363), + sequence: XRPLSequence::Plain(43497363), amount: XRPLPaymentAmount::Drops(1000000000), destination: "rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq".try_into()?, multisig_session_id: Uint64::from(1337u16), @@ -639,7 +639,7 @@ mod tests { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, fee: 30, - sequence: Sequence::Ticket(44218193), + sequence: XRPLSequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, multisig_session_id: Uint64::from(5461264u64), @@ -670,7 +670,7 @@ mod tests { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, fee: 30, - sequence: Sequence::Ticket(44218193), + sequence: XRPLSequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, multisig_session_id: Uint64::from(5461264u64), @@ -701,8 +701,8 @@ mod tests { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()?, fee: 30, - sequence: Sequence::Ticket(45205896), - amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string(), issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()? }, Uint128::from(100000000u128).try_into()?), + sequence: XRPLSequence::Ticket(45205896), + amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string().try_into()?, issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()? }, Uint128::from(100000000u128).try_into()?), destination: "raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".try_into()?, multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ @@ -733,7 +733,7 @@ mod tests { unsigned_tx: XRPLUnsignedTx::TicketCreate(XRPLTicketCreateTx { account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, fee: 30, - sequence: Sequence::Plain(44218194), + sequence: XRPLSequence::Plain(44218194), ticket_count: 3, multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ @@ -763,7 +763,7 @@ mod tests { unsigned_tx: XRPLUnsignedTx::SignerListSet(XRPLSignerListSetTx { account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, fee: 30, - sequence: Sequence::Plain(44218445), + sequence: XRPLSequence::Plain(44218445), signer_quorum: 3, signer_entries: vec![ XRPLSignerEntry{ diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 1a05724e9..f5333669e 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1053,7 +1053,7 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { ETH_DENOMINATION.to_string(), XRPLToken { issuer: xrpl_multisig_address.as_str().try_into().unwrap(), - currency: "ETH".to_string(), + currency: "ETH".to_string().try_into().unwrap(), }, ); From f97b3cbb13d272ceaa693c09a63d67141d2824b9 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 20 Feb 2024 23:15:31 +0200 Subject: [PATCH 60/80] xrpl multisig prover: use parse_message_id from voting verifier and other todo fixes --- contracts/voting-verifier/src/events.rs | 2 +- .../xrpl-multisig-prover/src/contract.rs | 7 +++-- contracts/xrpl-multisig-prover/src/error.rs | 3 ++ contracts/xrpl-multisig-prover/src/query.rs | 6 ++-- contracts/xrpl-multisig-prover/src/reply.rs | 4 +-- contracts/xrpl-multisig-prover/src/state.rs | 3 +- contracts/xrpl-multisig-prover/src/types.rs | 31 +++---------------- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 14 +++++---- 8 files changed, 27 insertions(+), 43 deletions(-) diff --git a/contracts/voting-verifier/src/events.rs b/contracts/voting-verifier/src/events.rs index a65a5cbf7..242f781fa 100644 --- a/contracts/voting-verifier/src/events.rs +++ b/contracts/voting-verifier/src/events.rs @@ -158,7 +158,7 @@ impl TryFrom for TxEventConfirmation { } } -fn parse_message_id( +pub fn parse_message_id( message_id: &nonempty::String, ) -> Result<(nonempty::String, u64), ContractError> { // expected format: : diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index e3832bbbb..9bf0aaeb6 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -8,12 +8,13 @@ use cosmwasm_std::{ entry_point, Storage, wasm_execute, SubMsg, Reply, DepsMut, Env, MessageInfo, Response, Fraction, Uint64, to_json_binary, Deps, StdResult, Binary, Addr, HexBinary, }; +use voting_verifier::events::parse_message_id; use multisig::types::MultisigState; use crate::{ error::ContractError, - state::{Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, MULTISIG_SESSION_TX, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO, REPLY_MESSAGE_ID, MESSAGE_ID_TO_MULTISIG_SESSION_ID}, + state::{Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, MULTISIG_SESSION_ID_TO_TX_HASH, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO, REPLY_MESSAGE_ID, MESSAGE_ID_TO_MULTISIG_SESSION_ID}, msg::{ExecuteMsg, QueryMsg}, reply, types::*, @@ -312,7 +313,7 @@ fn update_tx_status( axelar_multisig_address: impl Into, xrpl_multisig_address: String, ) -> Result { - let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; + let unsigned_tx_hash = MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; let multisig_session = querier.get_multisig_session(&multisig_session_id)?; @@ -343,7 +344,7 @@ fn update_tx_status( let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?).into(); - if parse_message_id(&message_id.id)?.0.to_string() != tx_hash.to_string() { + if parse_message_id(&message_id.id).map_err(|e| ContractError::InvalidMessageID(message_id.id.to_string()))?.0.to_string() != tx_hash.to_string() { return Err(ContractError::InvalidMessageID(message_id.id.to_string())); } diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index a7c2bd363..43f6ef08e 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -106,6 +106,9 @@ pub enum ContractError { #[error("invalid token denomination")] InvalidTokenDenom, + + #[error("no available tickets")] + NoAvailableTickets } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index d96eec8c7..b5eab1dca 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -5,12 +5,12 @@ use multisig::key::PublicKey; use crate::{ types::*, - state::{MULTISIG_SESSION_TX, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, + state::{MULTISIG_SESSION_ID_TO_TX_HASH, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, xrpl_serialize::XRPLSerialize }; pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &XRPLAccountId) -> StdResult { - let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; + let unsigned_tx_hash = MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; if tx_info.status != TransactionStatus::Pending { @@ -34,7 +34,7 @@ pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, publi } pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: &Uint64) -> StdResult { - let unsigned_tx_hash = MULTISIG_SESSION_TX.load(storage, multisig_session_id.u64())?; + let unsigned_tx_hash = MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 6655b5ca9..47148970b 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{from_json, Attribute, DepsMut, HexBinary, Reply, Response, Ui use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; use crate::{ - error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_TX, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, + error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_ID_TO_TX_HASH, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, xrpl_serialize::XRPLSerialize }; @@ -16,7 +16,7 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result = Item::new("config"); pub const REPLY_TX_HASH: Item = Item::new("reply_tx_hash"); pub const REPLY_MESSAGE_ID: Item = Item::new("reply_message_id"); -// TODO: rename to MULTISIG_SESSION_ID_TO_TX_HASH -pub const MULTISIG_SESSION_TX: Map = Map::new("multisig_session_tx"); +pub const MULTISIG_SESSION_ID_TO_TX_HASH: Map = Map::new("multisig_session_tx"); // The next seq. no. is determined on TicketCreate and depends on the number of created tickets, // not solely on the last sequence number used. diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index d15a24ea4..d8d073baa 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::{nonempty, VerificationStatus}; +use axelar_wasm_std::VerificationStatus; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, HexBinary, Binary, StdResult, Uint256, Uint128, Uint64}; @@ -8,6 +8,7 @@ use multisig::key::PublicKey; use ripemd::Ripemd160; use sha2::{Digest, Sha256}; +use voting_verifier::events::parse_message_id; use crate::axelar_workers::AxelarSigner; use crate::error::ContractError; @@ -19,33 +20,13 @@ pub enum TransactionStatus { Inconclusive, } -// TODO: import from verifier -pub fn parse_message_id( - message_id: &nonempty::String, -) -> Result<(nonempty::String, u64), ContractError> { - // expected format: : - let components = message_id.split(":").collect::>(); - - if components.len() != 2 { - return Err(ContractError::InvalidMessageID(message_id.to_string())); - } - - Ok(( - components[0].try_into()?, - components[1] - .parse::() - .map_err(|_| ContractError::InvalidMessageID(message_id.to_string()))?, - )) -} - #[cw_serde] pub struct TxHash(pub HexBinary); impl TryFrom for TxHash { type Error = ContractError; fn try_from(cc_id: CrossChainId) -> Result { - // TODO check this is correct - let (tx_id, _event_index) = parse_message_id(&cc_id.id)?; + let (tx_id, _event_index) = parse_message_id(&cc_id.id).map_err(|_e| ContractError::InvalidMessageID(cc_id.id.to_string()))?; Ok(Self(HexBinary::from_hex(tx_id.to_ascii_lowercase().as_str())?)) } } @@ -69,10 +50,8 @@ impl Into for VerificationStatus { #[cw_serde] pub struct TransactionInfo { pub status: TransactionStatus, - // TODO: save only the hash of the unsigned tx pub unsigned_contents: XRPLUnsignedTx, - // TODO: rename: original_message_id or similar, the message id that triggered this tx - pub message_id: Option, + pub original_message_id: Option, } impl From for TxHash { @@ -414,4 +393,4 @@ pub fn canonicalize_mantissa(mut mantissa: Uint128) -> Result<(u64, i64), Contra let mantissa = u64::from_be_bytes(mantissa.to_be_bytes()[8..].try_into().unwrap()); return Ok((mantissa, exponent)); -} \ No newline at end of file +} diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 32bef6c53..b0ac28186 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,6 +1,7 @@ use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; +use hex::ToHex; use k256::{ecdsa, schnorr::signature::SignatureEncoding}; use sha2::{Sha512, Digest, Sha256}; @@ -21,7 +22,7 @@ fn issue_tx( &TransactionInfo { status: TransactionStatus::Pending, unsigned_contents: tx.clone(), - message_id, + original_message_id: message_id, } )?; @@ -182,7 +183,7 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId // as long as it has not already been consumed if confirmed_tx_hash.is_none() // or if it has been consumed by the same message. - || TRANSACTION_INFO.load(storage, &confirmed_tx_hash.unwrap())?.message_id.as_ref() == Some(message_id) { + || TRANSACTION_INFO.load(storage, &confirmed_tx_hash.unwrap())?.original_message_id.as_ref() == Some(message_id) { return Ok(ticket_number); } } @@ -193,14 +194,16 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId Ok(new_ticket_number) } - pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { let last_assigned_ticket_number: u32 = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; - // TODO: handle no available tickets + let available_tickets = AVAILABLE_TICKETS.load(storage)?; + if available_tickets.len() == 0 { + return Err(ContractError::NoAvailableTickets); + } + // find next largest in available, otherwise use available_tickets[0] - // TODO: handle IndexOutOfBounds error on available_tickets[0] let ticket_number = available_tickets.iter().find(|&x| x > &last_assigned_ticket_number).unwrap_or(&available_tickets[0]); Ok(*ticket_number) } @@ -283,7 +286,6 @@ pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(mul .iter() .map(|(axelar_signer, signature)| -> Result { let txn_signature = match signature { - // TODO: use unwrapped signature instead of ignoring it multisig::key::Signature::Ecdsa(_) | multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( &ecdsa::Signature::try_from(signature.clone().as_ref()) From 4802b932be790f3696dff55da99803f83aae881b Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 21 Feb 2024 09:43:42 +0200 Subject: [PATCH 61/80] xrpl multisig prover: code quality improvements --- .../xrpl-multisig-prover/src/contract.rs | 10 +++---- contracts/xrpl-multisig-prover/src/query.rs | 8 ++--- contracts/xrpl-multisig-prover/src/types.rs | 29 +++++++++++++++++++ .../xrpl-multisig-prover/src/xrpl_multisig.rs | 27 ----------------- .../src/xrpl_serialize.rs | 1 - 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 9bf0aaeb6..01481be4f 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -330,17 +330,17 @@ fn update_tx_status( payload_hash: [0; 32], }; - let axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)> = multisig_session.signers + let xrpl_signers: Vec = multisig_session.signers .iter() .filter(|(signer, _)| signers.contains(&signer.address)) - .filter_map(|(signer, signature)| signature.as_ref().map(|signature| (signer.clone(), signature.clone()))) - .collect(); + .filter_map(|(signer, signature)| signature.as_ref().map(|signature| XRPLSigner::try_from((signer.clone(), signature.clone())))) + .collect::, ContractError>>()?; - if axelar_signers.len() != signers.len() { + if xrpl_signers.len() != signers.len() { return Err(ContractError::SignatureNotFound); } - let signed_tx = xrpl_multisig::make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; + let signed_tx = XRPLSignedTransaction::new(tx_info.unsigned_contents, xrpl_signers); let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?).into(); diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index b5eab1dca..f16eaf136 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -44,12 +44,12 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & MultisigState::Pending => GetProofResponse::Pending { unsigned_tx_hash }, MultisigState::Completed { .. } => { let axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)> = multisig_session.signers - .iter() - .filter(|(_, signature)| signature.is_some()) - .map(|(signer, signature)| (signer.clone(), signature.clone().unwrap())) + .into_iter() + .filter_map(|(signer, sig)| sig.map(|sig| (signer, sig))) .collect(); - let signed_tx = xrpl_multisig::make_xrpl_signed_tx(tx_info.unsigned_contents, axelar_signers)?; + let xrpl_signers = axelar_signers.into_iter().map(|p| XRPLSigner::try_from(p)).collect::, ContractError>>()?; + let signed_tx = XRPLSignedTransaction::new(tx_info.unsigned_contents, xrpl_signers); let tx_blob: HexBinary = HexBinary::from(signed_tx.xrpl_serialize()?); GetProofResponse::Completed { unsigned_tx_hash, tx_blob } } diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index d8d073baa..94c1e3f71 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -3,6 +3,8 @@ use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; use cosmwasm_std::{from_json, HexBinary, Binary, StdResult, Uint256, Uint128, Uint64}; use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; +use k256::ecdsa; +use k256::schnorr::signature::SignatureEncoding; use multisig::key::Signature; use multisig::key::PublicKey; use ripemd::Ripemd160; @@ -288,12 +290,39 @@ pub struct XRPLSigner { pub signing_pub_key: PublicKey, } +impl TryFrom<(multisig::msg::Signer, multisig::key::Signature)> for XRPLSigner { + type Error = ContractError; + + fn try_from((axelar_signer, signature): (multisig::msg::Signer, multisig::key::Signature)) -> Result { + let txn_signature = match signature { + multisig::key::Signature::Ecdsa(_) | + multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( + &ecdsa::Signature::try_from(signature.clone().as_ref()) + .map_err(|_| ContractError::FailedToEncodeSignature)? + ).to_vec()), + _ => unimplemented!("Unsupported signature type"), + }; + + Ok(XRPLSigner { + account: XRPLAccountId::from(&axelar_signer.pub_key), + signing_pub_key: axelar_signer.pub_key.clone().into(), + txn_signature, + }) + } +} + #[cw_serde] pub struct XRPLSignedTransaction { pub unsigned_tx: XRPLUnsignedTx, pub signers: Vec } +impl XRPLSignedTransaction { + pub fn new(unsigned_tx: XRPLUnsignedTx, signers: Vec) -> Self { + Self { unsigned_tx, signers } + } +} + #[cw_serde] pub struct XRPLCurrency(String); diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index b0ac28186..b4345684d 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -281,33 +281,6 @@ pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_address: &XRPLAcc Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)) } -pub fn make_xrpl_signed_tx(unsigned_tx: XRPLUnsignedTx, axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)>) -> Result { - let xrpl_signers: Vec = axelar_signers - .iter() - .map(|(axelar_signer, signature)| -> Result { - let txn_signature = match signature { - multisig::key::Signature::Ecdsa(_) | - multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( - &ecdsa::Signature::try_from(signature.clone().as_ref()) - .map_err(|_| ContractError::FailedToEncodeSignature)? - ).to_vec()), - _ => unimplemented!("Unsupported signature type"), - }; - - Ok(XRPLSigner { - account: XRPLAccountId::from(&axelar_signer.pub_key), - signing_pub_key: axelar_signer.pub_key.clone().into(), - txn_signature, - }) - }) - .collect::, ContractError>>()?; - - Ok(XRPLSignedTransaction { - unsigned_tx, - signers: xrpl_signers, - }) -} - #[cfg(test)] mod tests { use super::*; diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index e1db6c4ef..60677d785 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -726,7 +726,6 @@ mod tests { Ok(()) } - #[test] fn serialize_xrpl_signed_xrp_ticket_create_transaction() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { From dfe01703a52bc4a4545219487fb2ce774ffb9c08 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Thu, 29 Feb 2024 14:00:19 +0200 Subject: [PATCH 62/80] xrpl: xrpl_serialize parameter not a reference --- .../src/xrpl_serialize.rs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index 60677d785..ae86c3534 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -71,7 +71,7 @@ macro_rules! xrpl_json { // Process each key-value pair. $( - obj.add_field($key, &$value)?; + obj.add_field($key, $value)?; )* obj @@ -80,13 +80,13 @@ macro_rules! xrpl_json { pub trait XRPLSerialize { const TYPE_CODE: u8; - fn xrpl_serialize(&self) -> Result, ContractError>; + fn xrpl_serialize(self) -> Result, ContractError>; } impl XRPLSerialize for u16 { const TYPE_CODE: u8 = 1; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { Ok(self.to_be_bytes().to_vec()) } } @@ -94,7 +94,7 @@ impl XRPLSerialize for u16 { impl XRPLSerialize for u32 { const TYPE_CODE: u8 = 2; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { Ok(self.to_be_bytes().to_vec()) } } @@ -102,10 +102,10 @@ impl XRPLSerialize for u32 { impl XRPLSerialize for XRPLPaymentAmount { const TYPE_CODE: u8 = 6; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { match self { XRPLPaymentAmount::Drops(value) => { - if *value <= 10u64.pow(17) { + if value <= 10u64.pow(17) { Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) } else { Err(ContractError::InvalidAmount { reason: "more than maximum amount of drops".to_string() }) @@ -121,7 +121,7 @@ impl XRPLSerialize for XRPLPaymentAmount { impl XRPLSerialize for HexBinary { const TYPE_CODE: u8 = 7; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { Ok([encode_length(self.len())?, self.to_vec()].concat()) } } @@ -148,7 +148,7 @@ pub fn encode_length(mut length: usize) -> Result, ContractError> { impl XRPLSerialize for PublicKey { const TYPE_CODE: u8 = 7; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { match self.clone() { // rippled prefixes Ed25519 public keys with the byte 0xED so both types of public key are 33 bytes. // https://xrpl.org/cryptographic-keys.html @@ -203,7 +203,7 @@ impl TryInto for XRPLMemo { impl XRPLSerialize for XRPLAccountId { const TYPE_CODE: u8 = 8; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { let mut result: Vec = Vec::new(); result.extend(vec![self.to_bytes().len() as u8]); result.extend(self.to_bytes()); @@ -229,7 +229,7 @@ impl TryInto for XRPLPaymentTx { Destination: self.destination, Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} }); - obj.add_sequence(&self.sequence)?; + obj.add_sequence(self.sequence)?; Ok(obj) } } @@ -248,7 +248,7 @@ impl TryInto for XRPLSignerListSetTx { SignerEntries: XRPLArray{ field: Field::SignerEntry, items: self.signer_entries.clone() }, Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} }); - obj.add_sequence(&self.sequence)?; + obj.add_sequence(self.sequence)?; Ok(obj) } } @@ -266,7 +266,7 @@ impl TryInto for XRPLTicketCreateTx { SigningPubKey: HexBinary::from(vec![]), Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} }); - obj.add_sequence(&self.sequence)?; + obj.add_sequence(self.sequence)?; Ok(obj) } } @@ -294,7 +294,7 @@ impl TryInto for XRPLSignedTransaction { a.account.to_bytes().cmp(&b.account.to_bytes()) }); let mut obj: XRPLObject = self.unsigned_tx.clone().try_into()?; - obj.add_field(Field::Signers, &XRPLArray{ field: Field::Signer, items: sorted_signers })?; + obj.add_field(Field::Signers, XRPLArray{ field: Field::Signer, items: sorted_signers })?; Ok(obj) } } @@ -307,9 +307,9 @@ struct XRPLArray { impl XRPLSerialize for XRPLArray { const TYPE_CODE: u8 = 15; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { let mut result: Vec = Vec::new(); - for item in &self.items { + for item in self.items { result.extend(field_id(T::TYPE_CODE, self.field.clone().to_u8())); result.extend(item.xrpl_serialize()?); result.extend(field_id(T::TYPE_CODE, 1)); @@ -359,18 +359,18 @@ impl XRPLObject { } } - pub fn add_field(&mut self, field: Field, value: &T) -> Result<(), ContractError> { + pub fn add_field(&mut self, field: Field, value: T) -> Result<(), ContractError> { self.fields.push(SerializedField::new(T::TYPE_CODE, field.to_u8(), value.xrpl_serialize()?)); Ok(()) } - pub fn add_sequence(&mut self, sequence: &XRPLSequence) -> Result<(), ContractError> { + pub fn add_sequence(&mut self, sequence: XRPLSequence) -> Result<(), ContractError> { match sequence { XRPLSequence::Plain(seq) => { self.add_field(Field::Sequence, seq) }, XRPLSequence::Ticket(seq) => { - self.add_field(Field::Sequence, &0u32)?; + self.add_field(Field::Sequence, 0u32)?; self.add_field(Field::TicketSequence, seq) } } @@ -380,7 +380,7 @@ impl XRPLObject { impl XRPLSerialize for XRPLObject { const TYPE_CODE: u8 = 14; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { let mut fields: Vec = self.fields.clone(); fields.sort(); Ok(fields.into_iter().map(|f| { f.to_vec() }).collect::>>().concat()) @@ -394,7 +394,7 @@ where { const TYPE_CODE: u8 = XRPLObject::TYPE_CODE; - fn xrpl_serialize(&self) -> Result, ContractError> { + fn xrpl_serialize(self) -> Result, ContractError> { let obj: XRPLObject = self.clone().try_into()?; obj.xrpl_serialize() } From 650f606fd6a3404c7df8dd1cf0eceef2021af563 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Fri, 9 Feb 2024 15:11:08 +0200 Subject: [PATCH 63/80] add message_provider option on multisig handler --- ampd/src/commands/register_public_key.rs | 2 +- ampd/src/config.rs | 1 + ampd/src/handlers/config.rs | 1 + ampd/src/handlers/multisig.rs | 14 +++++++++++++- ampd/src/lib.rs | 3 ++- 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ampd/src/commands/register_public_key.rs b/ampd/src/commands/register_public_key.rs index 62c44b9d7..46d062574 100644 --- a/ampd/src/commands/register_public_key.rs +++ b/ampd/src/commands/register_public_key.rs @@ -82,7 +82,7 @@ fn get_multisig_address(config: &Config) -> Result { .handlers .iter() .find_map(|config| { - if let handlers::config::Config::MultisigSigner { cosmwasm_contract } = config { + if let handlers::config::Config::MultisigSigner { cosmwasm_contract, message_provider } = config { Some(cosmwasm_contract.clone()) } else { None diff --git a/ampd/src/config.rs b/ampd/src/config.rs index 392b17b39..fb3a9cf83 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -283,6 +283,7 @@ mod tests { cosmwasm_contract: TMAddress::from( AccountId::new("axelar", &[0u8; 32]).unwrap(), ), + message_provider: None, }, HandlerConfig::SuiMsgVerifier { cosmwasm_contract: TMAddress::from( diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index 4f2c64d0e..b45d29e50 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -33,6 +33,7 @@ pub enum Config { }, MultisigSigner { cosmwasm_contract: TMAddress, + message_provider: Option, }, XRPLMultisigSigner { cosmwasm_contract: TMAddress, diff --git a/ampd/src/handlers/multisig.rs b/ampd/src/handlers/multisig.rs index 2bf4115cd..3964aadcb 100644 --- a/ampd/src/handlers/multisig.rs +++ b/ampd/src/handlers/multisig.rs @@ -77,6 +77,7 @@ where broadcaster: B, signer: SharableEcdsaClient, latest_block_height: Receiver, + message_provider: Option, } impl Handler @@ -89,6 +90,7 @@ where broadcaster: B, signer: SharableEcdsaClient, latest_block_height: Receiver, + message_provider: Option, ) -> Self { Self { worker, @@ -96,6 +98,7 @@ where broadcaster, signer, latest_block_height, + message_provider } } @@ -164,6 +167,10 @@ where return Ok(()); } + if let Some(msg_provider_address) = &self.message_provider { + // call contract to get message to sign + } + match pub_keys.get(&self.worker) { Some(pub_key) => { let signature = self @@ -283,6 +290,7 @@ mod test { multisig: TMAddress, signer: SharableEcdsaClient, latest_block_height: u64, + message_provider: Option, ) -> Handler { let mut broadcaster = MockBroadcaster::new(); broadcaster @@ -294,7 +302,7 @@ mod test { let (tx, rx) = watch::channel(latest_block_height); - Handler::new(worker, multisig, broadcaster.client(), signer, rx) + Handler::new(worker, multisig, broadcaster.client(), signer, rx, message_provider) } #[test] @@ -364,6 +372,7 @@ mod test { rand_account(), SharableEcdsaClient::new(client), 100u64, + None ); assert!(handler.handle(&signing_started_event()).await.is_ok()); @@ -381,6 +390,7 @@ mod test { TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), SharableEcdsaClient::new(client), 100u64, + None ); assert!(handler.handle(&signing_started_event()).await.is_ok()); @@ -401,6 +411,7 @@ mod test { TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), SharableEcdsaClient::new(client), 99u64, + None ); assert!(matches!( @@ -424,6 +435,7 @@ mod test { TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), SharableEcdsaClient::new(client), 101u64, + None ); assert!(handler.handle(&signing_started_event()).await.is_ok()); diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index c8e470c9e..934140c6e 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -241,7 +241,7 @@ where ), stream_timeout, ), - handlers::config::Config::MultisigSigner { cosmwasm_contract } => self + handlers::config::Config::MultisigSigner { cosmwasm_contract, message_provider } => self .create_handler_task( "multisig-signer", handlers::multisig::Handler::new( @@ -250,6 +250,7 @@ where self.broadcaster.client(), self.ecdsa_client.clone(), self.block_height_monitor.latest_block_height(), + message_provider ), stream_timeout, ), From ddfebf22db5f26aeebaa3fb24ffe1d957ab3aadb Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Sun, 11 Feb 2024 23:55:43 +0200 Subject: [PATCH 64/80] ampd: xrpl msg verifier --- Cargo.lock | 1513 +++++++++++++++----------- ampd/Cargo.toml | 1 + ampd/src/handlers/config.rs | 5 + ampd/src/handlers/mod.rs | 1 + ampd/src/handlers/xrpl_verify_msg.rs | 173 +++ ampd/src/lib.rs | 24 + ampd/src/xrpl/json_rpc.rs | 28 + ampd/src/xrpl/mod.rs | 2 + ampd/src/xrpl/verifier.rs | 40 + 9 files changed, 1177 insertions(+), 610 deletions(-) create mode 100644 ampd/src/handlers/xrpl_verify_msg.rs create mode 100644 ampd/src/xrpl/json_rpc.rs create mode 100644 ampd/src/xrpl/mod.rs create mode 100644 ampd/src/xrpl/verifier.rs diff --git a/Cargo.lock b/Cargo.lock index 5a8f752d2..9c7e5959e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,9 +50,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -188,7 +188,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "serde_with 3.6.1", + "serde_with 3.7.0", "service-registry", "sha3 0.10.8", "sui-json-rpc-types", @@ -210,6 +210,7 @@ dependencies = [ "valuable-serde", "voting-verifier", "xrpl-multisig-prover", + "xrpl_http_client", ] [[package]] @@ -252,7 +253,7 @@ dependencies = [ "rustls-webpki", "serde", "serde_json", - "socket2 0.5.5", + "socket2 0.5.6", "tap", "thiserror", "tokio", @@ -264,9 +265,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -312,9 +313,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "ark-bls12-381" @@ -400,7 +401,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ - "quote 1.0.35", + "quote 1.0.36", "syn 1.0.109", ] @@ -412,8 +413,8 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint 0.4.4", "num-traits", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -485,8 +486,8 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -564,8 +565,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", "synstructure", ] @@ -576,8 +577,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -612,9 +613,9 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -624,13 +625,13 @@ source = "git+https://github.com/mystenmark/async-task?rev=4e45b26e11126b191701b [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -646,13 +647,13 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823b8bb275161044e2ac7a25879cb3e2480cb403e3943022c7c769c599b756aa" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -663,9 +664,9 @@ checksum = "7460f7dd8e100147b82a63afca1a20eb6c231ee36b90ba7272e14951cb58af59" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "axelar-wasm-std" @@ -697,9 +698,9 @@ version = "0.1.0" dependencies = [ "axelar-wasm-std", "error-stack", - "quote 1.0.35", + "quote 1.0.36", "report", - "syn 2.0.48", + "syn 2.0.60", "thiserror", ] @@ -758,9 +759,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -875,13 +876,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.16", - "proc-macro2 1.0.78", - "quote 1.0.35", + "prettyplease 0.2.19", + "proc-macro2 1.0.81", + "quote 1.0.36", "regex", "rustc-hash", "shlex", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -890,8 +891,8 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" dependencies = [ - "bs58 0.5.0", - "hmac", + "bs58 0.5.1", + "hmac 0.12.1", "k256", "once_cell", "pbkdf2 0.12.2", @@ -940,9 +941,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitmaps" @@ -1010,9 +1011,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -1092,9 +1093,9 @@ checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" [[package]] name = "brotli" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1119,9 +1120,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "sha2 0.10.8", "tinyvec", @@ -1149,9 +1150,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -1167,9 +1168,9 @@ checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -1179,9 +1180,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] @@ -1218,9 +1219,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] @@ -1250,9 +1251,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" dependencies = [ "jobserver", "libc", @@ -1275,15 +1276,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -1309,9 +1310,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -1319,26 +1320,26 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.0" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.0", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "heck 0.5.0", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -1383,10 +1384,10 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" dependencies = [ - "bs58 0.5.0", + "bs58 0.5.1", "coins-core", "digest 0.10.7", - "hmac", + "hmac 0.12.1", "k256", "serde", "sha2 0.10.8", @@ -1401,7 +1402,7 @@ checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" dependencies = [ "bitvec 1.0.1", "coins-bip32", - "hmac", + "hmac 0.12.1", "once_cell", "pbkdf2 0.12.2", "rand", @@ -1417,7 +1418,7 @@ checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ "base64 0.21.7", "bech32", - "bs58 0.5.0", + "bs58 0.5.1", "digest 0.10.7", "generic-array", "hex", @@ -1527,9 +1528,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.11.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d59688ad0945eaf6b84cb44fedbe93484c81b48970e98f09db8a22832d7961" +checksum = "5ba00838774b4ab0233e355d26710fbfc8327a05c017f6dc4873f876d1f79f78" dependencies = [ "cfg-if", "cpufeatures", @@ -1681,8 +1682,8 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d803bea6bd9ed61bd1ee0b4a2eb09ee20dbb539cc6e0b8795614d20952ebb1" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -1729,18 +1730,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ "crossbeam-utils", ] @@ -1799,6 +1800,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "ct-logs" version = "0.8.0" @@ -1853,9 +1864,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -1983,12 +1994,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ - "darling_core 0.20.5", - "darling_macro 0.20.5", + "darling_core 0.20.8", + "darling_macro 0.20.8", ] [[package]] @@ -1999,24 +2010,24 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "strsim 0.10.0", "syn 1.0.109", ] [[package]] name = "darling_core" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "strsim 0.10.0", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -2026,19 +2037,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core 0.14.4", - "quote 1.0.35", + "quote 1.0.36", "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ - "darling_core 0.20.5", - "quote 1.0.35", - "syn 2.0.48", + "darling_core 0.20.8", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -2093,9 +2104,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "der_derive", @@ -2123,9 +2134,9 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -2144,8 +2155,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4255bb7dd538590188bd0aea52e48bd699b19bd90b0d069ec2ced8461fe23273" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -2155,8 +2166,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -2176,8 +2187,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -2198,18 +2209,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "rustc_version", "syn 1.0.109", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "difference" version = "2.0.0" @@ -2291,9 +2296,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -2310,9 +2315,9 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dunce" @@ -2322,9 +2327,9 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ec-gpu" @@ -2338,7 +2343,7 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der 0.7.8", + "der 0.7.9", "digest 0.10.7", "elliptic-curve", "rfc6979", @@ -2415,9 +2420,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "elliptic-curve" @@ -2456,18 +2461,18 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] name = "enr" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe81b5c06ecfdbc71dd845216f225f53b62a10cb8a16c946836a3467f701d05b" +checksum = "2a3d8dc56e02f954cac8eb489772c552c473346fc34f67412bb6244fd647f7e4" dependencies = [ "base64 0.21.7", "bytes", @@ -2495,21 +2500,42 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f16ef37b2a9b242295d61a154ee91ae884afff6b8b933b486b12481cc58310ca" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] [[package]] name = "enum_dispatch" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f33313078bb8d4d05a2733a94ac4c2d8a0df9a2b84424ebf4f33bfc224a890e" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" dependencies = [ "once_cell", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", +] + +[[package]] +name = "enumflags2" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +dependencies = [ + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -2559,7 +2585,7 @@ dependencies = [ "ctr", "digest 0.10.7", "hex", - "hmac", + "hmac 0.12.1", "pbkdf2 0.11.0", "rand", "scrypt", @@ -2621,9 +2647,9 @@ dependencies = [ [[package]] name = "ethers" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c7cd562832e2ff584fa844cd2f6e5d4f35bbe11b28c7c9b8df957b2e1d0c701" +checksum = "816841ea989f0c69e459af1cf23a6b0033b19a55424a1ea3a30099becdb8dec0" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -2637,9 +2663,9 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35dc9a249c066d17e8947ff52a4116406163cf92c7f0763cb8c001760b26403f" +checksum = "5495afd16b4faa556c3bba1f21b98b4983e53c1755022377051a975c3b021759" dependencies = [ "ethers-core", "once_cell", @@ -2649,9 +2675,9 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43304317c7f776876e47f2f637859f6d0701c1ec7930a150f169d5fbe7d76f5a" +checksum = "6fceafa3578c836eeb874af87abacfb041f92b4da0a78a5edd042564b8ecdaaa" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -2668,9 +2694,9 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f96502317bf34f6d71a3e3d270defaa9485d754d789e15a8e04a84161c95eb" +checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" dependencies = [ "Inflector", "const-hex", @@ -2678,39 +2704,39 @@ dependencies = [ "ethers-core", "ethers-etherscan", "eyre", - "prettyplease 0.2.16", - "proc-macro2 1.0.78", - "quote 1.0.35", + "prettyplease 0.2.19", + "proc-macro2 1.0.81", + "quote 1.0.36", "regex", "reqwest", "serde", "serde_json", - "syn 2.0.48", - "toml 0.8.10", + "syn 2.0.60", + "toml 0.8.12", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "452ff6b0a64507ce8d67ffd48b1da3b42f03680dcf5382244e9c93822cbbf5de" +checksum = "87689dcabc0051cde10caaade298f9e9093d65f6125c14575db3fd8c669a168f" dependencies = [ "Inflector", "const-hex", "ethers-contract-abigen", "ethers-core", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "serde_json", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "ethers-core" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab3cef6cc1c9fd7f787043c81ad3052eff2b96a3878ef1526aa446311bdbfc9" +checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" dependencies = [ "arrayvec", "bytes", @@ -2728,8 +2754,8 @@ dependencies = [ "rlp", "serde", "serde_json", - "strum 0.25.0", - "syn 2.0.48", + "strum 0.26.2", + "syn 2.0.60", "tempfile", "thiserror", "tiny-keccak", @@ -2738,9 +2764,9 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d45b981f5fa769e1d0343ebc2a44cfa88c9bc312eb681b676318b40cef6fb1" +checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" dependencies = [ "chrono", "ethers-core", @@ -2754,9 +2780,9 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145211f34342487ef83a597c1e69f0d3e01512217a7c72cc8a25931854c7dca0" +checksum = "48f9fdf09aec667c099909d91908d5eaf9be1bd0e2500ba4172c1d28bfaa43de" dependencies = [ "async-trait", "auto_impl", @@ -2781,9 +2807,9 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb6b15393996e3b8a78ef1332d6483c11d839042c17be58decc92fa8b1c3508a" +checksum = "6434c9a33891f1effc9c75472e12666db2fa5a0fec4b29af6221680a6fe83ab2" dependencies = [ "async-trait", "auto_impl", @@ -2818,9 +2844,9 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b125a103b56aef008af5d5fb48191984aa326b50bfd2557d231dc499833de3" +checksum = "228875491c782ad851773b652dd8ecac62cda8571d3bc32a5853644dd26766c2" dependencies = [ "async-trait", "coins-bip32", @@ -2837,9 +2863,9 @@ dependencies = [ [[package]] name = "ethers-solc" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21df08582e0a43005018a858cc9b465c5fff9cf4056651be64f844e57d1f55f" +checksum = "66244a771d9163282646dbeffe0e6eca4dda4146b6498644e678ac6089b11edd" dependencies = [ "cfg-if", "const-hex", @@ -2891,10 +2917,10 @@ version = "0.1.0" dependencies = [ "error-stack", "events", - "quote 1.0.35", + "quote 1.0.36", "serde", "serde_json", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -2969,8 +2995,8 @@ version = "0.1.3" source = "git+https://github.com/MystenLabs/fastcrypto?rev=69180dc7275f5f0efb69e11e9d03f6db338d1dd6#69180dc7275f5f0efb69e11e9d03f6db338d1dd6" dependencies = [ "convert_case 0.6.0", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -3039,9 +3065,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fdlimit" @@ -3076,16 +3102,16 @@ dependencies = [ "num-bigint 0.3.3", "num-integer", "num-traits", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] name = "fixed-hash" @@ -3125,9 +3151,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flagset" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a7e408202050813e6f1d9addadcaafef3dca7530c7ddfb005d4081cce6779" +checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" dependencies = [ "serde", ] @@ -3167,6 +3193,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -3274,9 +3315,9 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -3293,9 +3334,9 @@ checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", "send_wrapper 0.4.0", @@ -3373,9 +3414,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -3386,9 +3427,9 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", "polyval", @@ -3433,9 +3474,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -3443,7 +3484,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.2", + "indexmap 2.2.6", "slab", "tokio", "tokio-util 0.7.10", @@ -3456,7 +3497,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", ] [[package]] @@ -3465,7 +3506,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.11", ] [[package]] @@ -3536,11 +3577,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -3557,7 +3604,17 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", ] [[package]] @@ -3569,6 +3626,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + [[package]] name = "home" version = "0.5.9" @@ -3580,9 +3648,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -3651,7 +3719,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -3721,6 +3789,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -3825,8 +3906,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -3849,9 +3930,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -3870,9 +3951,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.34.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" +checksum = "3eab73f58e59ca6526037208f0e98851159ec1633cf17b6cd2e1f2c3fd5d53cc" dependencies = [ "console", "lazy_static", @@ -3881,7 +3962,6 @@ dependencies = [ "pest_derive", "serde", "similar", - "yaml-rust", ] [[package]] @@ -3932,7 +4012,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab388864246d58a276e60e7569a833d9cc4cd75c66e5ca77c177dad38e59996" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", "dashmap", "hashbrown 0.12.3", "once_cell", @@ -3955,54 +4035,52 @@ dependencies = [ ] [[package]] -name = "is-terminal" -version = "0.4.11" +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe8f25ce1159c7740ff0b9b2f5cdf4a8428742ba7c112b9f20f22cd5219c7dab" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", + "either", ] [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -4066,31 +4144,33 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", "bit-set", - "diff", "ena", - "is-terminal", - "itertools 0.10.5", + "itertools 0.11.0", "lalrpop-util", "petgraph 0.6.4", "regex", - "regex-syntax 0.7.5", + "regex-syntax", "string_cache", "term", "tiny-keccak", "unicode-xid 0.2.4", + "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata", +] [[package]] name = "lazy_static" @@ -4115,12 +4195,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.5", ] [[package]] @@ -4131,13 +4211,12 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", - "redox_syscall", ] [[package]] @@ -4156,11 +4235,59 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "libz-sys" -version = "1.1.15" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" dependencies = [ "cc", "pkg-config", @@ -4191,9 +4318,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "serde", ] @@ -4238,9 +4365,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "merlin" @@ -4287,9 +4414,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -4319,8 +4446,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -4632,8 +4759,8 @@ version = "0.1.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ "enum-compat-util", - "quote 1.0.35", - "syn 2.0.48", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -4697,7 +4824,7 @@ name = "msim" version = "0.1.0" source = "git+https://github.com/MystenLabs/mysten-sim.git?rev=1a52783d6600ecc22e15253a982f77881bd47c77#1a52783d6600ecc22e15253a982f77881bd47c77" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", "async-task", "bincode", "bytes", @@ -4727,8 +4854,8 @@ version = "0.1.0" source = "git+https://github.com/MystenLabs/mysten-sim.git?rev=1a52783d6600ecc22e15253a982f77881bd47c77#1a52783d6600ecc22e15253a982f77881bd47c77" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -4781,8 +4908,8 @@ checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro-error", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", "synstructure", ] @@ -4872,7 +4999,7 @@ dependencies = [ "tap", "tokio", "tracing", - "uuid 1.7.0", + "uuid 1.8.0", "workspace-hack", ] @@ -4925,7 +5052,7 @@ name = "mysten-util-mem-derive" version = "0.1.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ - "proc-macro2 1.0.78", + "proc-macro2 1.0.81", "syn 1.0.109", "synstructure", "workspace-hack", @@ -4983,6 +5110,24 @@ dependencies = [ "workspace-hack", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "neptune" version = "13.0.0" @@ -5004,9 +5149,9 @@ dependencies = [ [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nexus-gateway" @@ -5055,9 +5200,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" dependencies = [ "num-bigint 0.4.4", "num-complex", @@ -5128,8 +5273,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -5201,9 +5346,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro-crate 3.1.0", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -5232,9 +5377,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "open-fastrlp" @@ -5256,17 +5401,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" dependencies = [ "bytes", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -5302,9 +5485,9 @@ checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -5380,8 +5563,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" dependencies = [ "proc-macro-crate 1.1.3", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -5392,8 +5575,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ "proc-macro-crate 2.0.0", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -5474,7 +5657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", "password-hash", "sha2 0.10.8", ] @@ -5486,7 +5669,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -5512,8 +5695,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" dependencies = [ "peg-runtime", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", ] [[package]] @@ -5557,9 +5740,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.7" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" +checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" dependencies = [ "memchr", "thiserror", @@ -5568,9 +5751,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.7" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" +checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" dependencies = [ "pest", "pest_generator", @@ -5578,22 +5761,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.7" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" +checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] name = "pest_meta" -version = "2.7.7" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" +checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" dependencies = [ "once_cell", "pest", @@ -5617,7 +5800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.2.2", + "indexmap 2.2.6", ] [[package]] @@ -5658,9 +5841,9 @@ checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", "phf_shared 0.11.2", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -5683,29 +5866,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -5741,27 +5924,27 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.8", + "der 0.7.9", "spki 0.7.3", ] [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "polyval" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", @@ -5823,18 +6006,18 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ - "proc-macro2 1.0.78", + "proc-macro2 1.0.81", "syn 1.0.109", ] [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" dependencies = [ - "proc-macro2 1.0.78", - "syn 2.0.48", + "proc-macro2 1.0.81", + "syn 2.0.60", ] [[package]] @@ -5907,8 +6090,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", "version_check", ] @@ -5919,8 +6102,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "version_check", ] @@ -5935,9 +6118,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -5976,13 +6159,13 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.2", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.2", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -6021,12 +6204,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" dependencies = [ "bytes", - "prost-derive 0.12.3", + "prost-derive 0.12.4", ] [[package]] @@ -6059,8 +6242,8 @@ checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -6072,22 +6255,22 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" dependencies = [ "anyhow", - "itertools 0.11.0", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "itertools 0.12.1", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -6157,7 +6340,7 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2 0.5.5", + "socket2 0.5.6", "tracing", "windows-sys 0.48.0", ] @@ -6173,11 +6356,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "proc-macro2 1.0.78", + "proc-macro2 1.0.81", ] [[package]] @@ -6257,9 +6440,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -6293,9 +6476,9 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -6327,9 +6510,9 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -6351,45 +6534,39 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.2", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - -[[package]] -name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "report" @@ -6404,9 +6581,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.24" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", "bytes", @@ -6418,10 +6595,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls 0.24.2", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -6433,6 +6612,7 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls 0.24.1", "tower-service", "url", @@ -6443,12 +6623,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "retain_mut" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086" - [[package]] name = "rewards" version = "0.1.0" @@ -6473,7 +6647,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] @@ -6494,16 +6668,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6532,20 +6707,19 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] [[package]] name = "roaring" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6106b5cf8587f5834158895e9715a3c6c9716c8aefab57f1f7680917191c7873" +checksum = "a1c77081a55300e016cb86f2864415b7518741879db925b8d488a0ee0d2da6bf" dependencies = [ "bytemuck", "byteorder", - "retain_mut", ] [[package]] @@ -6638,11 +6812,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -6669,7 +6843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-webpki", "sct 0.7.1", ] @@ -6701,15 +6875,15 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "rusty-fork" @@ -6725,9 +6899,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "salsa20" @@ -6749,9 +6923,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.10.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +checksum = "7c453e59a955f81fb62ee5d596b450383d699f152d350e9d23a0db2adb78e4c0" dependencies = [ "cfg-if", "derive_more", @@ -6761,13 +6935,13 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.10.0" +version = "2.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +checksum = "18cf6c6447f813ef19eb450e985bcce6705f9ce7660db221b59093d15c79c4b7" dependencies = [ "proc-macro-crate 1.1.3", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -6799,8 +6973,8 @@ version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "serde_derive_internals", "syn 1.0.109", ] @@ -6817,7 +6991,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" dependencies = [ - "hmac", + "hmac 0.12.1", "pbkdf2 0.11.0", "salsa20", "sha2 0.10.8", @@ -6839,7 +7013,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] @@ -6850,7 +7024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der 0.7.8", + "der 0.7.9", "generic-array", "pkcs8 0.10.2", "subtle", @@ -6879,9 +7053,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -6892,9 +7066,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -6902,9 +7076,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" dependencies = [ "serde", ] @@ -6923,9 +7097,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] @@ -6971,13 +7145,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -6986,18 +7160,18 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -7005,9 +7179,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -7015,13 +7189,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -7063,19 +7237,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" dependencies = [ "base64 0.21.7", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.2", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", - "serde_with_macros 3.6.1", + "serde_with_macros 3.7.0", "time", ] @@ -7085,22 +7259,22 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ - "darling 0.20.5", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "darling 0.20.8", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] name = "serde_with_macros" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" dependencies = [ - "darling 0.20.5", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "darling 0.20.8", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -7257,9 +7431,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "simple_asn1" @@ -7300,9 +7474,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "snap" @@ -7322,12 +7496,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -7373,7 +7547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der 0.7.8", + "der 0.7.9", ] [[package]] @@ -7403,9 +7577,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -7425,6 +7599,15 @@ dependencies = [ "strum_macros 0.25.3", ] +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros 0.26.2", +] + [[package]] name = "strum_macros" version = "0.24.3" @@ -7432,8 +7615,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "rustversion", "syn 1.0.109", ] @@ -7445,10 +7628,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", + "rustversion", + "syn 2.0.60", +] + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck 0.4.1", + "proc-macro2 1.0.81", + "quote 1.0.36", "rustversion", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -7589,10 +7785,10 @@ version = "0.7.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ "msim-macros", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "sui-enum-compat-util", - "syn 2.0.48", + "syn 2.0.60", "workspace-hack", ] @@ -7616,8 +7812,8 @@ name = "sui-protocol-config-macros" version = "0.1.0" source = "git+https://github.com/mystenlabs/sui?tag=mainnet-v1.14.2#299cbeafbb6aa5601e08f00ac24bd647c61a63e2" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", "workspace-hack", ] @@ -7733,19 +7929,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.48" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "unicode-ident", ] @@ -7761,8 +7957,8 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", "unicode-xid 0.2.4", ] @@ -7807,8 +8003,8 @@ checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -7820,12 +8016,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.0.1", + "fastrand 2.0.2", "rustix", "windows-sys 0.52.0", ] @@ -8017,29 +8213,29 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -8056,9 +8252,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -8077,9 +8273,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -8116,11 +8312,11 @@ source = "git+https://github.com/axelarnetwork/tofn.git?branch=update-deps#88285 dependencies = [ "bincode", "crypto-bigint", - "der 0.7.8", + "der 0.7.9", "ecdsa", "ed25519 2.2.3", "ed25519-dalek", - "hmac", + "hmac 0.12.1", "k256", "rand", "rand_chacha", @@ -8132,9 +8328,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -8144,7 +8340,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.6", "tokio-macros 2.2.0", "windows-sys 0.48.0", ] @@ -8164,9 +8360,9 @@ name = "tokio-macros" version = "2.1.0" source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=e4693500118d5e79ce098ee6dfc2c48f3ef19e45#e4693500118d5e79ce098ee6dfc2c48f3ef19e45" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -8175,9 +8371,19 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -8203,9 +8409,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -8270,14 +8476,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.4", + "toml_edit 0.22.9", ] [[package]] @@ -8295,9 +8501,9 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -8306,22 +8512,22 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.4" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.2.2", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.6", ] [[package]] @@ -8374,7 +8580,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.3", + "prost 0.12.4", "rustls 0.21.10", "rustls-pemfile", "tokio", @@ -8393,9 +8599,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ "prettyplease 0.1.25", - "proc-macro2 1.0.78", + "proc-macro2 1.0.81", "prost-build", - "quote 1.0.35", + "quote 1.0.36", "syn 1.0.109", ] @@ -8406,7 +8612,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f80db390246dfb46553481f6024f0082ba00178ea495dbb99e70ba9a4fafb5e1" dependencies = [ "async-stream", - "prost 0.12.3", + "prost 0.12.4", "tokio", "tokio-stream", "tonic 0.10.2", @@ -8460,7 +8666,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid 1.7.0", + "uuid 1.8.0", ] [[package]] @@ -8493,9 +8699,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -8566,8 +8772,8 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -8684,9 +8890,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -8778,9 +8984,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", "rand", @@ -8801,8 +9007,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d44690c645190cfce32f91a1582281654b2338c6073fa250b0949fd25c55b32" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", + "proc-macro2 1.0.81", + "quote 1.0.36", "syn 1.0.109", ] @@ -8822,7 +9028,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae2faf80ac463422992abf4de234731279c058aaf33171ca70277c98406b124" dependencies = [ - "quote 1.0.35", + "quote 1.0.36", "syn 1.0.109", ] @@ -8873,9 +9079,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -8896,11 +9102,17 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -8908,24 +9120,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -8935,38 +9147,38 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ - "quote 1.0.35", + "quote 1.0.36", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -9011,11 +9223,12 @@ dependencies = [ [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "wasm-bindgen", + "redox_syscall", + "wasite", "web-sys", ] @@ -9062,7 +9275,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -9080,7 +9293,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -9100,17 +9313,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -9121,9 +9335,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -9133,9 +9347,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -9145,9 +9359,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -9157,9 +9377,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -9169,9 +9389,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -9181,9 +9401,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -9193,15 +9413,24 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.5.39" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" dependencies = [ "memchr", ] @@ -9282,7 +9511,7 @@ dependencies = [ "ascii", "axelar-wasm-std", "axelar-wasm-std-derive", - "bs58 0.5.0", + "bs58 0.5.1", "connection-router", "cosmwasm-schema", "cosmwasm-std", @@ -9306,6 +9535,70 @@ dependencies = [ "voting-verifier", ] +[[package]] +name = "xrpl_api" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4576f242134156dea2cf7ead1ea2e2efe890a4588d9b1564a5256f0ffb8f246" +dependencies = [ + "enumflags2", + "serde", + "serde_json", + "xrpl_types", +] + +[[package]] +name = "xrpl_binary_codec" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b963e7cacf52dec89c6333984dfee9d1495cfa5b65cb867846fd4582e09af59" +dependencies = [ + "ascii", + "bs58 0.5.1", + "enumflags2", + "hex", + "libsecp256k1", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", + "xrpl_types", +] + +[[package]] +name = "xrpl_http_client" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e60c050783dbb46974d508a45c4b292ecc2b8de0e4db11180a27de6d25581e" +dependencies = [ + "libsecp256k1", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", + "tokio", + "tracing", + "xrpl_api", + "xrpl_binary_codec", + "xrpl_types", +] + +[[package]] +name = "xrpl_types" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d2955eacb8e921944c530d30daa941af3e93750b88d95dff3702dd9711f044b" +dependencies = [ + "ascii", + "bs58 0.5.1", + "enumflags2", + "hex", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "yaml-rust" version = "0.4.5" @@ -9345,9 +9638,9 @@ version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -9365,9 +9658,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.78", - "quote 1.0.35", - "syn 2.0.48", + "proc-macro2 1.0.81", + "quote 1.0.36", + "syn 2.0.60", ] [[package]] @@ -9383,7 +9676,7 @@ dependencies = [ "crc32fast", "crossbeam-utils", "flate2", - "hmac", + "hmac 0.12.1", "pbkdf2 0.11.0", "sha1", "time", @@ -9411,9 +9704,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index fe1766b77..8cc61fac8 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -64,6 +64,7 @@ valuable = { version = "0.1.0", features = ["derive"] } valuable-serde = { version = "0.1.0", features = ["std"] } voting-verifier = { workspace = true } xrpl-multisig-prover = { workspace = true } +xrpl_http_client = { version = "0.15.0" } [dev-dependencies] elliptic-curve = "0.13.5" diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index b45d29e50..974969226 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -48,6 +48,11 @@ pub enum Config { rpc_url: Url, rpc_timeout: Option, }, + XRPLMsgVerifier { + cosmwasm_contract: TMAddress, + rpc_url: Url, + rpc_timeout: Option, + } } fn validate_multisig_signer_config<'de, D>(configs: &[Config]) -> Result<(), D::Error> diff --git a/ampd/src/handlers/mod.rs b/ampd/src/handlers/mod.rs index ddbe3c403..016ccc309 100644 --- a/ampd/src/handlers/mod.rs +++ b/ampd/src/handlers/mod.rs @@ -8,6 +8,7 @@ pub mod multisig; pub mod xrpl_multisig; pub mod sui_verify_msg; pub mod sui_verify_worker_set; +pub mod xrpl_verify_msg; #[cfg(test)] mod tests { diff --git a/ampd/src/handlers/xrpl_verify_msg.rs b/ampd/src/handlers/xrpl_verify_msg.rs new file mode 100644 index 000000000..7ad999d19 --- /dev/null +++ b/ampd/src/handlers/xrpl_verify_msg.rs @@ -0,0 +1,173 @@ +use std::collections::{HashMap, HashSet}; +use std::convert::TryInto; + +use async_trait::async_trait; +use cosmrs::cosmwasm::MsgExecuteContract; +use error_stack::ResultExt; +use serde::Deserialize; +use axelar_wasm_std::voting::{PollId, Vote}; +use tokio::sync::watch::Receiver; + +use events_derive::try_from; +use voting_verifier::msg::ExecuteMsg; +use xrpl_http_client::Amount; +use events::{Error::EventTypeMismatch, Event}; +use tracing::info; + +use crate::event_processor::EventHandler; +use crate::handlers::errors::Error; +use crate::queue::queued_broadcaster::BroadcasterClient; +use crate::types::{Hash, TMAddress}; +use crate::xrpl::json_rpc::XRPLClient; +use crate::xrpl::verifier::verify_message; + +type Result = error_stack::Result; + +#[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +pub struct TransactionId(String); + +impl TransactionId { + pub fn as_str(&self) -> &str { + return self.0.as_str(); + } +} + +#[derive(Deserialize, Debug, PartialEq)] +pub struct XRPLAddress(pub String); + +#[derive(Deserialize, Debug)] +pub struct Message { + pub tx_id: TransactionId, + pub amount: Amount, // TODO: how is this going to be specified? + pub destination_address: String, + pub destination_chain: connection_router::state::ChainName, + pub source_address: XRPLAddress, + pub payload_hash: Hash, +} + +#[derive(Deserialize, Debug)] +#[try_from("wasm-messages_poll_started")] +struct PollStartedEvent { + #[serde(rename = "_contract_address")] + contract_address: TMAddress, + poll_id: PollId, + source_gateway_address: XRPLAddress, + messages: Vec, + participants: Vec, + expires_at: u64, +} + +pub struct Handler +where + C: XRPLClient, + B: BroadcasterClient, +{ + worker: TMAddress, + voting_verifier: TMAddress, + rpc_client: C, + broadcast_client: B, + latest_block_height: Receiver, +} + +impl Handler +where + C: XRPLClient, + B: BroadcasterClient, +{ + pub fn new( + worker: TMAddress, + voting_verifier: TMAddress, + rpc_client: C, + broadcast_client: B, + latest_block_height: Receiver, + ) -> Self { + Self { + worker, + voting_verifier, + rpc_client, + broadcast_client, + latest_block_height, + } + } + async fn broadcast_votes(&self, poll_id: PollId, votes: Vec) -> Result<()> { + let msg = serde_json::to_vec(&ExecuteMsg::Vote { poll_id, votes }) + .expect("vote msg should serialize"); + let tx = MsgExecuteContract { + sender: self.worker.as_ref().clone(), + contract: self.voting_verifier.as_ref().clone(), + msg, + funds: vec![], + }; + + self.broadcast_client + .broadcast(tx) + .await + .change_context(Error::Broadcaster) + } +} + +#[async_trait] +impl EventHandler for Handler +where + C: XRPLClient + Send + Sync, + B: BroadcasterClient + Send + Sync, +{ + type Err = Error; + + async fn handle(&self, event: &Event) -> Result<()> { + let PollStartedEvent { + contract_address, + poll_id, + source_gateway_address, + messages, + participants, + expires_at, + .. + } = match event.try_into() as error_stack::Result<_, _> { + Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { + return Ok(()); + } + event => event.change_context(Error::DeserializeEvent)?, + }; + + if self.voting_verifier != contract_address { + return Ok(()); + } + + if !participants.contains(&self.worker) { + return Ok(()); + } + + let latest_block_height = *self.latest_block_height.borrow(); + if latest_block_height >= expires_at { + info!(poll_id = poll_id.to_string(), "skipping expired poll"); + return Ok(()); + } + + // Does not assume voting verifier emits unique tx ids. + // RPC will throw an error if the input contains any duplicate, deduplicate tx ids to avoid unnecessary failures. + let deduplicated_tx_ids: HashSet<_> = messages.iter().map(|msg| msg.tx_id.clone()).collect(); + + let mut tx_responses = HashMap::new(); + + for tx_id in deduplicated_tx_ids { + match self.rpc_client.fetch_tx(&tx_id).await { + Ok(res) => { tx_responses.insert(tx_id, res); }, + Err(e) => return Err(e.change_context(Error::TxReceipts)) + } + } + + let votes = messages + .iter() + .map(|msg| { + tx_responses + .get(&msg.tx_id) + .map_or(Vote::NotFound, |tx_block| { + verify_message(&source_gateway_address, tx_block, msg) + }) + }) + .collect(); + + self.broadcast_votes(poll_id, votes).await + } +} \ No newline at end of file diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index 934140c6e..c4da20b7f 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -43,6 +43,7 @@ mod tm_client; mod tofnd; mod types; mod url; +mod xrpl; const PREFIX: &str = "axelar"; const DEFAULT_RPC_TIMEOUT: Duration = Duration::from_secs(3); @@ -288,6 +289,29 @@ where ), stream_timeout, ), + handlers::config::Config::XRPLMsgVerifier { + cosmwasm_contract, + rpc_url, + rpc_timeout + } => self.create_handler_task( + "xrpl-msg-verifier", + handlers::xrpl_verify_msg::Handler::new( + worker.clone(), + cosmwasm_contract, + xrpl_http_client::Client::builder() + .base_url(rpc_url.as_str()) + .http_client( + reqwest::ClientBuilder::new() + .connect_timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) + .timeout(rpc_timeout.unwrap_or(DEFAULT_RPC_TIMEOUT)) + .build() + .change_context(Error::Connection)?, + ).build(), + self.broadcaster.client(), + self.block_height_monitor.latest_block_height(), + ), + stream_timeout, + ), handlers::config::Config::SuiWorkerSetVerifier { cosmwasm_contract, rpc_url, diff --git a/ampd/src/xrpl/json_rpc.rs b/ampd/src/xrpl/json_rpc.rs new file mode 100644 index 000000000..82bce9c18 --- /dev/null +++ b/ampd/src/xrpl/json_rpc.rs @@ -0,0 +1,28 @@ +use async_trait::async_trait; +use mockall::automock; +use crate::handlers::xrpl_verify_msg::TransactionId; +use xrpl_http_client::{TxRequest, TxResponse, Client, error}; + +type Result = error_stack::Result; + +#[automock] +#[async_trait] +pub trait XRPLClient { + async fn fetch_tx( + &self, + tx_id: &TransactionId, + ) -> Result; +} + +#[async_trait] +impl XRPLClient for Client +{ + async fn fetch_tx( + &self, + tx_id: &TransactionId, + ) -> Result { + let req = TxRequest::new(tx_id.as_str()); + + Ok(self.call(req).await?) + } +} \ No newline at end of file diff --git a/ampd/src/xrpl/mod.rs b/ampd/src/xrpl/mod.rs new file mode 100644 index 000000000..f7b6096ac --- /dev/null +++ b/ampd/src/xrpl/mod.rs @@ -0,0 +1,2 @@ +pub mod verifier; +pub mod json_rpc; \ No newline at end of file diff --git a/ampd/src/xrpl/verifier.rs b/ampd/src/xrpl/verifier.rs new file mode 100644 index 000000000..8f6716607 --- /dev/null +++ b/ampd/src/xrpl/verifier.rs @@ -0,0 +1,40 @@ +use std::collections::HashMap; + +use crate::types::Hash; +use k256::sha2::Sha256; +use sha3::Digest; +use xrpl_http_client::{client::Client, Memo, Transaction::Payment, TxRequest, TxResponse}; +use axelar_wasm_std::voting::Vote; + +use crate::handlers::xrpl_verify_msg::{Message, XRPLAddress}; + +pub fn verify_message( + gateway_address: &XRPLAddress, + tx_response: &TxResponse, + message: &Message, +) -> Vote { + match &tx_response.tx { + Payment(payment_tx) if payment_tx.destination == gateway_address.0 && payment_tx.amount == message.amount && matches!(payment_tx.clone().common.memos, Some(_memos)) && verify_memos(payment_tx.clone().common.memos.unwrap(), message) => { + Vote::SucceededOnChain + }, + _ => Vote::NotFound, + } +} + +pub fn verify_memos(memos: Vec, message: &Message) -> bool { + let memo_kv: HashMap = memos + .into_iter() + .filter(|m| m.memo_type.is_some() && m.memo_data.is_some()) + .map(|m| (m.memo_type.unwrap(), m.memo_data.unwrap())) + .collect(); + + // TODO: how are memo types converted to strings here, maybe they need hex decoding? + memo_kv.get("destination_address") == Some(&message.destination_address) + && memo_kv.get("destination_chain") == Some(&message.destination_chain.to_string()) + && (memo_kv.get("payload_hash") == Some(&message.payload_hash.to_string()) || (memo_kv.contains_key("payload") && verify_payload(memo_kv.get("payload").unwrap(), &message.payload_hash))) +} + +// TODO: is ethers Hash type ok here? +pub fn verify_payload(payload: &String, payload_hash: &Hash) -> bool { + Sha256::digest(payload) == payload_hash.to_fixed_bytes().into() +} \ No newline at end of file From ee23777d98fe47c67a0de24ef0287ce302f65d1c Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Mon, 12 Feb 2024 20:20:09 +0200 Subject: [PATCH 65/80] ampd xrpl verification logic: separate multisig generated txs and deposits, finality, success codes --- ampd/src/handlers/xrpl_verify_msg.rs | 20 +++--------- ampd/src/xrpl/json_rpc.rs | 2 +- ampd/src/xrpl/mod.rs | 1 + ampd/src/xrpl/verifier.rs | 47 +++++++++++++++++++++++----- 4 files changed, 45 insertions(+), 25 deletions(-) diff --git a/ampd/src/handlers/xrpl_verify_msg.rs b/ampd/src/handlers/xrpl_verify_msg.rs index 7ad999d19..7d23fb70d 100644 --- a/ampd/src/handlers/xrpl_verify_msg.rs +++ b/ampd/src/handlers/xrpl_verify_msg.rs @@ -10,7 +10,6 @@ use tokio::sync::watch::Receiver; use events_derive::try_from; use voting_verifier::msg::ExecuteMsg; -use xrpl_http_client::Amount; use events::{Error::EventTypeMismatch, Event}; use tracing::info; @@ -20,25 +19,14 @@ use crate::queue::queued_broadcaster::BroadcasterClient; use crate::types::{Hash, TMAddress}; use crate::xrpl::json_rpc::XRPLClient; use crate::xrpl::verifier::verify_message; +use crate::xrpl::types::{TransactionId, XRPLAddress}; type Result = error_stack::Result; -#[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone)] -pub struct TransactionId(String); - -impl TransactionId { - pub fn as_str(&self) -> &str { - return self.0.as_str(); - } -} - -#[derive(Deserialize, Debug, PartialEq)] -pub struct XRPLAddress(pub String); - #[derive(Deserialize, Debug)] pub struct Message { pub tx_id: TransactionId, - pub amount: Amount, // TODO: how is this going to be specified? + pub event_index: u64, pub destination_address: String, pub destination_chain: connection_router::state::ChainName, pub source_address: XRPLAddress, @@ -162,8 +150,8 @@ where .map(|msg| { tx_responses .get(&msg.tx_id) - .map_or(Vote::NotFound, |tx_block| { - verify_message(&source_gateway_address, tx_block, msg) + .map_or(Vote::NotFound, |tx_response| { + verify_message(&source_gateway_address, &tx_response.tx, msg) }) }) .collect(); diff --git a/ampd/src/xrpl/json_rpc.rs b/ampd/src/xrpl/json_rpc.rs index 82bce9c18..d6850f7cf 100644 --- a/ampd/src/xrpl/json_rpc.rs +++ b/ampd/src/xrpl/json_rpc.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use mockall::automock; -use crate::handlers::xrpl_verify_msg::TransactionId; +use crate::xrpl::types::TransactionId; use xrpl_http_client::{TxRequest, TxResponse, Client, error}; type Result = error_stack::Result; diff --git a/ampd/src/xrpl/mod.rs b/ampd/src/xrpl/mod.rs index f7b6096ac..8f5e702c4 100644 --- a/ampd/src/xrpl/mod.rs +++ b/ampd/src/xrpl/mod.rs @@ -1,2 +1,3 @@ +pub mod types; pub mod verifier; pub mod json_rpc; \ No newline at end of file diff --git a/ampd/src/xrpl/verifier.rs b/ampd/src/xrpl/verifier.rs index 8f6716607..eec4f6a66 100644 --- a/ampd/src/xrpl/verifier.rs +++ b/ampd/src/xrpl/verifier.rs @@ -1,26 +1,57 @@ use std::collections::HashMap; +use std::str::FromStr; use crate::types::Hash; +use connection_router::state::ChainName; use k256::sha2::Sha256; use sha3::Digest; -use xrpl_http_client::{client::Client, Memo, Transaction::Payment, TxRequest, TxResponse}; +use xrpl_http_client::ResultCategory; +use xrpl_http_client::{Memo, Transaction::Payment, Transaction}; use axelar_wasm_std::voting::Vote; -use crate::handlers::xrpl_verify_msg::{Message, XRPLAddress}; +use crate::handlers::xrpl_verify_msg::Message; +use crate::xrpl::types::XRPLAddress; pub fn verify_message( - gateway_address: &XRPLAddress, - tx_response: &TxResponse, + multisig_address: &XRPLAddress, + tx: &Transaction, message: &Message, ) -> Vote { - match &tx_response.tx { - Payment(payment_tx) if payment_tx.destination == gateway_address.0 && payment_tx.amount == message.amount && matches!(payment_tx.clone().common.memos, Some(_memos)) && verify_memos(payment_tx.clone().common.memos.unwrap(), message) => { + if is_validated_tx(tx) && (is_valid_multisig_tx(tx, multisig_address, message) || is_valid_deposit_tx(tx, multisig_address, message)) { + if is_successful_tx(tx) { Vote::SucceededOnChain - }, - _ => Vote::NotFound, + } else { + Vote::FailedOnChain + } + } else { + Vote::NotFound } } +pub fn is_validated_tx(tx: &Transaction) -> bool { + matches!(tx.common().validated, Some(true)) +} + +pub fn is_valid_multisig_tx(tx: &Transaction, multisig_address: &XRPLAddress, message: &Message) -> bool { + tx.common().account == multisig_address.0 && message.source_address == *multisig_address && message.destination_chain == ChainName::from_str("XRPL").unwrap() +} + +pub fn is_valid_deposit_tx(tx: &Transaction, multisig_address: &XRPLAddress, message: &Message) -> bool { + if let Payment(payment_tx) = &tx { + if let Some(memos) = payment_tx.clone().common.memos { + return payment_tx.destination == multisig_address.0 && message.source_address.0 == tx.common().account && verify_memos(memos, message); + } + } + return false; +} + +pub fn is_successful_tx(tx: &Transaction) -> bool { + if let Some(meta) = &tx.common().meta { + return meta.transaction_result.category() == ResultCategory::Tes; + } + return false; +} + pub fn verify_memos(memos: Vec, message: &Message) -> bool { let memo_kv: HashMap = memos .into_iter() From 4a66bdd3121411fd97e1a5daad0204deebf38e83 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 13 Feb 2024 11:12:54 +0200 Subject: [PATCH 66/80] Revert "add message_provider option on multisig handler" This reverts commit 213231436bf41fa13bc7444d60fbf1f663ba84a0. --- ampd/src/commands/register_public_key.rs | 2 +- ampd/src/config.rs | 1 - ampd/src/handlers/config.rs | 1 - ampd/src/handlers/multisig.rs | 14 +------------- ampd/src/lib.rs | 3 +-- 5 files changed, 3 insertions(+), 18 deletions(-) diff --git a/ampd/src/commands/register_public_key.rs b/ampd/src/commands/register_public_key.rs index 46d062574..62c44b9d7 100644 --- a/ampd/src/commands/register_public_key.rs +++ b/ampd/src/commands/register_public_key.rs @@ -82,7 +82,7 @@ fn get_multisig_address(config: &Config) -> Result { .handlers .iter() .find_map(|config| { - if let handlers::config::Config::MultisigSigner { cosmwasm_contract, message_provider } = config { + if let handlers::config::Config::MultisigSigner { cosmwasm_contract } = config { Some(cosmwasm_contract.clone()) } else { None diff --git a/ampd/src/config.rs b/ampd/src/config.rs index fb3a9cf83..392b17b39 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -283,7 +283,6 @@ mod tests { cosmwasm_contract: TMAddress::from( AccountId::new("axelar", &[0u8; 32]).unwrap(), ), - message_provider: None, }, HandlerConfig::SuiMsgVerifier { cosmwasm_contract: TMAddress::from( diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index 974969226..046e70b63 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -33,7 +33,6 @@ pub enum Config { }, MultisigSigner { cosmwasm_contract: TMAddress, - message_provider: Option, }, XRPLMultisigSigner { cosmwasm_contract: TMAddress, diff --git a/ampd/src/handlers/multisig.rs b/ampd/src/handlers/multisig.rs index 3964aadcb..2bf4115cd 100644 --- a/ampd/src/handlers/multisig.rs +++ b/ampd/src/handlers/multisig.rs @@ -77,7 +77,6 @@ where broadcaster: B, signer: SharableEcdsaClient, latest_block_height: Receiver, - message_provider: Option, } impl Handler @@ -90,7 +89,6 @@ where broadcaster: B, signer: SharableEcdsaClient, latest_block_height: Receiver, - message_provider: Option, ) -> Self { Self { worker, @@ -98,7 +96,6 @@ where broadcaster, signer, latest_block_height, - message_provider } } @@ -167,10 +164,6 @@ where return Ok(()); } - if let Some(msg_provider_address) = &self.message_provider { - // call contract to get message to sign - } - match pub_keys.get(&self.worker) { Some(pub_key) => { let signature = self @@ -290,7 +283,6 @@ mod test { multisig: TMAddress, signer: SharableEcdsaClient, latest_block_height: u64, - message_provider: Option, ) -> Handler { let mut broadcaster = MockBroadcaster::new(); broadcaster @@ -302,7 +294,7 @@ mod test { let (tx, rx) = watch::channel(latest_block_height); - Handler::new(worker, multisig, broadcaster.client(), signer, rx, message_provider) + Handler::new(worker, multisig, broadcaster.client(), signer, rx) } #[test] @@ -372,7 +364,6 @@ mod test { rand_account(), SharableEcdsaClient::new(client), 100u64, - None ); assert!(handler.handle(&signing_started_event()).await.is_ok()); @@ -390,7 +381,6 @@ mod test { TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), SharableEcdsaClient::new(client), 100u64, - None ); assert!(handler.handle(&signing_started_event()).await.is_ok()); @@ -411,7 +401,6 @@ mod test { TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), SharableEcdsaClient::new(client), 99u64, - None ); assert!(matches!( @@ -435,7 +424,6 @@ mod test { TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), SharableEcdsaClient::new(client), 101u64, - None ); assert!(handler.handle(&signing_started_event()).await.is_ok()); diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index c4da20b7f..0eda82a7f 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -242,7 +242,7 @@ where ), stream_timeout, ), - handlers::config::Config::MultisigSigner { cosmwasm_contract, message_provider } => self + handlers::config::Config::MultisigSigner { cosmwasm_contract } => self .create_handler_task( "multisig-signer", handlers::multisig::Handler::new( @@ -251,7 +251,6 @@ where self.broadcaster.client(), self.ecdsa_client.clone(), self.block_height_monitor.latest_block_height(), - message_provider ), stream_timeout, ), From 7e7c94caddfa24a9fd7805d60364d4ceba836f04 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 13 Feb 2024 11:29:56 +0200 Subject: [PATCH 67/80] ampd xrpl add missing xrpl types file --- ampd/src/xrpl/types.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 ampd/src/xrpl/types.rs diff --git a/ampd/src/xrpl/types.rs b/ampd/src/xrpl/types.rs new file mode 100644 index 000000000..2a7a71773 --- /dev/null +++ b/ampd/src/xrpl/types.rs @@ -0,0 +1,13 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +pub struct TransactionId(String); + +impl TransactionId { + pub fn as_str(&self) -> &str { + return self.0.as_str(); + } +} + +#[derive(Deserialize, Debug, PartialEq)] +pub struct XRPLAddress(pub String); \ No newline at end of file From 25d0e906d1835c19f5b7f293dfb878bb6699eedf Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Fri, 16 Feb 2024 13:04:33 +0100 Subject: [PATCH 68/80] ampd: xrpl memo payload handling with fee, relayer, amount, currencyg --- ampd/src/xrpl/verifier.rs | 40 ++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/ampd/src/xrpl/verifier.rs b/ampd/src/xrpl/verifier.rs index eec4f6a66..e719f15cc 100644 --- a/ampd/src/xrpl/verifier.rs +++ b/ampd/src/xrpl/verifier.rs @@ -2,12 +2,15 @@ use std::collections::HashMap; use std::str::FromStr; use crate::types::Hash; +use serde::Serialize; use connection_router::state::ChainName; use k256::sha2::Sha256; +use serde_json::to_string; use sha3::Digest; use xrpl_http_client::ResultCategory; use xrpl_http_client::{Memo, Transaction::Payment, Transaction}; use axelar_wasm_std::voting::Vote; +use cosmwasm_std::{Uint256, HexBinary}; use crate::handlers::xrpl_verify_msg::Message; use crate::xrpl::types::XRPLAddress; @@ -52,6 +55,15 @@ pub fn is_successful_tx(tx: &Transaction) -> bool { return false; } +#[derive(Serialize, Debug, PartialEq)] +struct MemoPayload { + fee: Uint256, + relayer: String, + amount: Uint256, + currency: String, + payload_hash: HexBinary +} + pub fn verify_memos(memos: Vec, message: &Message) -> bool { let memo_kv: HashMap = memos .into_iter() @@ -59,13 +71,23 @@ pub fn verify_memos(memos: Vec, message: &Message) -> bool { .map(|m| (m.memo_type.unwrap(), m.memo_data.unwrap())) .collect(); - // TODO: how are memo types converted to strings here, maybe they need hex decoding? - memo_kv.get("destination_address") == Some(&message.destination_address) - && memo_kv.get("destination_chain") == Some(&message.destination_chain.to_string()) - && (memo_kv.get("payload_hash") == Some(&message.payload_hash.to_string()) || (memo_kv.contains_key("payload") && verify_payload(memo_kv.get("payload").unwrap(), &message.payload_hash))) -} - -// TODO: is ethers Hash type ok here? -pub fn verify_payload(payload: &String, payload_hash: &Hash) -> bool { - Sha256::digest(payload) == payload_hash.to_fixed_bytes().into() + || -> Option { + let memo_payload_hash = memo_kv + .get("payload_hash") + .cloned() + .or_else(|| { + memo_kv.get("payload").map(|p| hex::encode(Sha256::digest(p).to_vec())) + })?; + let memo_payload = MemoPayload { + fee: Uint256::from_str(memo_kv.get("fee")?).ok()?, + relayer: memo_kv.get("relayer")?.clone(), + amount: Uint256::from_str(memo_kv.get("amount")?).ok()?, + currency: memo_kv.get("currency")?.clone(), + payload_hash: HexBinary::from_hex(memo_payload_hash.as_str()).ok()? + }; + let memo_hash: [u8; 32] = Sha256::digest(to_string(&memo_payload).ok()?).into(); + Some(memo_kv.get("destination_address") == Some(&message.destination_address) + && memo_kv.get("destination_chain") == Some(&message.destination_chain.to_string()) + && *message.payload_hash.as_fixed_bytes() == memo_hash) + }().unwrap_or(false) } \ No newline at end of file From a05b652ac45158dbe83ea8308d1579ccb5c68df0 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Thu, 18 Apr 2024 15:55:50 +0300 Subject: [PATCH 69/80] xrpl multisig prover: add compatibility with relayer --- .../xrpl-multisig-prover/src/contract.rs | 70 ++++++++++--------- contracts/xrpl-multisig-prover/src/error.rs | 7 +- contracts/xrpl-multisig-prover/src/events.rs | 2 - contracts/xrpl-multisig-prover/src/msg.rs | 12 ++-- contracts/xrpl-multisig-prover/src/querier.rs | 6 -- contracts/xrpl-multisig-prover/src/query.rs | 18 ++++- contracts/xrpl-multisig-prover/src/reply.rs | 13 ++-- contracts/xrpl-multisig-prover/src/state.rs | 3 +- contracts/xrpl-multisig-prover/src/types.rs | 21 ++---- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 19 ++--- .../src/xrpl_serialize.rs | 40 +++++------ integration-tests/tests/message_routing.rs | 9 +-- integration-tests/tests/test_utils/mod.rs | 12 +++- integration-tests/tests/update_worker_set.rs | 5 +- 14 files changed, 116 insertions(+), 121 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 01481be4f..d12fcfb42 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -10,7 +10,7 @@ use cosmwasm_std::{ }; use voting_verifier::events::parse_message_id; -use multisig::types::MultisigState; +use multisig::{key::PublicKey, types::MultisigState}; use crate::{ error::ContractError, @@ -40,6 +40,7 @@ pub struct InstantiateMsg { pub next_sequence_number: u32, pub last_assigned_ticket_number: u32, pub governance_address: String, + pub relayer_address: String, // TODO: REMOVE pub xrp_denom: String, } @@ -55,6 +56,7 @@ pub fn instantiate( let voting_verifier_address = deps.api.addr_validate(&msg.voting_verifier_address)?; let service_registry_address = deps.api.addr_validate(&msg.service_registry_address)?; let governance_address = deps.api.addr_validate(&msg.governance_address)?; + let relayer_address = deps.api.addr_validate(&msg.relayer_address)?; if msg.signing_threshold.numerator() > u32::MAX.into() || msg.signing_threshold.denominator() == Uint64::zero() { return Err(ContractError::InvalidSigningThreshold.into()); @@ -73,6 +75,7 @@ pub fn instantiate( ticket_count_threshold: msg.ticket_count_threshold, key_type: multisig::key::KeyType::Ecdsa, governance_address, + relayer_address, xrp_denom: msg.xrp_denom, }; @@ -105,15 +108,23 @@ pub fn require_governance(governance: &Addr, sender: &Addr) -> Result<(), Contra Ok(()) } +pub fn require_permissioned_relayer(relayer: &Addr, sender: &Addr) -> Result<(), ContractError> { + if relayer != sender { + return Err(ContractError::Unauthorized); + } + Ok(()) +} + fn register_token( storage: &mut dyn Storage, config: &Config, sender: &Addr, denom: String, token: &XRPLToken, + decimals: u8, ) -> Result { require_governance(&config.governance_address, sender)?; - TOKENS.save(storage, &denom, token)?; + TOKENS.save(storage, &denom, &(token.clone(), decimals))?; Ok(Response::default()) } @@ -128,20 +139,22 @@ pub fn execute( let querier = Querier::new(deps.querier, config.clone()); let res = match msg { - ExecuteMsg::RegisterToken { denom, token } => { - register_token(deps.storage, &config, &info.sender, denom, &token) + ExecuteMsg::RegisterToken { denom, token , decimals } => { + register_token(deps.storage, &config, &info.sender, denom, &token, decimals) }, - ExecuteMsg::ConstructProof { message_id } => { - construct_payment_proof(deps.storage, &querier, info, env.contract.address, env.block.height, &config, message_id) + // TODO: coin should be info.funds + ExecuteMsg::ConstructProof { message_id, coin } => { + require_permissioned_relayer(&config.relayer_address, &info.sender)?; + construct_payment_proof(deps.storage, &querier, env.contract.address, env.block.height, &config, message_id, &coin) }, ExecuteMsg::UpdateWorkerSet {} => { construct_signer_list_set_proof(deps.storage, &querier, env, &config) }, - ExecuteMsg::UpdateTxStatus { multisig_session_id, signers, message_id, message_status } => { - update_tx_status(deps.storage, &querier, &multisig_session_id, &signers, &message_id, message_status, config.axelar_multisig_address, config.xrpl_multisig_address) + ExecuteMsg::UpdateTxStatus { multisig_session_id, signer_public_keys, message_id, message_status } => { + update_tx_status(deps.storage, &querier, &multisig_session_id, &signer_public_keys, &message_id, message_status, config.axelar_multisig_address, config.xrpl_multisig_address) }, ExecuteMsg::TicketCreate {} => { - construct_ticket_create_proof(deps.storage, &querier, env.contract.address, &config) + construct_ticket_create_proof(deps.storage, env.contract.address, &config) }, }?; @@ -151,54 +164,47 @@ pub fn execute( fn construct_payment_proof( storage: &mut dyn Storage, querier: &Querier, - info: MessageInfo, self_address: Addr, block_height: u64, config: &Config, message_id: CrossChainId, + coin: &cosmwasm_std::Coin, ) -> Result { - if info.funds.len() != 1 { + // if info.funds.len() != 1 { + if coin.amount == cosmwasm_std::Uint128::zero() { return Err(ContractError::InvalidPaymentAmount); } - // Protect against double signing the same message ID + // Prevent creating a duplicate signing session before the previous one expires match MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, &message_id)? { Some(multisig_session_id) => { let multisig_session = querier.get_multisig_session(&Uint64::from(multisig_session_id))?; - if let MultisigState::Completed { .. } = multisig_session.state { - return Err(ContractError::PaymentAlreadySigned); - } - - if multisig_session.expires_at <= block_height { - return Err(ContractError::PaymentAlreadyHasActiveSigningSession); + if multisig_session.state == MultisigState::Pending && multisig_session.expires_at <= block_height { + return Err(ContractError::PaymentAlreadyHasActiveSigningSession(multisig_session_id)); } }, None => (), }; - let mut funds = info.funds; - let coin = funds.remove(0); let message = querier.get_message(&message_id)?; let xrpl_payment_amount = if coin.denom == config.xrp_denom { - // TODO: handle decimal precision conversion let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { reason: "overflow".to_string() })?; XRPLPaymentAmount::Drops(drops) } else { - let xrpl_token = TOKENS.load(storage, &coin.denom)?; + let (xrpl_token, decimals) = TOKENS.load(storage, &coin.denom)?; + // TODO: handle decimal precision conversion between CosmWasm Coin and XRPLToken XRPLPaymentAmount::Token( xrpl_token, - coin.amount.try_into()?, + canonicalize_coin_amount(coin.amount, decimals)?, ) }; - let multisig_session_id = querier.get_next_multisig_session_id()?; let tx_hash = xrpl_multisig::issue_payment( storage, config, message.destination_address.to_string().try_into()?, &xrpl_payment_amount, &message_id, - &multisig_session_id, )?; REPLY_MESSAGE_ID.save(storage, &message_id)?; @@ -254,12 +260,10 @@ fn construct_signer_list_set_proof( return Err(ContractError::WorkerSetUnchanged.into()) } - let multisig_session_id = querier.get_next_multisig_session_id()?; let tx_hash = xrpl_multisig::issue_signer_list_set( storage, config, cur_worker_set, - multisig_session_id, )?; NEXT_WORKER_SET.save(storage, &tx_hash, &new_worker_set)?; @@ -276,7 +280,6 @@ fn construct_signer_list_set_proof( fn construct_ticket_create_proof( storage: &mut dyn Storage, - querier: &Querier, self_address: Addr, config: &Config, ) -> Result { @@ -285,12 +288,10 @@ fn construct_ticket_create_proof( return Err(ContractError::TicketCountThresholdNotReached.into()); } - let multisig_session_id = querier.get_next_multisig_session_id()?; let tx_hash = xrpl_multisig::issue_ticket_create( storage, config, ticket_count, - multisig_session_id, )?; let response = start_signing_session( @@ -307,7 +308,7 @@ fn update_tx_status( storage: &mut dyn Storage, querier: &Querier, multisig_session_id: &Uint64, - signers: &Vec, + signer_public_keys: &Vec, message_id: &CrossChainId, status: VerificationStatus, axelar_multisig_address: impl Into, @@ -332,11 +333,11 @@ fn update_tx_status( let xrpl_signers: Vec = multisig_session.signers .iter() - .filter(|(signer, _)| signers.contains(&signer.address)) + .filter(|(signer, _)| signer_public_keys.contains(&signer.pub_key)) .filter_map(|(signer, signature)| signature.as_ref().map(|signature| XRPLSigner::try_from((signer.clone(), signature.clone())))) .collect::, ContractError>>()?; - if xrpl_signers.len() != signers.len() { + if xrpl_signers.len() != signer_public_keys.len() { return Err(ContractError::SignatureNotFound); } @@ -344,7 +345,7 @@ fn update_tx_status( let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?).into(); - if parse_message_id(&message_id.id).map_err(|e| ContractError::InvalidMessageID(message_id.id.to_string()))?.0.to_string() != tx_hash.to_string() { + if parse_message_id(&message_id.id).map_err(|_| ContractError::InvalidMessageID(message_id.id.to_string()))?.0.to_string() != tx_hash.to_string() { return Err(ContractError::InvalidMessageID(message_id.id.to_string())); } @@ -386,5 +387,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { signature } => to_json_binary(&query::verify_message(deps.storage, &multisig_session_id, &public_key, &signature)?), QueryMsg::GetWorkerSet {} => to_json_binary(&query::get_worker_set(deps.storage)?), + QueryMsg::GetMultisigSessionId { message_id } => to_json_binary(&query::get_multisig_session_id(deps.storage, &message_id)?), // TODO: rename } } diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index 43f6ef08e..f7710589b 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -98,11 +98,8 @@ pub enum ContractError { #[error("invalid message destination address")] InvalidMessageDestinationAddress, - #[error("payment already has active signing session")] - PaymentAlreadyHasActiveSigningSession, - - #[error("payment already signed")] - PaymentAlreadySigned, + #[error("payment already has active signing session with ID {0}")] + PaymentAlreadyHasActiveSigningSession(u64), #[error("invalid token denomination")] InvalidTokenDenom, diff --git a/contracts/xrpl-multisig-prover/src/events.rs b/contracts/xrpl-multisig-prover/src/events.rs index 99e598581..58c6c1ec6 100644 --- a/contracts/xrpl-multisig-prover/src/events.rs +++ b/contracts/xrpl-multisig-prover/src/events.rs @@ -6,8 +6,6 @@ use multisig::key::PublicKey; use serde_json::to_string; use crate::types::TxHash; - - pub enum Event { ProofUnderConstruction { tx_hash: TxHash, diff --git a/contracts/xrpl-multisig-prover/src/msg.rs b/contracts/xrpl-multisig-prover/src/msg.rs index 46099fe47..b027ed58d 100644 --- a/contracts/xrpl-multisig-prover/src/msg.rs +++ b/contracts/xrpl-multisig-prover/src/msg.rs @@ -1,6 +1,6 @@ use connection_router::state::CrossChainId; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{HexBinary, Uint64, Addr}; +use cosmwasm_std::{HexBinary, Uint64}; use axelar_wasm_std::VerificationStatus; use multisig::key::{PublicKey, Signature}; @@ -17,6 +17,9 @@ pub enum QueryMsg { #[returns(multisig::worker_set::WorkerSet)] GetWorkerSet, + + #[returns(Option)] + GetMultisigSessionId { message_id: CrossChainId }, } #[cw_serde] @@ -28,11 +31,12 @@ pub enum GetProofResponse { #[cw_serde] pub enum ExecuteMsg { - RegisterToken { denom: String, token: XRPLToken }, - ConstructProof { message_id: CrossChainId }, + RegisterToken { denom: String, token: XRPLToken, decimals: u8 }, + // TODO: remove coin parameter + ConstructProof { message_id: CrossChainId, coin: cosmwasm_std::Coin }, UpdateTxStatus { multisig_session_id: Uint64, - signers: Vec, + signer_public_keys: Vec, message_id: CrossChainId, message_status: VerificationStatus, }, diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index baafac693..7d1157c5a 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -81,10 +81,4 @@ impl<'a> Querier<'a> { }; query(self.querier, self.config.axelar_multisig_address.to_string(), &query_msg) } - - pub fn get_next_multisig_session_id(&self) -> Result { - query(self.querier, self.config.axelar_multisig_address.to_string(), - &multisig::msg::QueryMsg::GetNextSessionId, - ) - } } diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index f16eaf136..516f9df58 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -1,8 +1,10 @@ +use connection_router::state::CrossChainId; use cosmwasm_std::{StdResult, Uint64, HexBinary, Storage}; use multisig::{key::Signature, types::MultisigState}; use multisig::key::PublicKey; +use crate::state::{AVAILABLE_TICKETS, MESSAGE_ID_TO_MULTISIG_SESSION_ID, MESSAGE_ID_TO_TICKET}; use crate::{ types::*, state::{MULTISIG_SESSION_ID_TO_TX_HASH, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, @@ -28,7 +30,7 @@ pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, publi let signer_xrpl_address = XRPLAccountId::from(public_key); let tx_hash = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; - // m.tx_hash is going to be over 32 bytes due to inclusion of the signer address, so it has to be passed unchecked + // m.tx_hash is going to be over 32 bytes due to inclusion of the signer address, so it has to be passed unchecked signature.verify(&multisig::types::MsgToSign::unchecked(tx_hash), &public_key) .map_err(|_e| ContractError::SignatureVerificationFailed.into()) } @@ -61,3 +63,17 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & pub fn get_worker_set(storage: &dyn Storage) -> StdResult { Ok(CURRENT_WORKER_SET.load(storage)?.into()) } + +pub fn get_multisig_session_id(storage: &dyn Storage, message_id: &CrossChainId) -> StdResult> { + let existing_ticket_number = MESSAGE_ID_TO_TICKET.may_load(storage, message_id)?; + let available_tickets = AVAILABLE_TICKETS.may_load(storage)?; + if existing_ticket_number.is_none() || available_tickets.is_none() { + return Ok(None); + } + + if available_tickets.unwrap().contains(&existing_ticket_number.unwrap()) { + return Ok(MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, message_id)?); + } + + return Ok(None) +} diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 47148970b..7c3a2a6e2 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -2,8 +2,7 @@ use cosmwasm_std::{from_json, Attribute, DepsMut, HexBinary, Reply, Response, Ui use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; use crate::{ - error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_ID_TO_TX_HASH, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, - xrpl_serialize::XRPLSerialize + error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_ID_TO_TX_HASH, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, types::XRPLUnsignedTx, xrpl_serialize::XRPLSerialize }; pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { @@ -22,21 +21,21 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { MESSAGE_ID_TO_MULTISIG_SESSION_ID.save( deps.storage, &message_id, &multisig_session_id.u64(), - )? + )?; + REPLY_MESSAGE_ID.remove(deps.storage); }, + None if matches!(tx_info.unsigned_contents, XRPLUnsignedTx::Payment(_)) => panic!("No reply message ID found for Payment"), None => (), } - REPLY_MESSAGE_ID.remove(deps.storage); - - let tx_info = TRANSACTION_INFO.load(deps.storage, &tx_hash)?; - let res = reply.result.unwrap(); let evt_attributes: Vec = res.events diff --git a/contracts/xrpl-multisig-prover/src/state.rs b/contracts/xrpl-multisig-prover/src/state.rs index 409edd3f9..f2b4d105c 100644 --- a/contracts/xrpl-multisig-prover/src/state.rs +++ b/contracts/xrpl-multisig-prover/src/state.rs @@ -19,6 +19,7 @@ pub struct Config { pub ticket_count_threshold: u32, pub key_type: multisig::key::KeyType, pub governance_address: Addr, + pub relayer_address: Addr, pub xrp_denom: String, } @@ -43,7 +44,7 @@ pub const AVAILABLE_TICKETS: Item> = Item::new("available_tickets"); pub const TRANSACTION_INFO: Map<&TxHash, TransactionInfo> = Map::new("transaction_info"); pub const LATEST_SEQUENTIAL_TX_HASH: Item = Item::new("latest_sequential_tx_hash"); -pub const TOKENS: Map<&String, XRPLToken> = Map::new("tokens"); +pub const TOKENS: Map<&String, (XRPLToken, u8)> = Map::new("tokens"); pub const CURRENT_WORKER_SET: Item = Item::new("current_worker_set"); pub const NEXT_WORKER_SET: Map<&TxHash, WorkerSet> = Map::new("next_worker_set"); diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 94c1e3f71..96b87dddc 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -1,7 +1,7 @@ use axelar_wasm_std::VerificationStatus; use connection_router::state::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{from_json, HexBinary, Binary, StdResult, Uint256, Uint128, Uint64}; +use cosmwasm_std::{from_json, HexBinary, Binary, StdResult, Uint256, Uint128}; use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; use k256::ecdsa; use k256::schnorr::signature::SignatureEncoding; @@ -138,7 +138,7 @@ impl Into for XRPLSequence { pub struct XRPLSignerEntry { pub account: XRPLAccountId, pub signer_weight: u16, -} +} impl From for XRPLSignerEntry { fn from(signer: AxelarSigner) -> Self { @@ -207,7 +207,6 @@ pub struct XRPLPaymentTx { pub sequence: XRPLSequence, pub amount: XRPLPaymentAmount, pub destination: XRPLAccountId, - pub multisig_session_id: Uint64 } #[cw_serde] @@ -217,7 +216,6 @@ pub struct XRPLSignerListSetTx { pub sequence: XRPLSequence, pub signer_quorum: u32, pub signer_entries: Vec, - pub multisig_session_id: Uint64 } #[cw_serde] @@ -226,7 +224,6 @@ pub struct XRPLTicketCreateTx { pub fee: u64, pub sequence: XRPLSequence, pub ticket_count: u32, - pub multisig_session_id: Uint64 } #[cw_serde] @@ -382,20 +379,14 @@ impl XRPLTokenAmount { } } -impl TryFrom for XRPLTokenAmount { - type Error = ContractError; - - fn try_from(amount: Uint128) -> Result { - let (mantissa, exponent) = canonicalize_mantissa(amount)?; - Ok(XRPLTokenAmount::new(mantissa, exponent)) - } +pub fn canonicalize_coin_amount(amount: Uint128, decimals: u8) -> Result{ + let (mantissa, exponent) = canonicalize_mantissa(amount, -1 * i64::from(decimals))?; + Ok(XRPLTokenAmount::new(mantissa, exponent)) } // always called when XRPLTokenAmount instantiated // see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 -pub fn canonicalize_mantissa(mut mantissa: Uint128) -> Result<(u64, i64), ContractError> { - let mut exponent = 0i64; - +pub fn canonicalize_mantissa(mut mantissa: Uint128, mut exponent: i64) -> Result<(u64, i64), ContractError> { let ten = Uint128::from(10u128); while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index b4345684d..58e894921 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,8 +1,6 @@ use axelar_wasm_std::nonempty; use connection_router::state::CrossChainId; -use cosmwasm_std::{wasm_execute, HexBinary, Storage, Uint64, WasmMsg}; -use hex::ToHex; -use k256::{ecdsa, schnorr::signature::SignatureEncoding}; +use cosmwasm_std::{wasm_execute, HexBinary, Storage, WasmMsg}; use sha2::{Sha512, Digest, Sha256}; use crate::{ @@ -43,8 +41,7 @@ pub fn issue_payment( config: &Config, destination: nonempty::String, amount: &XRPLPaymentAmount, - message_id: &CrossChainId, - multisig_session_id: &Uint64, + message_id: &CrossChainId ) -> Result { let ticket_number = assign_ticket_number(storage, message_id)?; @@ -52,7 +49,6 @@ pub fn issue_payment( account: config.xrpl_multisig_address.as_str().try_into()?, fee: config.xrpl_fee, sequence: XRPLSequence::Ticket(ticket_number), - multisig_session_id: multisig_session_id.clone(), amount: amount.clone(), destination: XRPLAccountId::try_from(destination.as_str())? }; @@ -67,8 +63,7 @@ pub fn issue_payment( pub fn issue_ticket_create( storage: &mut dyn Storage, config: &Config, - ticket_count: u32, - multisig_session_id: Uint64 + ticket_count: u32 ) -> Result { let sequence_number = get_next_sequence_number(storage)?; @@ -77,7 +72,6 @@ pub fn issue_ticket_create( fee: config.xrpl_fee, sequence: XRPLSequence::Plain(sequence_number.clone()), ticket_count, - multisig_session_id, }; issue_tx( @@ -90,8 +84,7 @@ pub fn issue_ticket_create( pub fn issue_signer_list_set( storage: &mut dyn Storage, config: &Config, - workers: WorkerSet, - multisig_session_id: Uint64 + workers: WorkerSet ) -> Result { let sequence_number = get_next_sequence_number(storage)?; @@ -101,7 +94,6 @@ pub fn issue_signer_list_set( sequence: XRPLSequence::Plain(sequence_number.clone()), signer_quorum: workers.quorum, signer_entries: workers.signers.into_iter().map(|worker| XRPLSignerEntry::from(worker)).collect(), - multisig_session_id, }; issue_tx( @@ -181,7 +173,7 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId if let Some(ticket_number) = MESSAGE_ID_TO_TICKET.may_load(storage, &message_id)? { let confirmed_tx_hash = CONFIRMED_TRANSACTIONS.may_load(storage, &ticket_number)?; // as long as it has not already been consumed - if confirmed_tx_hash.is_none() + if confirmed_tx_hash.is_none() // or if it has been consumed by the same message. || TRANSACTION_INFO.load(storage, &confirmed_tx_hash.unwrap())?.original_message_id.as_ref() == Some(message_id) { return Ok(ticket_number); @@ -197,6 +189,7 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId pub fn get_next_ticket_number(storage: &dyn Storage) -> Result { let last_assigned_ticket_number: u32 = LAST_ASSIGNED_TICKET_NUMBER.load(storage)?; + // TODO: handle no available tickets let available_tickets = AVAILABLE_TICKETS.load(storage)?; if available_tickets.len() == 0 { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index ae86c3534..3142b37da 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -227,7 +227,6 @@ impl TryInto for XRPLPaymentTx { Account: self.account, SigningPubKey: HexBinary::from(vec![]), Destination: self.destination, - Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} }); obj.add_sequence(self.sequence)?; Ok(obj) @@ -246,7 +245,6 @@ impl TryInto for XRPLSignerListSetTx { Account: self.account, SigningPubKey: HexBinary::from(vec![]), SignerEntries: XRPLArray{ field: Field::SignerEntry, items: self.signer_entries.clone() }, - Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} }); obj.add_sequence(self.sequence)?; Ok(obj) @@ -264,7 +262,6 @@ impl TryInto for XRPLTicketCreateTx { Fee: XRPLPaymentAmount::Drops(self.fee), Account: self.account, SigningPubKey: HexBinary::from(vec![]), - Memos: XRPLArray{ field: Field::Memo, items: vec![XRPLMemo(hex_encode_session_id(self.multisig_session_id))]} }); obj.add_sequence(self.sequence)?; Ok(obj) @@ -413,7 +410,7 @@ pub fn field_id(type_code: u8, field_code: u8) -> Vec { #[cfg(test)] mod tests { - use cosmwasm_std::{Uint128, Uint64}; + use cosmwasm_std::{Uint128}; use multisig::key::PublicKey; use crate::types::XRPLToken; @@ -453,7 +450,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, currency: "USD".to_string().try_into()?, - }, Uint128::zero().try_into()?) + }, canonicalize_coin_amount(Uint128::zero(), 0)?) .xrpl_serialize()? ); assert_hex_eq!( @@ -461,7 +458,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, currency: "USD".to_string().try_into()?, - }, Uint128::from(1u128).try_into()?) + }, canonicalize_coin_amount(Uint128::one(), 0)?) .xrpl_serialize()? ); // minimum absolute amount @@ -491,7 +488,7 @@ mod tests { XRPLPaymentAmount::Token(XRPLToken { issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, currency: "${}".to_string().try_into()?, - }, Uint128::from(42u128).try_into()?) + }, canonicalize_coin_amount(Uint128::from(42u128), 0)?) .xrpl_serialize()? ); // minimum XRP @@ -587,11 +584,10 @@ mod tests { XRPLTokenAmount::new(3369568318000000u64, -16) ), destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, - multisig_session_id: Uint64::from(1u8), }; let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize()?; assert_eq!( - "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D0101E1F1", + "1200002200000000240000000161D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000168400000000000000C730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", hex::encode_upper(encoded_unsigned_tx) ); Ok(()) @@ -605,11 +601,10 @@ mod tests { sequence: XRPLSequence::Plain(1), amount: XRPLPaymentAmount::Drops(1000), destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, - multisig_session_id: Uint64::from(0u8), }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; assert_eq!( - "120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8F9EA7D00E1F1", + "120000220000000024000000016140000000000003E868400000000000000A730081145B812C9D57731E27A2DA8B1830195F88EF32A3B68314B5F762798A53D543A014CAF8B297CFF8F2F937E8", hex::encode_upper(encoded_unsigned_tx) ); @@ -619,11 +614,10 @@ mod tests { sequence: XRPLSequence::Plain(43497363), amount: XRPLPaymentAmount::Drops(1000000000), destination: "rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq".try_into()?, - multisig_session_id: Uint64::from(1337u16), }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; assert_eq!( - "1200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6F9EA7D020539E1F1", + "1200002200000000240297B79361400000003B9ACA0068400000000000000373008114245409103F1B06F22FBCED389AAE0EFCE2F6689A83146919924835FA51D3991CDF5CF4505781227686E6", hex::encode_upper(encoded_unsigned_tx) ); Ok(()) @@ -642,7 +636,6 @@ mod tests { sequence: XRPLSequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, - multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, @@ -658,7 +651,7 @@ mod tests { }; let encoded_signed_tx = &signed_tx.xrpl_serialize()?; assert_eq!( - "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1", hex::encode_upper(encoded_signed_tx) ); Ok(()) @@ -673,7 +666,6 @@ mod tests { sequence: XRPLSequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, - multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, @@ -689,7 +681,7 @@ mod tests { }; let encoded_signed_tx = &signed_tx.xrpl_serialize()?; assert_eq!( - "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + "12000022000000002400000000202902A2B751614000000005F5E10068400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4831449599D50E0C1AC0CFC8D3B2A30830F3738EACC3EF3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674463044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E0458114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE98114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1", hex::encode_upper(encoded_signed_tx) ); Ok(()) @@ -702,9 +694,11 @@ mod tests { account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()?, fee: 30, sequence: XRPLSequence::Ticket(45205896), - amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string().try_into()?, issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()? }, Uint128::from(100000000u128).try_into()?), + amount: XRPLPaymentAmount::Token(XRPLToken{ + currency: "ETH".to_string().try_into()?, + issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()? + }, canonicalize_coin_amount(Uint128::from(100000000u128), 0)?), destination: "raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".try_into()?, - multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".try_into()?, @@ -720,7 +714,7 @@ mod tests { }; let encoded_signed_tx = &signed_tx.xrpl_serialize()?; assert_eq!( - "12000022000000002400000000202902B1C98861D6838D7EA4C680000000000000000000000000004554480000000000EC792533BC26024CFAA5DDC2D04128E59581309C68400000000000001E73008114EC792533BC26024CFAA5DDC2D04128E59581309C831439659AAAD4DC8603798352FCF954419A67977536F3E0107321EDDD9822C7FA239DDA9913EBEE813ECBE69E35D88FF651548D5CC42C033A8A667B744076CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E010681142B3CF7B1986F5CB4EFEF11F933F40EC3106412C2E1E0107321ED45E67EAF446E6C26EB3A2B55B64339ECF3A4D1D03180BEE20EB5AFDD23FA644F7440531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09811472C14C0DB6CEF64A87CC3D152D7B0E917D372BE7E1F1F9EA7D03535510E1F1", + "12000022000000002400000000202902B1C98861D6838D7EA4C680000000000000000000000000004554480000000000EC792533BC26024CFAA5DDC2D04128E59581309C68400000000000001E73008114EC792533BC26024CFAA5DDC2D04128E59581309C831439659AAAD4DC8603798352FCF954419A67977536F3E0107321EDDD9822C7FA239DDA9913EBEE813ECBE69E35D88FF651548D5CC42C033A8A667B744076CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E010681142B3CF7B1986F5CB4EFEF11F933F40EC3106412C2E1E0107321ED45E67EAF446E6C26EB3A2B55B64339ECF3A4D1D03180BEE20EB5AFDD23FA644F7440531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09811472C14C0DB6CEF64A87CC3D152D7B0E917D372BE7E1F1", hex::encode_upper(encoded_signed_tx) ); Ok(()) @@ -734,7 +728,6 @@ mod tests { fee: 30, sequence: XRPLSequence::Plain(44218194), ticket_count: 3, - multisig_session_id: Uint64::from(5461264u64), }), signers: vec![ XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, @@ -750,7 +743,7 @@ mod tests { }; let encoded_signed_tx = signed_tx.xrpl_serialize()?; assert_eq!( - "12000A22000000002402A2B75220280000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC0088567446304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F8114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C588114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + "12000A22000000002402A2B75220280000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC0088567446304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F8114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74473045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C588114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1", hex::encode_upper(encoded_signed_tx) ); Ok(()) @@ -774,7 +767,6 @@ mod tests { signer_weight: 1 } ], - multisig_session_id: Uint64::from(5461264u64) }), signers: vec![ XRPLSigner{ account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, @@ -790,7 +782,7 @@ mod tests { }; let encoded_signed_tx = signed_tx.xrpl_serialize()?; assert_eq!( - "12000C22000000002402A2B84D20230000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674473045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B398114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74463044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A8114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F4EB1300028114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1EB1300018114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F9EA7D03535510E1F1", + "12000C22000000002402A2B84D20230000000368400000000000001E73008114447BB6E37CA4D5D89FC2E2470A64632DA9BDD9E4F3E0107321025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC00885674473045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B398114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1E0107321036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE74463044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A8114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1F4EB1300028114552A0D8EFCF978186CA9C37112B502D3728DA9EFE1EB1300018114BA058AB3573EA34DC934D60E719A12DE6C213DE2E1F1", hex::encode_upper(encoded_signed_tx) ); Ok(()) diff --git a/integration-tests/tests/message_routing.rs b/integration-tests/tests/message_routing.rs index 13e75abcd..c15b00dae 100644 --- a/integration-tests/tests/message_routing.rs +++ b/integration-tests/tests/message_routing.rs @@ -1,6 +1,7 @@ use axelar_wasm_std::VerificationStatus; use connection_router::state::{Address, CrossChainId, Message}; use cosmwasm_std::{HexBinary, Uint128, Coin}; +use multisig::key::KeyType; use crate::test_utils::ETH_DENOMINATION; @@ -148,7 +149,7 @@ fn xrpl_ticket_create_can_be_proven() { destination_address: Address::try_from(xrpl_multisig_address).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "8c23c331f52ed2856308fea85be7583f8ff726ad87b0dbbcbe5e371563b7c02d:0" + id: "9c2f220fe5ee650b3cd10b0a72af1206b3912afce8376214234354180198c5d5:0" .to_string() .try_into() .unwrap(), @@ -174,7 +175,7 @@ fn xrpl_ticket_create_can_be_proven() { test_utils::xrpl_update_tx_status( &mut protocol.app, &xrpl.multisig_prover_address, - workers.iter().map(|w| w.addr.clone()).collect(), + workers.iter().map(|w| (KeyType::Ecdsa, HexBinary::from(w.key_pair.encoded_verifying_key())).try_into().unwrap()).collect(), session_id, proof_msgs[0].cc_id.clone(), VerificationStatus::SucceededOnChain @@ -269,7 +270,7 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { destination_address: Address::try_from("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".to_string()).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "21b61845c5a4e3b00e60f21c0fde6c0208eff28c9e57709306f47672bf8f984d:0" + id: "c5c80adaff8703e589988f68587535d5c5cac5a7d7b99f0507aee3de40201137:0" .to_string() .try_into() .unwrap(), @@ -295,7 +296,7 @@ fn payment_towards_xrpl_can_be_verified_and_routed_and_proven() { test_utils::xrpl_update_tx_status( &mut protocol.app, &xrpl.multisig_prover_address, - workers.iter().map(|w| w.addr.clone()).collect(), + workers.iter().map(|w| (KeyType::Ecdsa, HexBinary::from(w.key_pair.encoded_verifying_key())).try_into().unwrap()).collect(), session_id, proof_msgs[0].cc_id.clone(), VerificationStatus::SucceededOnChain diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index f5333669e..c69b831d1 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -250,8 +250,10 @@ pub fn construct_xrpl_payment_proof_and_sign( multisig_prover_address.clone(), &xrpl_multisig_prover::msg::ExecuteMsg::ConstructProof { message_id: message.cc_id.clone(), + coin: coins.to_vec().get(0).unwrap().clone(), // TODO: remove }, - coins, + &[] + // coins, ); assert!(response.is_ok()); let response = response.unwrap(); @@ -397,7 +399,7 @@ pub fn get_xrpl_proof( pub fn xrpl_update_tx_status( app: &mut App, multisig_prover_address: &Addr, - signers: Vec, + signer_public_keys: Vec, multisig_session_id: Uint64, message_id: CrossChainId, message_status: VerificationStatus, @@ -409,7 +411,7 @@ pub fn xrpl_update_tx_status( message_status, multisig_session_id, message_id, - signers, + signer_public_keys, }, &[], ); @@ -983,6 +985,7 @@ pub fn register_token( multisig_prover_address: Addr, denom: String, token: XRPLToken, + decimals: u8, ) { let response = protocol.app.execute_contract( protocol.governance_address.clone(), @@ -990,6 +993,7 @@ pub fn register_token( &xrpl_multisig_prover::msg::ExecuteMsg::RegisterToken { denom, token, + decimals, }, &[], ); @@ -1043,6 +1047,7 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { (44218195..44218200).collect::>() ].concat(), governance_address: protocol.governance_address.to_string(), + relayer_address: Addr::unchecked("relayer").to_string(), xrp_denom: "uxrp".to_string(), }, ); @@ -1055,6 +1060,7 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> Chain { issuer: xrpl_multisig_address.as_str().try_into().unwrap(), currency: "ETH".to_string().try_into().unwrap(), }, + 18u8, ); let response = protocol.app.execute_contract( diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index ec734147a..5d132ea55 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -2,6 +2,7 @@ use connection_router::{state::{Address, CrossChainId}, Message}; use cosmwasm_std::{Addr, HexBinary}; use axelar_wasm_std::VerificationStatus; use cw_multi_test::Executor; +use multisig::key::KeyType; use test_utils::Worker; mod test_utils; @@ -198,7 +199,7 @@ fn xrpl_worker_set_can_be_initialized_and_then_manually_updated() { destination_address: Address::try_from(xrpl_multisig_address).unwrap(), cc_id: CrossChainId { chain: xrpl.chain_name.clone(), - id: "f6471a07847f853d13601496cb643c6f111856ab336b204deefde520657900fb:0" + id: "fbf428da41656ca3aef36287bfcb6d8491daa76f20c201c4a60172450ab517f9:0" .to_string() .try_into() .unwrap(), @@ -225,7 +226,7 @@ fn xrpl_worker_set_can_be_initialized_and_then_manually_updated() { test_utils::xrpl_update_tx_status( &mut protocol.app, &xrpl.multisig_prover_address, - initial_workers.iter().map(|w| w.addr.clone()).collect(), + initial_workers.iter().map(|w| (KeyType::Ecdsa, HexBinary::from(w.key_pair.encoded_verifying_key())).try_into().unwrap()).collect(), session_id, proof_msgs[0].cc_id.clone(), VerificationStatus::SucceededOnChain From 280df178469e011c2595cb4f3ded979700676b24 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Thu, 18 Apr 2024 19:06:49 +0300 Subject: [PATCH 70/80] ampd xrpl: update config & verify ABI-encoded payload hash --- ampd/src/handlers/config.rs | 3 +- ampd/src/handlers/xrpl_multisig.rs | 18 ++++-- ampd/src/handlers/xrpl_verify_msg.rs | 15 ++--- ampd/src/lib.rs | 5 +- ampd/src/xrpl/json_rpc.rs | 18 +++--- ampd/src/xrpl/types.rs | 2 +- ampd/src/xrpl/verifier.rs | 91 +++++++++++++++++++++------- 7 files changed, 102 insertions(+), 50 deletions(-) diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index 046e70b63..aae46e935 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -35,7 +35,8 @@ pub enum Config { cosmwasm_contract: TMAddress, }, XRPLMultisigSigner { - cosmwasm_contract: TMAddress, + multisig_prover_contract: TMAddress, + multisig_contract: TMAddress, }, SuiMsgVerifier { cosmwasm_contract: TMAddress, diff --git a/ampd/src/handlers/xrpl_multisig.rs b/ampd/src/handlers/xrpl_multisig.rs index 812e00441..620121b48 100644 --- a/ampd/src/handlers/xrpl_multisig.rs +++ b/ampd/src/handlers/xrpl_multisig.rs @@ -26,10 +26,10 @@ use crate::tofnd::grpc::SharableEcdsaClient; use crate::tofnd::MessageDigest; #[derive(Debug, Deserialize)] -#[try_from("wasm-signing_started")] +#[try_from("wasm-xrpl_signing_started")] struct XRPLSigningStartedEvent { #[serde(rename = "_contract_address")] - multisig_address: TMAddress, + multisig_prover_address: TMAddress, session_id: u64, #[serde(deserialize_with = "deserialize_public_keys")] pub_keys: HashMap, @@ -72,6 +72,7 @@ where B: BroadcasterClient, { worker: TMAddress, + multisig_prover: TMAddress, multisig: TMAddress, broadcaster: B, signer: SharableEcdsaClient, @@ -85,6 +86,7 @@ where pub fn new( worker: TMAddress, multisig: TMAddress, + multisig_prover: TMAddress, broadcaster: B, signer: SharableEcdsaClient, latest_block_height: Receiver, @@ -92,6 +94,7 @@ where Self { worker, multisig, + multisig_prover, broadcaster, signer, latest_block_height, @@ -132,7 +135,7 @@ where async fn handle(&self, event: &events::Event) -> error_stack::Result<(), Error> { let XRPLSigningStartedEvent { - multisig_address, + multisig_prover_address, session_id, pub_keys, unsigned_tx, @@ -144,7 +147,7 @@ where result => result.change_context(DeserializeEvent)?, }; - if self.multisig != multisig_address { + if self.multisig_prover != multisig_prover_address { return Ok(()); } @@ -286,6 +289,7 @@ mod test { fn get_handler( worker: TMAddress, multisig: TMAddress, + multisig_prover: TMAddress, signer: SharableEcdsaClient, latest_block_height: u64, ) -> Handler { @@ -299,7 +303,7 @@ mod test { let (_tx, rx) = watch::channel(latest_block_height); - Handler::new(worker, multisig, broadcaster.client(), signer, rx) + Handler::new(worker, multisig, multisig_prover, broadcaster.client(), signer, rx) } #[test] @@ -365,6 +369,7 @@ mod test { .returning(move |_, _, _| Err(Report::from(tofnd::error::Error::SignFailed))); let handler = get_handler( + rand_account(), rand_account(), rand_account(), SharableEcdsaClient::new(client), @@ -384,6 +389,7 @@ mod test { let handler = get_handler( rand_account(), TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), + rand_account(), SharableEcdsaClient::new(client), 100u64, ); @@ -404,6 +410,7 @@ mod test { let handler = get_handler( worker, TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), + rand_account(), SharableEcdsaClient::new(client), 99u64, ); @@ -427,6 +434,7 @@ mod test { let handler = get_handler( worker, TMAddress::from(MULTISIG_ADDRESS.parse::().unwrap()), + rand_account(), SharableEcdsaClient::new(client), 101u64, ); diff --git a/ampd/src/handlers/xrpl_verify_msg.rs b/ampd/src/handlers/xrpl_verify_msg.rs index 7d23fb70d..d3bd56624 100644 --- a/ampd/src/handlers/xrpl_verify_msg.rs +++ b/ampd/src/handlers/xrpl_verify_msg.rs @@ -110,7 +110,6 @@ where messages, participants, expires_at, - .. } = match event.try_into() as error_stack::Result<_, _> { Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { return Ok(()); @@ -145,15 +144,13 @@ where } } - let votes = messages + let votes: Vec<_> = messages .iter() - .map(|msg| { - tx_responses - .get(&msg.tx_id) - .map_or(Vote::NotFound, |tx_response| { - verify_message(&source_gateway_address, &tx_response.tx, msg) - }) - }) + .map(|msg| tx_responses + .get(&msg.tx_id) + .and_then(|tx_response| tx_response.as_ref().map(|tx| verify_message(&source_gateway_address, &tx.tx, msg))) + .unwrap_or(Vote::NotFound) + ) .collect(); self.broadcast_votes(poll_id, votes).await diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index 0eda82a7f..5a68c9cba 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -254,12 +254,13 @@ where ), stream_timeout, ), - handlers::config::Config::XRPLMultisigSigner { cosmwasm_contract } => self + handlers::config::Config::XRPLMultisigSigner { multisig_contract, multisig_prover_contract } => self .create_handler_task( "xrpl-multisig-signer", handlers::xrpl_multisig::Handler::new( worker.clone(), - cosmwasm_contract, + multisig_contract, + multisig_prover_contract, self.broadcaster.client(), self.ecdsa_client.clone(), self.block_height_monitor.latest_block_height(), diff --git a/ampd/src/xrpl/json_rpc.rs b/ampd/src/xrpl/json_rpc.rs index d6850f7cf..ee524c703 100644 --- a/ampd/src/xrpl/json_rpc.rs +++ b/ampd/src/xrpl/json_rpc.rs @@ -11,18 +11,16 @@ pub trait XRPLClient { async fn fetch_tx( &self, tx_id: &TransactionId, - ) -> Result; + ) -> Result>; } #[async_trait] -impl XRPLClient for Client -{ - async fn fetch_tx( - &self, - tx_id: &TransactionId, - ) -> Result { +impl XRPLClient for Client { + async fn fetch_tx(&self, tx_id: &TransactionId) -> Result> { let req = TxRequest::new(tx_id.as_str()); - - Ok(self.call(req).await?) + self.call(req).await.map(Some).or_else(|err| match err { + error::Error::Api(reason) if reason == "txnNotFound" => Ok(None), + _ => Err(err.into()), + }) } -} \ No newline at end of file +} diff --git a/ampd/src/xrpl/types.rs b/ampd/src/xrpl/types.rs index 2a7a71773..0e9399a4c 100644 --- a/ampd/src/xrpl/types.rs +++ b/ampd/src/xrpl/types.rs @@ -1,7 +1,7 @@ use serde::Deserialize; #[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone)] -pub struct TransactionId(String); +pub struct TransactionId(pub String); impl TransactionId { pub fn as_str(&self) -> &str { diff --git a/ampd/src/xrpl/verifier.rs b/ampd/src/xrpl/verifier.rs index e719f15cc..459164528 100644 --- a/ampd/src/xrpl/verifier.rs +++ b/ampd/src/xrpl/verifier.rs @@ -7,7 +7,7 @@ use connection_router::state::ChainName; use k256::sha2::Sha256; use serde_json::to_string; use sha3::Digest; -use xrpl_http_client::ResultCategory; +use xrpl_http_client::{Amount, ResultCategory}; use xrpl_http_client::{Memo, Transaction::Payment, Transaction}; use axelar_wasm_std::voting::Vote; use cosmwasm_std::{Uint256, HexBinary}; @@ -20,7 +20,7 @@ pub fn verify_message( tx: &Transaction, message: &Message, ) -> Vote { - if is_validated_tx(tx) && (is_valid_multisig_tx(tx, multisig_address, message) || is_valid_deposit_tx(tx, multisig_address, message)) { + if message.event_index == 0 && is_validated_tx(tx) && (is_valid_multisig_tx(tx, multisig_address, message) || is_valid_deposit_tx(tx, multisig_address, message)) { if is_successful_tx(tx) { Vote::SucceededOnChain } else { @@ -42,7 +42,7 @@ pub fn is_valid_multisig_tx(tx: &Transaction, multisig_address: &XRPLAddress, me pub fn is_valid_deposit_tx(tx: &Transaction, multisig_address: &XRPLAddress, message: &Message) -> bool { if let Payment(payment_tx) = &tx { if let Some(memos) = payment_tx.clone().common.memos { - return payment_tx.destination == multisig_address.0 && message.source_address.0 == tx.common().account && verify_memos(memos, message); + return payment_tx.destination == multisig_address.0 && message.source_address.0 == tx.common().account && verify_memos(payment_tx.amount.clone(), memos, message); } } return false; @@ -64,30 +64,77 @@ struct MemoPayload { payload_hash: HexBinary } -pub fn verify_memos(memos: Vec, message: &Message) -> bool { +fn remove_0x_prefix(s: String) -> String { + if s.starts_with("0x") { + s[2..].to_string() + } else { + s + } +} + +pub fn verify_memos(amount: Amount, memos: Vec, message: &Message) -> bool { let memo_kv: HashMap = memos .into_iter() .filter(|m| m.memo_type.is_some() && m.memo_data.is_some()) - .map(|m| (m.memo_type.unwrap(), m.memo_data.unwrap())) + .map(|m| (String::from_utf8(hex::decode(m.memo_type.unwrap()).unwrap()).unwrap(), m.memo_data.unwrap())) .collect(); || -> Option { - let memo_payload_hash = memo_kv - .get("payload_hash") - .cloned() - .or_else(|| { - memo_kv.get("payload").map(|p| hex::encode(Sha256::digest(p).to_vec())) - })?; - let memo_payload = MemoPayload { - fee: Uint256::from_str(memo_kv.get("fee")?).ok()?, - relayer: memo_kv.get("relayer")?.clone(), - amount: Uint256::from_str(memo_kv.get("amount")?).ok()?, - currency: memo_kv.get("currency")?.clone(), - payload_hash: HexBinary::from_hex(memo_payload_hash.as_str()).ok()? + let (token , amount) = match amount { + Amount::Issued(a) => (a.currency, a.value), + Amount::Drops(a) => ("XRP".to_string(), a), }; - let memo_hash: [u8; 32] = Sha256::digest(to_string(&memo_payload).ok()?).into(); - Some(memo_kv.get("destination_address") == Some(&message.destination_address) - && memo_kv.get("destination_chain") == Some(&message.destination_chain.to_string()) - && *message.payload_hash.as_fixed_bytes() == memo_hash) + + let expected_payload = ethers::abi::encode(&vec![ + ethers::abi::Token::String(token), + ethers::abi::Token::String(amount), + ethers::abi::Token::FixedBytes(hex::decode(remove_0x_prefix(memo_kv.get("payload_hash")?.clone())).ok()?), + ]); + let expected_payload_hash = ethers::utils::keccak256(expected_payload.clone()); + + Some(memo_kv.get("destination_address") == Some(&remove_0x_prefix(message.destination_address.clone()).to_uppercase()) + && memo_kv.get("destination_chain") == Some(&hex::encode_upper(message.destination_chain.to_string())) + && *message.payload_hash.to_fixed_bytes().to_vec() == expected_payload_hash) }().unwrap_or(false) -} \ No newline at end of file +} + +#[cfg(test)] +mod test { + use std::str::FromStr; + + use crate::xrpl::{types::XRPLAddress, verifier::verify_memos}; + use ethers::types::TxHash; + use xrpl_http_client::{Amount, Memo}; + use crate::handlers::xrpl_verify_msg::Message; + use connection_router::state::ChainName; + + #[test] + fn test_verify_memos() { + let memos = vec![ + Memo { + memo_type: Some("64657374696E6174696F6E5F61646472657373".to_string()), + memo_data: Some("592639C10223C4EC6C0FFC670E94D289A25DD1AD".to_string()), + memo_format: None + }, + Memo { + memo_type: Some("64657374696E6174696F6E5F636861696E".to_string()), + memo_data: Some("657468657265756D".to_string()), + memo_format: None + }, + Memo { + memo_type: Some("7061796C6F61645F68617368".to_string()), + memo_data: Some("4F246000525114CC0CC261973D12E9A1C53B7AA295DF41FA6A6BFD00045BF0E6".to_string()), + memo_format: None + } + ]; + let message = Message { + tx_id: crate::xrpl::types::TransactionId("1c6019555252bcb7bca95237d333a6c473112d6396d4f151a4a1c1f00f04f8f3".to_string()), + event_index: 14, + source_address: XRPLAddress("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".to_string()), + destination_address: "0x592639c10223C4EC6C0ffc670e94d289A25DD1ad".to_string(), + destination_chain: ChainName::from_str("ethereum").unwrap(), + payload_hash: TxHash(hex::decode("25b99c1524e2467c7d30cdaae191d6ce6fa6e7fa73e8cb561d2dc93178f1e083").unwrap().to_vec().try_into().unwrap()) + }; + assert!(verify_memos(Amount::Drops("1000000".to_string()), memos, &message)); + } +} From b4da97022def2c178b85853a6d915a37790cb13b Mon Sep 17 00:00:00 2001 From: k4m4 Date: Thu, 18 Apr 2024 19:12:30 +0300 Subject: [PATCH 71/80] Revert "xrpl-multisig-prover: create get_next_multisig_session_id query function" This reverts commit 42bf6b3f7a21c445f34cd948f0829308708f4801. --- contracts/multisig-prover/src/test/mocks/multisig.rs | 1 - contracts/multisig/src/contract.rs | 1 - contracts/multisig/src/contract/query.rs | 6 ------ contracts/multisig/src/msg.rs | 3 --- 4 files changed, 11 deletions(-) diff --git a/contracts/multisig-prover/src/test/mocks/multisig.rs b/contracts/multisig-prover/src/test/mocks/multisig.rs index 4ed74abea..ff1a89f35 100644 --- a/contracts/multisig-prover/src/test/mocks/multisig.rs +++ b/contracts/multisig-prover/src/test/mocks/multisig.rs @@ -89,7 +89,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { worker_address, key_type, )), - QueryMsg::GetNextSessionId => unimplemented!(), } } diff --git a/contracts/multisig/src/contract.rs b/contracts/multisig/src/contract.rs index 0a282d579..c343de0d7 100644 --- a/contracts/multisig/src/contract.rs +++ b/contracts/multisig/src/contract.rs @@ -103,7 +103,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { deps.api.addr_validate(&worker_address)?, key_type, )?), - QueryMsg::GetNextSessionId => to_binary(&query::get_next_session_id(deps)?), } } diff --git a/contracts/multisig/src/contract/query.rs b/contracts/multisig/src/contract/query.rs index 34fa38429..33c66d7cf 100644 --- a/contracts/multisig/src/contract/query.rs +++ b/contracts/multisig/src/contract/query.rs @@ -1,5 +1,3 @@ -use std::ops::Add; - use crate::{ key::{KeyType, PublicKey}, state::{load_pub_key, load_session_signatures}, @@ -36,7 +34,3 @@ pub fn get_public_key(deps: Deps, worker: Addr, key_type: KeyType) -> StdResult< let raw = load_pub_key(deps.storage, worker, key_type)?; Ok(PublicKey::try_from((key_type, raw)).expect("could not decode pub key")) } - -pub fn get_next_session_id(deps: Deps) -> StdResult { - Ok(SIGNING_SESSION_COUNTER.load(deps.storage)?.add(Uint64::one())) -} diff --git a/contracts/multisig/src/msg.rs b/contracts/multisig/src/msg.rs index c290a8518..ba4062465 100644 --- a/contracts/multisig/src/msg.rs +++ b/contracts/multisig/src/msg.rs @@ -67,9 +67,6 @@ pub enum QueryMsg { worker_address: String, key_type: KeyType, }, - - #[returns(Uint64)] - GetNextSessionId, } #[cw_serde] From c1c58e647f16639e32e085668ef2bd442308bb66 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Sat, 20 Apr 2024 22:14:23 +0300 Subject: [PATCH 72/80] Implement sig_verifier VerifyMessage query --- contracts/multisig/src/signing.rs | 3 +-- .../xrpl-multisig-prover/src/contract.rs | 23 ++++++++++++------- contracts/xrpl-multisig-prover/src/msg.rs | 8 ++++++- contracts/xrpl-multisig-prover/src/query.rs | 2 +- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/contracts/multisig/src/signing.rs b/contracts/multisig/src/signing.rs index 767cc3aad..888d67c7d 100644 --- a/contracts/multisig/src/signing.rs +++ b/contracts/multisig/src/signing.rs @@ -73,7 +73,7 @@ pub fn validate_session_signature( }); } - /*sig_verifier + sig_verifier .map_or_else( || signature.verify(&session.msg, pub_key), |sig_verifier| { @@ -91,7 +91,6 @@ pub fn validate_session_signature( session_id: session.id, signer: signer.into(), })?; - */ Ok(()) } diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 2e3deb57b..49f7f3f11 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -232,7 +232,7 @@ pub fn start_signing_session( storage: &mut dyn Storage, config: &Config, tx_hash: TxHash, - _self_address: Addr, + self_address: Addr, ) -> Result { REPLY_TX_HASH.save(storage, &tx_hash)?; let cur_worker_set_id = match CURRENT_WORKER_SET.may_load(storage)? { @@ -246,9 +246,7 @@ pub fn start_signing_session( worker_set_id: cur_worker_set_id, chain_name: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), msg: tx_hash.into(), - // TODO: implement sig_verifier - //sig_verifier: Some(self_address.into()) - sig_verifier: None, + sig_verifier: Some(self_address.into()) }; let wasm_msg = wasm_execute(&config.axelar_multisig, &start_sig_msg, vec![])?; @@ -397,11 +395,20 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetProof { multisig_session_id, } => to_json_binary(&query::get_proof(deps.storage, querier, &multisig_session_id)?), - QueryMsg::VerifyMessage { - multisig_session_id, + QueryMsg::VerifySignature { + session_id, + message: _, public_key, - signature - } => to_json_binary(&query::verify_message(deps.storage, &multisig_session_id, &public_key, &signature)?), + signature, + signer_address: _, + } => to_json_binary(&query::verify_signature( + deps.storage, + &session_id, + &PublicKey::Ecdsa(public_key), + &multisig::key::Signature::try_from( + (multisig::key::KeyType::Ecdsa, signature) + ).map_err(|_| ContractError::InvalidSignature)?)? + ), QueryMsg::GetWorkerSet {} => to_json_binary(&query::get_worker_set(deps.storage)?), QueryMsg::GetMultisigSessionId { message_id } => to_json_binary(&query::get_multisig_session_id(deps.storage, &message_id)?), // TODO: rename } diff --git a/contracts/xrpl-multisig-prover/src/msg.rs b/contracts/xrpl-multisig-prover/src/msg.rs index 3737d50a1..d8f6f9c21 100644 --- a/contracts/xrpl-multisig-prover/src/msg.rs +++ b/contracts/xrpl-multisig-prover/src/msg.rs @@ -35,7 +35,13 @@ pub enum QueryMsg { GetProof { multisig_session_id: Uint64 }, #[returns(bool)] - VerifyMessage { multisig_session_id: Uint64, public_key: PublicKey, signature: Signature }, + VerifySignature { + signature: HexBinary, + message: HexBinary, + public_key: HexBinary, + signer_address: String, + session_id: Uint64, + }, #[returns(multisig::worker_set::WorkerSet)] GetWorkerSet, diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index 249f8f10c..d73dd5193 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -26,7 +26,7 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, Ok(xrpl_multisig::xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, serialized_tx).into()) } -pub fn verify_message(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: &PublicKey, signature: &Signature) -> StdResult { +pub fn verify_signature(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: &PublicKey, signature: &Signature) -> StdResult { let signer_xrpl_address = XRPLAccountId::from(public_key); let tx_hash = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; From dba8b702422fafab35ea4be39ca914df2d836ae6 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Mon, 22 Apr 2024 16:40:34 +0300 Subject: [PATCH 73/80] ampd: skip XRPL `PollStartedEvent`s & fix XRPL payload_hash verification --- ampd/src/handlers/multisig.rs | 12 ++++++++++++ ampd/src/xrpl/verifier.rs | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ampd/src/handlers/multisig.rs b/ampd/src/handlers/multisig.rs index 8a47e92f9..ce17d6b6b 100644 --- a/ampd/src/handlers/multisig.rs +++ b/ampd/src/handlers/multisig.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; use std::convert::TryInto; +use std::str::FromStr; use async_trait::async_trait; +use connection_router_api::ChainName; use cosmrs::cosmwasm::MsgExecuteContract; use cosmwasm_std::{HexBinary, Uint64}; use ecdsa::VerifyingKey; @@ -34,6 +36,7 @@ struct SigningStartedEvent { #[serde(with = "hex")] msg: MessageDigest, expires_at: u64, + chain: ChainName, } fn deserialize_public_keys<'de, D>( @@ -139,6 +142,7 @@ where pub_keys, msg, expires_at, + chain, } = match event.try_into() as error_stack::Result<_, _> { Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { return Ok(()); @@ -146,6 +150,14 @@ where result => result.change_context(DeserializeEvent)?, }; + if chain.eq(&ChainName::from_str("XRPL").unwrap()) { + info!( + session_id = session_id.to_string(), + "skipping XRP signing session" + ); + return Ok(()); + } + info!( session_id = session_id, msg = encode(&msg), diff --git a/ampd/src/xrpl/verifier.rs b/ampd/src/xrpl/verifier.rs index 108b05521..1c029b370 100644 --- a/ampd/src/xrpl/verifier.rs +++ b/ampd/src/xrpl/verifier.rs @@ -83,7 +83,7 @@ pub fn verify_memos(amount: Amount, memos: Vec, message: &Message) -> bool let expected_payload = ethers::abi::encode(&vec![ ethers::abi::Token::String(token), - ethers::abi::Token::String(amount), + ethers::abi::Token::Uint(ethers::types::U256::from_dec_str(amount.as_ref()).ok()?), ethers::abi::Token::FixedBytes(hex::decode(remove_0x_prefix(memo_kv.get("payload_hash")?.clone())).ok()?), ]); let expected_payload_hash = ethers::utils::keccak256(expected_payload.clone()); @@ -129,7 +129,7 @@ mod test { source_address: XRPLAddress("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".to_string()), destination_address: "0x592639c10223C4EC6C0ffc670e94d289A25DD1ad".to_string(), destination_chain: ChainName::from_str("ethereum").unwrap(), - payload_hash: TxHash(hex::decode("25b99c1524e2467c7d30cdaae191d6ce6fa6e7fa73e8cb561d2dc93178f1e083").unwrap().to_vec().try_into().unwrap()) + payload_hash: TxHash(hex::decode("feb30b51f41e4785664824dd0ee694e0d275757753f570e9f6b5e27d06197fa7").unwrap().to_vec().try_into().unwrap()) }; assert!(verify_memos(Amount::Drops("1000000".to_string()), memos, &message)); } From d521c6bcb982ea2faa6478b5369f83e475417dd6 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Mon, 6 May 2024 22:31:41 +0300 Subject: [PATCH 74/80] improve type conversions --- .../src/axelar_workers.rs | 53 ++++++++++--------- .../xrpl-multisig-prover/src/contract.rs | 4 +- contracts/xrpl-multisig-prover/src/error.rs | 3 ++ .../xrpl-multisig-prover/src/xrpl_multisig.rs | 7 +-- .../src/xrpl_serialize.rs | 2 +- 5 files changed, 38 insertions(+), 31 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index 211d13e23..e1c15559f 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -22,7 +22,7 @@ pub struct AxelarSigner { impl Into for AxelarSigner { fn into(self) -> Participant { - let weight = nonempty::Uint256::try_from(Uint256::from(self.weight as u128)).unwrap(); + let weight = nonempty::Uint256::try_from(Uint256::from(u128::try_from(self.weight).unwrap())).unwrap(); Participant { address: self.address, weight, @@ -46,7 +46,7 @@ impl Into for WorkerSet { .collect(); multisig::worker_set::WorkerSet::new( participants, - Uint256::from(self.quorum as u128), + Uint256::from(u128::try_from(self.quorum).unwrap()), self.created_at ) } @@ -67,29 +67,30 @@ impl WorkerSet { } } -fn convert_uint256_to_u16_unsafely(value: Uint256) -> u16 { +fn convert_uint256_to_u16(value: Uint256) -> Result { + if value > Uint256::from(u16::MAX) { + return Err(ContractError::GenericError("Overflow, cannot convert value to u16".to_owned())) + } let bytes = value.to_le_bytes(); - (bytes[0] as u16) | (bytes[1] as u16) << 8 + Ok(u16::from(bytes[0]) | u16::from(bytes[1]).checked_shl(8).unwrap()) // this unwrap is never supposed to fail } // Converts a Vec to Vec, scaling down with precision loss, if necessary. // We make sure that XRPL multisig and Axelar multisig both use the same scaled down numbers and have the same precision loss -fn convert_or_scale_weights(weights: Vec) -> Vec { +fn convert_or_scale_weights(weights: Vec) -> Result, ContractError> { let max_weight: Option<&Uint256> = weights.iter().max(); match max_weight { Some(max_weight) => { let max_u16_as_uint256 = Uint256::from(u16::MAX); - // Scaling down - weights - .iter() - .map(|weight| { - // multiply_ratio returns a rounded down value - let scaled = weight.multiply_ratio(max_u16_as_uint256, *max_weight); - convert_uint256_to_u16_unsafely(scaled) - }) - .collect() + let mut result = Vec::with_capacity(weights.len()); + for &weight in weights.iter() { + let scaled = weight.multiply_ratio(max_u16_as_uint256, *max_weight); + result.push(convert_uint256_to_u16(scaled)?); + } + + Ok(result) }, - None => vec![], + None => Ok(vec![]), } } @@ -109,7 +110,7 @@ pub fn get_active_worker_set( let weights = convert_or_scale_weights(participants .iter() .map(|participant| Uint256::from(participant.weight)) - .collect()); + .collect())?; let mut signers: Vec = vec![]; for (i, participant) in participants.iter().enumerate() { @@ -123,11 +124,12 @@ pub fn get_active_worker_set( let sum_of_weights: u32 = weights.iter().map(|w| u32::from(*w)).sum(); - let quorum = (sum_of_weights as u64) + let quorum = u32::try_from(u64::from(sum_of_weights) .checked_mul(signing_threshold.numerator().into()) .unwrap() .checked_div(signing_threshold.denominator().into()) - .unwrap() as u32; + .unwrap()) + .unwrap(); let worker_set = WorkerSet { signers: BTreeSet::from_iter(signers.into_iter()), @@ -165,15 +167,15 @@ mod tests { #[test] fn test_convert_or_scale_weights() { let weights = vec![Uint256::from(1u128), Uint256::from(1u128)]; - let scaled_weights = convert_or_scale_weights(weights); + let scaled_weights = convert_or_scale_weights(weights).unwrap(); assert_eq!(scaled_weights, vec![65535, 65535]); let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128)]; - let scaled_weights = convert_or_scale_weights(weights); + let scaled_weights = convert_or_scale_weights(weights).unwrap(); assert_eq!(scaled_weights, vec![21845, 43690, 65535]); let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128), Uint256::from(4u128)]; - let scaled_weights = convert_or_scale_weights(weights); + let scaled_weights = convert_or_scale_weights(weights).unwrap(); assert_eq!(scaled_weights, vec![16383, 32767, 49151, 65535]); let weights = vec![ @@ -182,7 +184,7 @@ mod tests { Uint256::MAX - Uint256::from(1u128), Uint256::MAX ]; - let scaled_weights = convert_or_scale_weights(weights); + let scaled_weights = convert_or_scale_weights(weights).unwrap(); assert_eq!(scaled_weights, vec![65534, 65534, 65534, 65535]); let weights = vec![ @@ -191,7 +193,7 @@ mod tests { Uint256::MAX - Uint256::from(1u128), Uint256::MAX ]; - let scaled_weights = convert_or_scale_weights(weights); + let scaled_weights = convert_or_scale_weights(weights).unwrap(); assert_eq!(scaled_weights, vec![0, 0, 65534, 65535]); let weights = vec![ @@ -201,9 +203,10 @@ mod tests { Uint256::from(400000000u128), Uint256::from(50000000000u128), ]; - let scaled_weights = convert_or_scale_weights(weights); + let scaled_weights = convert_or_scale_weights(weights).unwrap(); assert_eq!(scaled_weights, vec![0, 2, 39, 524, 65535]); - assert_eq!(convert_or_scale_weights(vec![] as Vec), vec![] as Vec); + let scaled_weights = convert_or_scale_weights(vec![]).unwrap(); + assert_eq!(scaled_weights, vec![] as Vec); } } diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index ff6fb9c86..fcadf2939 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -276,7 +276,7 @@ fn construct_signer_list_set_proof( if !axelar_workers::should_update_worker_set( &new_worker_set.clone().into(), &cur_worker_set.clone().into(), - config.worker_set_diff_threshold as usize, + usize::try_from(config.worker_set_diff_threshold).unwrap(), ) { return Err(ContractError::WorkerSetUnchanged.into()) } @@ -304,7 +304,7 @@ fn construct_ticket_create_proof( self_address: Addr, config: &Config, ) -> Result { - let ticket_count = xrpl_multisig::available_ticket_count(storage)?; + let ticket_count = xrpl_multisig::tickets_available_to_request(storage)?; if ticket_count < config.ticket_count_threshold { return Err(ContractError::TicketCountThresholdNotReached.into()); } diff --git a/contracts/xrpl-multisig-prover/src/error.rs b/contracts/xrpl-multisig-prover/src/error.rs index b61bf5b05..a0895dc0a 100644 --- a/contracts/xrpl-multisig-prover/src/error.rs +++ b/contracts/xrpl-multisig-prover/src/error.rs @@ -109,6 +109,9 @@ pub enum ContractError { #[error("no worker set stored")] NoWorkerSet, + + #[error("generic error {0}")] + GenericError(String), } impl From for StdError { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 6bcbe6e8e..d9eeb764e 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -206,10 +206,11 @@ pub fn get_next_ticket_number(storage: &dyn Storage) -> Result Result { +pub fn tickets_available_to_request(storage: &mut dyn Storage) -> Result { let available_tickets = AVAILABLE_TICKETS.load(storage)?; - let ticket_count = 250 - (available_tickets.len() as u32); - Ok(ticket_count) + let available_ticket_count = u32::try_from(available_tickets.len()).map_err(|e| ContractError::GenericError(e.to_string()))?; + assert!(available_ticket_count <= 250); + Ok(250 - available_ticket_count) } fn get_next_sequence_number(storage: &dyn Storage) -> Result { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index 71e640bed..097804ba0 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -205,7 +205,7 @@ impl XRPLSerialize for XRPLAccountId { fn xrpl_serialize(self) -> Result, ContractError> { let mut result: Vec = Vec::new(); - result.extend(vec![self.to_bytes().len() as u8]); + result.extend(vec![20u8]); result.extend(self.to_bytes()); Ok(result) } From 09b32c3e73dc7a66c95b46b1ff9f22e5cae74715 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 7 May 2024 02:32:35 +0300 Subject: [PATCH 75/80] vec handling improvements --- .../src/axelar_workers.rs | 20 ++++++++++--------- .../xrpl-multisig-prover/src/contract.rs | 2 +- contracts/xrpl-multisig-prover/src/querier.rs | 3 ++- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index e1c15559f..483f38f55 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -77,7 +77,7 @@ fn convert_uint256_to_u16(value: Uint256) -> Result { // Converts a Vec to Vec, scaling down with precision loss, if necessary. // We make sure that XRPL multisig and Axelar multisig both use the same scaled down numbers and have the same precision loss -fn convert_or_scale_weights(weights: Vec) -> Result, ContractError> { +fn convert_or_scale_weights(weights: &[Uint256]) -> Result, ContractError> { let max_weight: Option<&Uint256> = weights.iter().max(); match max_weight { Some(max_weight) => { @@ -107,10 +107,12 @@ pub fn get_active_worker_set( .filter_map(|result| result.ok()) .collect(); + let weights = convert_or_scale_weights(participants .iter() .map(|participant| Uint256::from(participant.weight)) - .collect())?; + .collect::>() + .as_slice())?; let mut signers: Vec = vec![]; for (i, participant) in participants.iter().enumerate() { @@ -167,15 +169,15 @@ mod tests { #[test] fn test_convert_or_scale_weights() { let weights = vec![Uint256::from(1u128), Uint256::from(1u128)]; - let scaled_weights = convert_or_scale_weights(weights).unwrap(); + let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![65535, 65535]); let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128)]; - let scaled_weights = convert_or_scale_weights(weights).unwrap(); + let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![21845, 43690, 65535]); let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128), Uint256::from(4u128)]; - let scaled_weights = convert_or_scale_weights(weights).unwrap(); + let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![16383, 32767, 49151, 65535]); let weights = vec![ @@ -184,7 +186,7 @@ mod tests { Uint256::MAX - Uint256::from(1u128), Uint256::MAX ]; - let scaled_weights = convert_or_scale_weights(weights).unwrap(); + let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![65534, 65534, 65534, 65535]); let weights = vec![ @@ -193,7 +195,7 @@ mod tests { Uint256::MAX - Uint256::from(1u128), Uint256::MAX ]; - let scaled_weights = convert_or_scale_weights(weights).unwrap(); + let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![0, 0, 65534, 65535]); let weights = vec![ @@ -203,10 +205,10 @@ mod tests { Uint256::from(400000000u128), Uint256::from(50000000000u128), ]; - let scaled_weights = convert_or_scale_weights(weights).unwrap(); + let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![0, 2, 39, 524, 65535]); - let scaled_weights = convert_or_scale_weights(vec![]).unwrap(); + let scaled_weights = convert_or_scale_weights(&vec![]).unwrap(); assert_eq!(scaled_weights, vec![] as Vec); } } diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index fcadf2939..d11727363 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -363,7 +363,7 @@ fn update_tx_status( let signed_tx = XRPLSignedTransaction::new(tx_info.unsigned_contents, xrpl_signers); let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); - let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice().to_vec())?).into(); + let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice())?).into(); if parse_message_id(&message_id.id).map_err(|_| ContractError::InvalidMessageID(message_id.id.to_string()))?.0.to_string() != tx_hash.to_string() { return Err(ContractError::InvalidMessageID(message_id.id.to_string())); diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index c7243625a..3972d3cec 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -72,7 +72,8 @@ impl<'a> Querier<'a> { messages: vec![message], } )?; - Ok(statuses[0].1) + let status = statuses.get(0).ok_or(ContractError::GenericError("failed fetching message status".to_owned()))?; + Ok(status.1) } pub fn get_multisig_session(&self, multisig_session_id: &Uint64) -> Result { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index d9eeb764e..9c6b6938d 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -271,8 +271,8 @@ pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result) -> Result { - Ok(TxHash(HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx.as_slice())))) +pub fn compute_signed_tx_hash(encoded_signed_tx: &[u8]) -> Result { + Ok(TxHash(HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx)))) } pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_address: &XRPLAccountId) -> Result<[u8; 32], ContractError> { From bfaf2a652271dcd0d54d977b995ff465ed09ada5 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Tue, 7 May 2024 21:41:43 +0300 Subject: [PATCH 76/80] optimize xrpl serialize functions --- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 5 +- .../src/xrpl_serialize.rs | 50 ++++++++++++------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 9c6b6938d..b98d0e391 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -276,8 +276,9 @@ pub fn compute_signed_tx_hash(encoded_signed_tx: &[u8]) -> Result Result<[u8; 32], ContractError> { - let msg = &[encoded_unsigned_tx.to_vec(), signer_address.to_bytes().into()].concat(); - Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg)) + let mut msg = encoded_unsigned_tx.to_vec(); + msg.extend_from_slice(&signer_address.to_bytes()); + Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg.as_slice())) } #[cfg(test)] diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index 097804ba0..9a91ac581 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -1,5 +1,6 @@ use std::cmp::Ordering; +use cosmwasm_schema::serde::Serializer; use cosmwasm_std::{HexBinary, Uint64}; use multisig::key::PublicKey; @@ -80,13 +81,13 @@ macro_rules! xrpl_json { pub trait XRPLSerialize { const TYPE_CODE: u8; - fn xrpl_serialize(self) -> Result, ContractError>; + fn xrpl_serialize(&self) -> Result, ContractError>; } impl XRPLSerialize for u16 { const TYPE_CODE: u8 = 1; - fn xrpl_serialize(self) -> Result, ContractError> { + fn xrpl_serialize(&self) -> Result, ContractError> { Ok(self.to_be_bytes().to_vec()) } } @@ -94,7 +95,7 @@ impl XRPLSerialize for u16 { impl XRPLSerialize for u32 { const TYPE_CODE: u8 = 2; - fn xrpl_serialize(self) -> Result, ContractError> { + fn xrpl_serialize(&self) -> Result, ContractError> { Ok(self.to_be_bytes().to_vec()) } } @@ -102,9 +103,9 @@ impl XRPLSerialize for u32 { impl XRPLSerialize for XRPLPaymentAmount { const TYPE_CODE: u8 = 6; - fn xrpl_serialize(self) -> Result, ContractError> { + fn xrpl_serialize(&self) -> Result, ContractError> { match self { - XRPLPaymentAmount::Drops(value) => { + &XRPLPaymentAmount::Drops(value) => { if value <= 10u64.pow(17) { Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) } else { @@ -112,7 +113,11 @@ impl XRPLSerialize for XRPLPaymentAmount { } }, XRPLPaymentAmount::Token(token, amount) => { - Ok([amount.to_bytes().as_ref(), token.currency.clone().to_bytes().as_ref(), token.issuer.to_bytes().as_ref()].concat()) + let mut buf = Vec::with_capacity(48); + buf.extend_from_slice(&amount.to_bytes()); + buf.extend_from_slice(&token.currency.clone().to_bytes()); + buf.extend_from_slice(&token.issuer.to_bytes()); + Ok(buf) } } } @@ -121,8 +126,13 @@ impl XRPLSerialize for XRPLPaymentAmount { impl XRPLSerialize for HexBinary { const TYPE_CODE: u8 = 7; - fn xrpl_serialize(self) -> Result, ContractError> { - Ok([encode_length(self.len())?, self.to_vec()].concat()) + fn xrpl_serialize(&self) -> Result, ContractError> { + let len_encoded = encode_length(self.len())?; + let contents = self.to_vec(); + let mut result = Vec::with_capacity(len_encoded.len() + contents.len()); + result.extend(len_encoded); + result.extend(contents); + Ok(result) } } @@ -148,7 +158,7 @@ pub fn encode_length(mut length: usize) -> Result, ContractError> { impl XRPLSerialize for PublicKey { const TYPE_CODE: u8 = 7; - fn xrpl_serialize(self) -> Result, ContractError> { + fn xrpl_serialize(&self) -> Result, ContractError> { match self.clone() { // rippled prefixes Ed25519 public keys with the byte 0xED so both types of public key are 33 bytes. // https://xrpl.org/cryptographic-keys.html @@ -203,7 +213,7 @@ impl TryInto for XRPLMemo { impl XRPLSerialize for XRPLAccountId { const TYPE_CODE: u8 = 8; - fn xrpl_serialize(self) -> Result, ContractError> { + fn xrpl_serialize(&self) -> Result, ContractError> { let mut result: Vec = Vec::new(); result.extend(vec![20u8]); result.extend(self.to_bytes()); @@ -304,9 +314,9 @@ struct XRPLArray { impl XRPLSerialize for XRPLArray { const TYPE_CODE: u8 = 15; - fn xrpl_serialize(self) -> Result, ContractError> { + fn xrpl_serialize(&self) -> Result, ContractError> { let mut result: Vec = Vec::new(); - for item in self.items { + for item in &self.items { result.extend(field_id(T::TYPE_CODE, self.field.clone().to_u8())); result.extend(item.xrpl_serialize()?); result.extend(field_id(T::TYPE_CODE, 1)); @@ -327,9 +337,6 @@ impl SerializedField { fn new(type_code: u8, field_code: u8, serialized_value: Vec) -> Self { Self { type_code, field_code, serialized_value } } - fn to_vec(&self) -> Vec { - return [field_id(self.type_code, self.field_code), self.clone().serialized_value].concat() - } } impl PartialOrd for SerializedField { @@ -377,10 +384,15 @@ impl XRPLObject { impl XRPLSerialize for XRPLObject { const TYPE_CODE: u8 = 14; - fn xrpl_serialize(self) -> Result, ContractError> { + fn xrpl_serialize(&self) -> Result, ContractError> { let mut fields: Vec = self.fields.clone(); fields.sort(); - Ok(fields.into_iter().map(|f| { f.to_vec() }).collect::>>().concat()) + let mut buf = Vec::new(); + for field in fields { + buf.extend(field_id(field.type_code, field.field_code)); + buf.extend(field.serialized_value); + } + Ok(buf) } } @@ -391,7 +403,7 @@ where { const TYPE_CODE: u8 = XRPLObject::TYPE_CODE; - fn xrpl_serialize(self) -> Result, ContractError> { + fn xrpl_serialize(&self) -> Result, ContractError> { let obj: XRPLObject = self.clone().try_into()?; obj.xrpl_serialize() } @@ -408,6 +420,8 @@ pub fn field_id(type_code: u8, field_code: u8) -> Vec { } } + + #[cfg(test)] mod tests { use cosmwasm_std::Uint128; From 7acea6f3aca8a00e80835be53522d91c01e2b944 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Wed, 8 May 2024 15:22:34 +0300 Subject: [PATCH 77/80] allocation improvements --- contracts/xrpl-multisig-prover/src/query.rs | 14 ++-- contracts/xrpl-multisig-prover/src/types.rs | 21 ++--- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 9 ++- .../src/xrpl_serialize.rs | 78 ++++++++++--------- integration-tests/tests/test_utils/mod.rs | 2 +- 5 files changed, 63 insertions(+), 61 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index d73dd5193..d6e463679 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -19,11 +19,10 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, return Err(ContractError::TransactionStatusNotPending.into()); } - let serialized_unsigned_tx = tx_info.unsigned_contents.xrpl_serialize()?; + let mut tx_blob = tx_info.unsigned_contents.xrpl_serialize()?; + tx_blob.extend(signer_xrpl_address.to_bytes()); - let serialized_tx = &[serialized_unsigned_tx, signer_xrpl_address.to_bytes().to_vec()].concat(); - - Ok(xrpl_multisig::xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, serialized_tx).into()) + Ok(xrpl_multisig::xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, tx_blob.as_slice()).into()) } pub fn verify_signature(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: &PublicKey, signature: &Signature) -> StdResult { @@ -44,12 +43,11 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & let response = match multisig_session.state { MultisigState::Pending => GetProofResponse::Pending { unsigned_tx_hash }, MultisigState::Completed { .. } => { - let axelar_signers: Vec<(multisig::msg::Signer, multisig::key::Signature)> = multisig_session.signers + let xrpl_signers: Vec = multisig_session.signers .into_iter() .filter_map(|(signer, sig)| sig.map(|sig| (signer, sig))) - .collect(); - - let xrpl_signers = axelar_signers.into_iter().map(|p| XRPLSigner::try_from(p)).collect::, ContractError>>()?; + .map(|p| XRPLSigner::try_from(p)) + .collect::, ContractError>>()?; let signed_tx = XRPLSignedTransaction::new(tx_info.unsigned_contents, xrpl_signers); let tx_blob: HexBinary = HexBinary::from(signed_tx.xrpl_serialize()?); GetProofResponse::Completed { unsigned_tx_hash, tx_blob } diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 1e991a0a6..78de6e537 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -83,7 +83,7 @@ impl KeyDeserialize for TxHash { type Output = TxHash; fn from_vec(value: Vec) -> StdResult { - Ok(from_json(&Binary::from(value)).expect("violated invariant: TxHash is not deserializable")) + from_json(&Binary::from(value)) } } @@ -113,9 +113,7 @@ pub struct XRPLToken { #[cw_serde] pub enum XRPLPaymentAmount { - Drops( - u64, - ), + Drops(u64), Token(XRPLToken, XRPLTokenAmount), } @@ -239,14 +237,17 @@ impl XRPLAccountId { } pub fn to_string(&self) -> String { - let address_type_prefix: &[u8] = &[0x00]; - let payload = [address_type_prefix, &self.to_bytes()].concat(); + let mut payload = Vec::::with_capacity(25); + payload.extend(&[0x00]); + payload.extend_from_slice(&self.to_bytes()); let checksum_hash1 = Sha256::digest(payload.clone()); let checksum_hash2 = Sha256::digest(checksum_hash1); let checksum = &checksum_hash2[0..4]; - bs58::encode([payload, checksum.to_vec()].concat()) + payload.extend(checksum); + + bs58::encode(payload) .with_alphabet(bs58::Alphabet::RIPPLE) .into_string() } @@ -265,10 +266,10 @@ impl From<&PublicKey> for XRPLAccountId { } } -impl TryFrom<&str> for XRPLAccountId { - type Error = ContractError; +impl std::str::FromStr for XRPLAccountId { + type Err = ContractError; - fn try_from(address: &str) -> Result { + fn from_str(address: &str) -> Result { let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().map_err(|_| ContractError::InvalidAddress)?; // .map_err(|_| ContractError::InvalidAddress)?; if res.len() != 25 { diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index b98d0e391..a7214fd6a 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use axelar_wasm_std::nonempty; use connection_router_api::CrossChainId; use cosmwasm_std::{wasm_execute, HexBinary, Response, Storage}; @@ -46,11 +47,11 @@ pub fn issue_payment( let ticket_number = assign_ticket_number(storage, message_id)?; let tx = XRPLPaymentTx { - account: config.xrpl_multisig.as_str().try_into()?, + account: XRPLAccountId::from_str(config.xrpl_multisig.as_str())?, fee: config.xrpl_fee, sequence: XRPLSequence::Ticket(ticket_number), amount: amount.clone(), - destination: XRPLAccountId::try_from(destination.as_str())? + destination: XRPLAccountId::from_str(destination.as_str())? }; issue_tx( @@ -68,7 +69,7 @@ pub fn issue_ticket_create( let sequence_number = get_next_sequence_number(storage)?; let tx = XRPLTicketCreateTx { - account: config.xrpl_multisig.as_str().try_into()?, + account: XRPLAccountId::from_str(config.xrpl_multisig.as_str())?, fee: config.xrpl_fee, sequence: XRPLSequence::Plain(sequence_number.clone()), ticket_count, @@ -89,7 +90,7 @@ pub fn issue_signer_list_set( let sequence_number = get_next_sequence_number(storage)?; let tx = XRPLSignerListSetTx { - account: config.xrpl_multisig.as_str().try_into()?, + account: XRPLAccountId::from_str(config.xrpl_multisig.as_str())?, fee: config.xrpl_fee, sequence: XRPLSequence::Plain(sequence_number.clone()), signer_quorum: workers.quorum, diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index 9a91ac581..bf83dd7b8 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -424,6 +424,8 @@ pub fn field_id(type_code: u8, field_code: u8) -> Vec { #[cfg(test)] mod tests { + use std::str::FromStr; + use cosmwasm_std::Uint128; use multisig::key::PublicKey; @@ -462,7 +464,7 @@ mod tests { assert_hex_eq!( "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + issuer: XRPLAccountId::from_str("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ")?, currency: "USD".to_string().try_into()?, }, canonicalize_coin_amount(Uint128::zero(), 0)?) .xrpl_serialize()? @@ -470,7 +472,7 @@ mod tests { assert_hex_eq!( "D4838D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + issuer: XRPLAccountId::from_str("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ")?, currency: "USD".to_string().try_into()?, }, canonicalize_coin_amount(Uint128::one(), 0)?) .xrpl_serialize()? @@ -479,7 +481,7 @@ mod tests { assert_hex_eq!( "C0438D7EA4C6800000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + issuer: XRPLAccountId::from_str("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ")?, currency: "USD".to_string().try_into()? }, XRPLTokenAmount::new(MIN_MANTISSA, MIN_EXPONENT)) .xrpl_serialize()? @@ -488,7 +490,7 @@ mod tests { assert_hex_eq!( "EC6386F26FC0FFFF00000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + issuer: XRPLAccountId::from_str("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ")?, currency: "USD".to_string().try_into()? }, XRPLTokenAmount::new(MAX_MANTISSA, MAX_EXPONENT)) .xrpl_serialize()? @@ -500,7 +502,7 @@ mod tests { assert_hex_eq!( "D4CEEBE0B40E8000000000000000000000000000247B7D00000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { - issuer: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + issuer: XRPLAccountId::from_str("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ")?, currency: "${}".to_string().try_into()?, }, canonicalize_coin_amount(Uint128::from(42u128), 0)?) .xrpl_serialize()? @@ -536,25 +538,25 @@ mod tests { // account "0" (with length prefix) assert_hex_eq!( "140000000000000000000000000000000000000000", - XRPLAccountId::try_from("rrrrrrrrrrrrrrrrrrrrrhoLvTp")? + XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrrhoLvTp")? .xrpl_serialize()? ); // account "1" (with length prefix) assert_hex_eq!( "140000000000000000000000000000000000000001", - XRPLAccountId::try_from("rrrrrrrrrrrrrrrrrrrrBZbvji")? + XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrBZbvji")? .xrpl_serialize()? ); // max acccount assert_hex_eq!( "14FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - XRPLAccountId::try_from("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi")? + XRPLAccountId::from_str("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi")? .xrpl_serialize()? ); assert_hex_eq!( "13000081140000000000000000000000000000000000000000", XRPLSignerEntry{ - account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, + account: XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrrhoLvTp")?, signer_weight: 0 }.xrpl_serialize()? ); @@ -575,7 +577,7 @@ mod tests { "EA13000081140000000000000000000000000000000000000000E1F1", XRPLArray::{ field: Field::Memo, items: vec![ XRPLSignerEntry{ - account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp".try_into()?, + account: XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrrhoLvTp")?, signer_weight: 0 }, ] } @@ -587,17 +589,17 @@ mod tests { #[test] fn serialize_xrpl_unsigned_token_payment_transaction() -> Result<(), ContractError> { let unsigned_tx = XRPLPaymentTx { - account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + account: XRPLAccountId::from_str("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ")?, fee: 12, sequence: XRPLSequence::Plain(1), amount: XRPLPaymentAmount::Token( XRPLToken { currency: "JPY".to_string().try_into()?, - issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji".try_into()?, + issuer: XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrBZbvji")?, }, XRPLTokenAmount::new(3369568318000000u64, -16) ), - destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, + destination: XRPLAccountId::from_str("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")?, }; let encoded_unsigned_tx = XRPLUnsignedTx::Payment(unsigned_tx).xrpl_serialize()?; assert_eq!( @@ -610,11 +612,11 @@ mod tests { #[test] fn serialize_xrpl_unsigned_xrp_payment_transaction() -> Result<(), ContractError> { let tx = XRPLPaymentTx { - account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ".try_into()?, + account: XRPLAccountId::from_str("r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ")?, fee: 10, sequence: XRPLSequence::Plain(1), amount: XRPLPaymentAmount::Drops(1000), - destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh".try_into()?, + destination: XRPLAccountId::from_str("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")?, }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; assert_eq!( @@ -623,11 +625,11 @@ mod tests { ); let tx = XRPLPaymentTx { - account: "rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj".try_into()?, + account: XRPLAccountId::from_str("rhKnz85JUKcrAizwxNUDfqCvaUi9ZMhuwj")?, fee: 3, sequence: XRPLSequence::Plain(43497363), amount: XRPLPaymentAmount::Drops(1000000000), - destination: "rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq".try_into()?, + destination: XRPLAccountId::from_str("rw2521mDNXyKzHBrFGZ5Rj4wzUjS9FbiZq")?, }; let encoded_unsigned_tx = &XRPLUnsignedTx::Payment(tx).xrpl_serialize()?; assert_eq!( @@ -645,19 +647,19 @@ mod tests { fn serialize_xrpl_signed_xrp_payment_transaction() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, + account: XRPLAccountId::from_str("rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb")?, fee: 30, sequence: XRPLSequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), - destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, + destination: XRPLAccountId::from_str("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG")?, }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + account: XRPLAccountId::from_str("r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ")?, txn_signature: HexBinary::from_hex("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045")?, signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + account: XRPLAccountId::from_str("rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f")?, txn_signature: HexBinary::from_hex("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9")?, signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? } @@ -675,19 +677,19 @@ mod tests { fn tx_serialization_sort_signers() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, + account: XRPLAccountId::from_str("rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb")?, fee: 30, sequence: XRPLSequence::Ticket(44218193), amount: XRPLPaymentAmount::Drops(100000000), - destination: "rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG".try_into()?, + destination: XRPLAccountId::from_str("rfgqgX62inhKsfti1NR6FeMS8NcQJCFniG")?, }), signers: vec![ XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + account: XRPLAccountId::from_str("rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f")?, txn_signature: HexBinary::from_hex("3045022100FC1490C236AD05A306EB5FD89072F14FEFC19ED35EB61BACD294D10E0910EDB102205A4CF0C0A759D7158A8FEE2F526C70277910DE88BF85564A1B3142AE635C9CE9")?, signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? }, XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + account: XRPLAccountId::from_str("r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ")?, txn_signature: HexBinary::from_hex("3044022023DD4545108D411008FC9A76A58E1573AB0F8786413C8F38A92B1E2EAED60014022012A0A7890BFD0F0C8EA2C342107F65D4C91CAC29AAF3CF2840350BF3FB91E045")?, signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? }, @@ -705,22 +707,22 @@ mod tests { fn tx_serialization_ed25519_signers() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::Payment(XRPLPaymentTx { - account: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()?, + account: XRPLAccountId::from_str("r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM")?, fee: 30, sequence: XRPLSequence::Ticket(45205896), amount: XRPLPaymentAmount::Token(XRPLToken{ currency: "ETH".to_string().try_into()?, - issuer: "r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM".try_into()? + issuer: XRPLAccountId::from_str("r4ZMbbb4Y3KoeexmjEeTdhqUBrYjjWdyGM")? }, canonicalize_coin_amount(Uint128::from(100000000u128), 0)?), - destination: "raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo".try_into()?, + destination: XRPLAccountId::from_str("raNVNWvhUQzFkDDTdEw3roXRJfMJFVJuQo")?, }), signers: vec![ XRPLSigner{ - account: "rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj".try_into()?, + account: XRPLAccountId::from_str("rBTmbPMAWghUv52pCCtkLYh5SPVy2PuDSj")?, txn_signature: HexBinary::from_hex("531B9E854C81AEFA573C00DF1603C3DE80C1F3680D39A80F3FB725A0388D177E3EC5E28AD6760D9EEF8203FEB1FC61F9D9451F777114B97943E5702B54589E09")?, signing_pub_key: PublicKey::Ed25519(HexBinary::from_hex("45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f")?) }, XRPLSigner{ - account: "rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5".try_into()?, + account: XRPLAccountId::from_str("rhAdaMDgF89314TfNRHc5GsA6LQZdk35S5")?, txn_signature: HexBinary::from_hex("76CF2097D7038B90445CB952AE52CBDBE6D55FE7C0562493FE3D9AAE5E05A66A43777CBCDAA89233CAFD4D1D0F9B02DB0619B9BB14957CC3ADAA8D7D343E0106")?, signing_pub_key: PublicKey::Ed25519(HexBinary::from_hex("dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b")?) }, @@ -738,18 +740,18 @@ mod tests { fn serialize_xrpl_signed_xrp_ticket_create_transaction() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::TicketCreate(XRPLTicketCreateTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, + account: XRPLAccountId::from_str("rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb")?, fee: 30, sequence: XRPLSequence::Plain(44218194), ticket_count: 3, }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + account: XRPLAccountId::from_str("r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ")?, txn_signature: HexBinary::from_hex("304402203C10D5295AE4A34FD702355B075E951CF9FFE3A73F8B7557FB68E5DF64D87D3702200945D65BAAD7F10A14EA57E08914005F412709D10F27D868D63BE3052F30363F")?, signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")? }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + account: XRPLAccountId::from_str("rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f")?, txn_signature: HexBinary::from_hex("3045022100EF2CBAC3B2D81E1E3502B064BA198D9D0D3F1FFE6604DAC5019C53C262B5F9E7022000808A438BD5CA808649DCDA6766D2BA0E8FA7E94150675F73FC41B2F73C9C58")?, signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")? }, @@ -767,28 +769,28 @@ mod tests { fn serialize_xrpl_signed_signer_list_set_transaction() -> Result<(), ContractError> { let signed_tx = XRPLSignedTransaction { unsigned_tx: XRPLUnsignedTx::SignerListSet(XRPLSignerListSetTx { - account: "rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb".try_into()?, + account: XRPLAccountId::from_str("rfEf91bLxrTVC76vw1W3Ur8Jk4Lwujskmb")?, fee: 30, sequence: XRPLSequence::Plain(44218445), signer_quorum: 3, signer_entries: vec![ XRPLSignerEntry{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + account: XRPLAccountId::from_str("r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ")?, signer_weight: 2 }, XRPLSignerEntry{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + account: XRPLAccountId::from_str("rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f")?, signer_weight: 1 } ], }), signers: vec![ XRPLSigner{ - account: "r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ".try_into()?, + account: XRPLAccountId::from_str("r3mJFUQeVQma7qucT4iQSNCWuijVCPcicZ")?, txn_signature: HexBinary::from_hex("3045022100B94B346A418BE9EF5AEE7806EE984E3E9B48EB4ED48E79B5BFB69C607167023E02206B14BD72B69206D14DADA82ACCDD2539D275719FB187ECE2A46BAC9025877B39")?, signing_pub_key: pub_key_from_hex("025E0231BFAD810E5276E2CF9EB2F3F380CE0BDF6D84C3B6173499D3DDCC008856")?, }, XRPLSigner{ - account: "rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f".try_into()?, + account: XRPLAccountId::from_str("rHxbKjRSFUUyuiio1jnFhimJRVAYYaGj7f")?, txn_signature: HexBinary::from_hex("3044022072A1028FF972D9D6E950810AF72443EEE352ADB1BC54B1112983842C857C464502206D74A77387979A47863F08F9191611D142C2BD6B32D5C750EF58513C5669F21A")?, signing_pub_key: pub_key_from_hex("036FF6F4B2BC5E08ABA924BD8FD986608F3685CA651A015B3D9D6A656DE14769FE")?, }, diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 75d5ac80a..cdc380d46 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -1048,7 +1048,7 @@ pub fn setup_xrpl(protocol: &mut Protocol) -> XRPLChain { &multisig_prover, ETH_DENOMINATION.to_string(), XRPLToken { - issuer: xrpl_multisig_address.as_str().try_into().unwrap(), + issuer: XRPLAccountId::from_str(xrpl_multisig_address.as_str()).unwrap(), currency: "ETH".to_string().try_into().unwrap(), }, 18u8, From ee9a6782c669c0d3fed6d519a012754f95594f0e Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Thu, 9 May 2024 23:07:52 +0300 Subject: [PATCH 78/80] minor clippy improvements --- .../src/axelar_workers.rs | 26 ++++----- .../xrpl-multisig-prover/src/contract.rs | 49 ++++++++--------- contracts/xrpl-multisig-prover/src/querier.rs | 8 +-- contracts/xrpl-multisig-prover/src/query.rs | 10 ++-- contracts/xrpl-multisig-prover/src/reply.rs | 7 ++- contracts/xrpl-multisig-prover/src/types.rs | 54 +++++++++++-------- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 12 ++--- .../src/xrpl_serialize.rs | 15 +++--- 8 files changed, 90 insertions(+), 91 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index 483f38f55..b5915c967 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -20,11 +20,11 @@ pub struct AxelarSigner { pub pub_key: PublicKey, } -impl Into for AxelarSigner { - fn into(self) -> Participant { - let weight = nonempty::Uint256::try_from(Uint256::from(u128::try_from(self.weight).unwrap())).unwrap(); - Participant { - address: self.address, +impl From for Participant { + fn from(signer: AxelarSigner) -> Self { + let weight = nonempty::Uint256::try_from(Uint256::from(u128::from(signer.weight))).unwrap(); + Self { + address: signer.address, weight, } } @@ -39,15 +39,15 @@ pub struct WorkerSet { pub created_at: u64, } -impl Into for WorkerSet { - fn into(self) -> multisig::worker_set::WorkerSet { - let participants = self.signers.into_iter() +impl From for multisig::worker_set::WorkerSet { + fn from(worker_set: WorkerSet) -> Self { + let participants = worker_set.signers.into_iter() .map(|s| (s.clone().into(), s.pub_key)) .collect(); multisig::worker_set::WorkerSet::new( participants, - Uint256::from(u128::try_from(self.quorum).unwrap()), - self.created_at + Uint256::from(u128::from(worker_set.quorum)), + worker_set.created_at ) } } @@ -103,7 +103,7 @@ pub fn get_active_worker_set( let participants: Vec = workers .into_iter() - .map(|worker| Participant::try_from(worker)) + .map(Participant::try_from) .filter_map(|result| result.ok()) .collect(); @@ -116,7 +116,7 @@ pub fn get_active_worker_set( let mut signers: Vec = vec![]; for (i, participant) in participants.iter().enumerate() { - let pub_key: PublicKey = querier.get_public_key(&participant.address.to_string())?; + let pub_key: PublicKey = querier.get_public_key(participant.address.to_string())?; signers.push(AxelarSigner { address: participant.address.clone(), weight: weights[i], @@ -134,7 +134,7 @@ pub fn get_active_worker_set( .unwrap(); let worker_set = WorkerSet { - signers: BTreeSet::from_iter(signers.into_iter()), + signers: BTreeSet::from_iter(signers), quorum, created_at: block_height, }; diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index d11727363..898beeaa7 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -193,14 +193,11 @@ fn construct_payment_proof( } // Prevent creating a duplicate signing session before the previous one expires - match MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, &message_id)? { - Some(multisig_session_id) => { - let multisig_session = querier.get_multisig_session(&Uint64::from(multisig_session_id))?; - if multisig_session.state == MultisigState::Pending && multisig_session.expires_at <= block_height { - return Err(ContractError::PaymentAlreadyHasActiveSigningSession(multisig_session_id)); - } - }, - None => (), + if let Some(multisig_session_id) = MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, &message_id)? { + let multisig_session = querier.get_multisig_session(&Uint64::from(multisig_session_id))?; + if multisig_session.state == MultisigState::Pending && multisig_session.expires_at <= block_height { + return Err(ContractError::PaymentAlreadyHasActiveSigningSession(multisig_session_id)); + } }; let message = querier.get_message(&message_id)?; @@ -225,13 +222,11 @@ fn construct_payment_proof( )?; REPLY_MESSAGE_ID.save(storage, &message_id)?; - Ok( - start_signing_session( - storage, - config, - tx_hash, - self_address - )? + start_signing_session( + storage, + config, + tx_hash, + self_address ) } @@ -268,7 +263,7 @@ fn construct_signer_list_set_proof( config: &Config, ) -> Result { if !CURRENT_WORKER_SET.exists(storage) { - return Err(ContractError::WorkerSetIsNotSet.into()) + return Err(ContractError::WorkerSetIsNotSet) } let new_worker_set = axelar_workers::get_active_worker_set(querier, config.signing_threshold, env.block.height)?; @@ -278,7 +273,7 @@ fn construct_signer_list_set_proof( &cur_worker_set.clone().into(), usize::try_from(config.worker_set_diff_threshold).unwrap(), ) { - return Err(ContractError::WorkerSetUnchanged.into()) + return Err(ContractError::WorkerSetUnchanged) } let tx_hash = xrpl_multisig::issue_signer_list_set( @@ -289,13 +284,11 @@ fn construct_signer_list_set_proof( NEXT_WORKER_SET.save(storage, &tx_hash, &new_worker_set)?; - Ok( - start_signing_session( - storage, - config, - tx_hash, - env.contract.address - )? + start_signing_session( + storage, + config, + tx_hash, + env.contract.address ) } @@ -306,7 +299,7 @@ fn construct_ticket_create_proof( ) -> Result { let ticket_count = xrpl_multisig::tickets_available_to_request(storage)?; if ticket_count < config.ticket_count_threshold { - return Err(ContractError::TicketCountThresholdNotReached.into()); + return Err(ContractError::TicketCountThresholdNotReached); } let tx_hash = xrpl_multisig::issue_ticket_create( @@ -330,13 +323,13 @@ fn update_tx_status( querier: &Querier, config: &Config, multisig_session_id: &Uint64, - signer_public_keys: &Vec, + signer_public_keys: &[PublicKey], message_id: &CrossChainId, status: VerificationStatus, ) -> Result { let unsigned_tx_hash = MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; - let multisig_session = querier.get_multisig_session(&multisig_session_id)?; + let multisig_session = querier.get_multisig_session(multisig_session_id)?; let destination_str = match &tx_info.unsigned_contents { XRPLUnsignedTx::Payment(p) => p.destination.to_string(), @@ -363,7 +356,7 @@ fn update_tx_status( let signed_tx = XRPLSignedTransaction::new(tx_info.unsigned_contents, xrpl_signers); let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); - let tx_hash: HexBinary = TxHash::from(xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice())?).into(); + let tx_hash: HexBinary = xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice())?.into(); if parse_message_id(&message_id.id).map_err(|_| ContractError::InvalidMessageID(message_id.id.to_string()))?.0.to_string() != tx_hash.to_string() { return Err(ContractError::InvalidMessageID(message_id.id.to_string())); diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index 3972d3cec..970afe784 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -48,10 +48,10 @@ impl<'a> Querier<'a> { ) } - pub fn get_public_key(&self, worker_address: &String) -> Result { + pub fn get_public_key(&self, worker_address: String) -> Result { query(self.querier, self.config.axelar_multisig.to_string(), &multisig::msg::QueryMsg::GetPublicKey { - worker_address: worker_address.clone(), + worker_address, key_type: self.config.key_type, }, ) @@ -72,13 +72,13 @@ impl<'a> Querier<'a> { messages: vec![message], } )?; - let status = statuses.get(0).ok_or(ContractError::GenericError("failed fetching message status".to_owned()))?; + let status = statuses.first().ok_or(ContractError::GenericError("failed fetching message status".to_owned()))?; Ok(status.1) } pub fn get_multisig_session(&self, multisig_session_id: &Uint64) -> Result { let query_msg = multisig::msg::QueryMsg::GetMultisig { - session_id: multisig_session_id.clone(), + session_id: *multisig_session_id, }; query(self.querier, self.config.axelar_multisig.to_string(), &query_msg) } diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index d6e463679..eac1f579d 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -30,7 +30,7 @@ pub fn verify_signature(storage: &dyn Storage, multisig_session_id: &Uint64, pub let tx_hash = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; // m.tx_hash is going to be over 32 bytes due to inclusion of the signer address, so it has to be passed unchecked - Ok(signature.verify(&multisig::types::MsgToSign::unchecked(tx_hash), &public_key).is_ok()) + Ok(signature.verify(multisig::types::MsgToSign::unchecked(tx_hash), public_key).is_ok()) } pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: &Uint64) -> StdResult { @@ -38,7 +38,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; - let multisig_session= querier.get_multisig_session(&multisig_session_id)?; + let multisig_session= querier.get_multisig_session(multisig_session_id)?; let response = match multisig_session.state { MultisigState::Pending => GetProofResponse::Pending { unsigned_tx_hash }, @@ -46,7 +46,7 @@ pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: & let xrpl_signers: Vec = multisig_session.signers .into_iter() .filter_map(|(signer, sig)| sig.map(|sig| (signer, sig))) - .map(|p| XRPLSigner::try_from(p)) + .map(XRPLSigner::try_from) .collect::, ContractError>>()?; let signed_tx = XRPLSignedTransaction::new(tx_info.unsigned_contents, xrpl_signers); let tx_blob: HexBinary = HexBinary::from(signed_tx.xrpl_serialize()?); @@ -69,8 +69,8 @@ pub fn get_multisig_session_id(storage: &dyn Storage, message_id: &CrossChainId) } if available_tickets.unwrap().contains(&existing_ticket_number.unwrap()) { - return Ok(MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, message_id)?); + return MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, message_id); } - return Ok(None) + Ok(None) } diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 7c3a2a6e2..1fd4dc441 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -11,7 +11,7 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result Result = res.events .into_iter() .filter(|e| e.ty == "wasm-signing_started") - .map(|e| e.attributes) - .flatten() - .filter(|a| !a.key.starts_with("_") && a.key != "msg") + .flat_map(|e| e.attributes) + .filter(|a| !a.key.starts_with('_') && a.key != "msg") .collect(); let evt = cosmwasm_std::Event::new("xrpl_signing_started") diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index 78de6e537..a23e806ec 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -1,3 +1,6 @@ +use std::fmt; +use std::fmt::Display; + use axelar_wasm_std::VerificationStatus; use connection_router_api::CrossChainId; use cosmwasm_schema::cw_serde; @@ -33,15 +36,15 @@ impl TryFrom for TxHash { } } -impl Into for TxHash { - fn into(self) -> HexBinary { - self.0 +impl From for HexBinary { + fn from(hash: TxHash) -> Self { + hash.0 } } -impl Into for VerificationStatus { - fn into(self) -> TransactionStatus { - match self { +impl From for TransactionStatus { + fn from(status: VerificationStatus) -> TransactionStatus { + match status { VerificationStatus::SucceededOnChain => TransactionStatus::Succeeded, VerificationStatus::FailedOnChain => TransactionStatus::FailedOnChain, _ => TransactionStatus::Inconclusive, @@ -83,7 +86,7 @@ impl KeyDeserialize for TxHash { type Output = TxHash; fn from_vec(value: Vec) -> StdResult { - from_json(&Binary::from(value)) + from_json(Binary::from(value)) } } @@ -123,9 +126,9 @@ pub enum XRPLSequence { Ticket(u32), } -impl Into for XRPLSequence { - fn into(self) -> u32 { - match self { +impl From for u32 { + fn from(value: XRPLSequence) -> Self { + match value { XRPLSequence::Plain(sequence) => sequence, XRPLSequence::Ticket(ticket) => ticket, } @@ -229,14 +232,16 @@ pub struct XRPLAccountId([u8; 20]); impl XRPLAccountId { pub const fn to_bytes(&self) -> [u8; 20] { - return self.0; + self.0 } pub fn from_bytes(bytes: [u8; 20]) -> Self { Self(bytes) } +} - pub fn to_string(&self) -> String { +impl Display for XRPLAccountId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut payload = Vec::::with_capacity(25); payload.extend(&[0x00]); payload.extend_from_slice(&self.to_bytes()); @@ -247,9 +252,11 @@ impl XRPLAccountId { payload.extend(checksum); - bs58::encode(payload) + let str = bs58::encode(payload) .with_alphabet(bs58::Alphabet::RIPPLE) - .into_string() + .into_string(); + + write!(f, "{}", str) } } @@ -262,7 +269,7 @@ impl From<&PublicKey> for XRPLAccountId { let public_key_inner_hash = Sha256::digest(public_key_hex); let account_id = Ripemd160::digest(public_key_inner_hash); - return XRPLAccountId(account_id.into()); + XRPLAccountId(account_id.into()) } } @@ -277,7 +284,7 @@ impl std::str::FromStr for XRPLAccountId { } let mut buffer = [0u8; 20]; buffer.copy_from_slice(&res[1..21]); - return Ok(XRPLAccountId(buffer)) + Ok(XRPLAccountId(buffer)) } } @@ -303,7 +310,7 @@ impl TryFrom<(multisig::msg::Signer, multisig::key::Signature)> for XRPLSigner { Ok(XRPLSigner { account: XRPLAccountId::from(&axelar_signer.pub_key), - signing_pub_key: axelar_signer.pub_key.clone().into(), + signing_pub_key: axelar_signer.pub_key.clone(), txn_signature, }) } @@ -330,10 +337,11 @@ impl XRPLCurrency { buffer[12..15].copy_from_slice(self.to_string().as_bytes()); buffer } +} - // Convert the CurrencyCode to a String - fn to_string(self) -> String { - self.0 +impl Display for XRPLCurrency { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) } } @@ -366,7 +374,7 @@ pub struct XRPLTokenAmount { impl XRPLTokenAmount { pub fn new(mantissa: u64, exponent: i64) -> Self { - assert!(mantissa == 0 || (MIN_MANTISSA <= mantissa && mantissa <= MAX_MANTISSA && MIN_EXPONENT <= exponent && exponent <= MAX_EXPONENT)); + assert!(mantissa == 0 || ((MIN_MANTISSA..=MAX_MANTISSA).contains(&mantissa) && (MIN_EXPONENT..=MAX_EXPONENT).contains(&exponent))); Self { mantissa, exponent } } @@ -381,7 +389,7 @@ impl XRPLTokenAmount { } pub fn canonicalize_coin_amount(amount: Uint128, decimals: u8) -> Result{ - let (mantissa, exponent) = canonicalize_mantissa(amount, -1 * i64::from(decimals))?; + let (mantissa, exponent) = canonicalize_mantissa(amount, -i64::from(decimals))?; Ok(XRPLTokenAmount::new(mantissa, exponent)) } @@ -413,5 +421,5 @@ pub fn canonicalize_mantissa(mut mantissa: Uint128, mut exponent: i64) -> Result let mantissa = u64::from_be_bytes(mantissa.to_be_bytes()[8..].try_into().unwrap()); - return Ok((mantissa, exponent)); + Ok((mantissa, exponent)) } diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index a7214fd6a..4434c9f97 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -27,7 +27,7 @@ fn issue_tx( match tx.sequence() { XRPLSequence::Ticket(ticket_number) => { - LAST_ASSIGNED_TICKET_NUMBER.save(storage, &ticket_number)?; + LAST_ASSIGNED_TICKET_NUMBER.save(storage, ticket_number)?; }, XRPLSequence::Plain(_) => { LATEST_SEQUENTIAL_TX_HASH.save(storage, &tx_hash)?; @@ -71,7 +71,7 @@ pub fn issue_ticket_create( let tx = XRPLTicketCreateTx { account: XRPLAccountId::from_str(config.xrpl_multisig.as_str())?, fee: config.xrpl_fee, - sequence: XRPLSequence::Plain(sequence_number.clone()), + sequence: XRPLSequence::Plain(sequence_number), ticket_count, }; @@ -92,9 +92,9 @@ pub fn issue_signer_list_set( let tx = XRPLSignerListSetTx { account: XRPLAccountId::from_str(config.xrpl_multisig.as_str())?, fee: config.xrpl_fee, - sequence: XRPLSequence::Plain(sequence_number.clone()), + sequence: XRPLSequence::Plain(sequence_number), signer_quorum: workers.quorum, - signer_entries: workers.signers.into_iter().map(|worker| XRPLSignerEntry::from(worker)).collect(), + signer_entries: workers.signers.into_iter().map(XRPLSignerEntry::from).collect(), }; issue_tx( @@ -176,7 +176,7 @@ pub fn update_tx_status( pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId) -> Result { // If this message ID has already been ticketed, // then use the same ticket number as before, - if let Some(ticket_number) = MESSAGE_ID_TO_TICKET.may_load(storage, &message_id)? { + if let Some(ticket_number) = MESSAGE_ID_TO_TICKET.may_load(storage, message_id)? { let confirmed_tx_hash = CONFIRMED_TRANSACTIONS.may_load(storage, &ticket_number)?; // as long as it has not already been consumed if confirmed_tx_hash.is_none() @@ -198,7 +198,7 @@ pub fn get_next_ticket_number(storage: &dyn Storage) -> Result u8 { + const fn to_u8(&self) -> u8 { match self { Field::SigningPubKey => 3, Field::TxnSignature => 4, @@ -194,9 +193,9 @@ impl TryInto for XRPLSignerEntry { #[derive(Clone)] pub struct XRPLMemo(HexBinary); -impl Into for XRPLMemo { - fn into(self) -> HexBinary { - return self.0; +impl From for HexBinary { + fn from(memo: XRPLMemo) -> Self { + memo.0 } } @@ -341,17 +340,17 @@ impl SerializedField { impl PartialOrd for SerializedField { fn partial_cmp(&self, other: &SerializedField) -> Option { - return (self.type_code, self.field_code).partial_cmp(&(other.type_code, other.field_code)) + Some(self.cmp(other)) } } impl Ord for SerializedField { fn cmp(&self, other: &SerializedField) -> Ordering { - return (self.type_code, self.field_code).cmp(&(other.type_code, other.field_code)) + (self.type_code, self.field_code).cmp(&(other.type_code, other.field_code)) } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct XRPLObject { fields: Vec } From 6481d67da79328ac7c3e6ad6f10aa8fbd5a03c36 Mon Sep 17 00:00:00 2001 From: Alex Brezas Date: Thu, 9 May 2024 23:10:04 +0300 Subject: [PATCH 79/80] run cargo fmt --- .../src/axelar_workers.rs | 72 ++++--- .../xrpl-multisig-prover/src/contract.rs | 181 +++++++++++------- contracts/xrpl-multisig-prover/src/events.rs | 3 +- contracts/xrpl-multisig-prover/src/lib.rs | 14 +- contracts/xrpl-multisig-prover/src/msg.rs | 22 ++- contracts/xrpl-multisig-prover/src/querier.rs | 82 +++++--- contracts/xrpl-multisig-prover/src/query.rs | 62 ++++-- contracts/xrpl-multisig-prover/src/reply.rs | 39 ++-- contracts/xrpl-multisig-prover/src/state.rs | 10 +- contracts/xrpl-multisig-prover/src/types.rs | 118 +++++++----- .../xrpl-multisig-prover/src/xrpl_multisig.rs | 151 ++++++++++----- .../src/xrpl_serialize.rs | 135 +++++++------ 12 files changed, 563 insertions(+), 326 deletions(-) diff --git a/contracts/xrpl-multisig-prover/src/axelar_workers.rs b/contracts/xrpl-multisig-prover/src/axelar_workers.rs index b5915c967..000d95177 100644 --- a/contracts/xrpl-multisig-prover/src/axelar_workers.rs +++ b/contracts/xrpl-multisig-prover/src/axelar_workers.rs @@ -1,16 +1,19 @@ +use itertools::Itertools; use std::collections::hash_map::RandomState; use std::collections::{BTreeMap, BTreeSet, HashMap}; -use itertools::Itertools; -use axelar_wasm_std::{nonempty, MajorityThreshold}; use axelar_wasm_std::Participant; +use axelar_wasm_std::{nonempty, MajorityThreshold}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Fraction, HexBinary, Uint256}; +use multisig::{ + key::{KeyType, PublicKey}, + msg::Signer, +}; use service_registry::state::WeightedWorker; -use multisig::{key::{PublicKey, KeyType}, msg::Signer}; -use crate::querier::Querier; use crate::error::ContractError; +use crate::querier::Querier; #[cw_serde] #[derive(Eq, Ord, PartialOrd)] @@ -41,21 +44,22 @@ pub struct WorkerSet { impl From for multisig::worker_set::WorkerSet { fn from(worker_set: WorkerSet) -> Self { - let participants = worker_set.signers.into_iter() + let participants = worker_set + .signers + .into_iter() .map(|s| (s.clone().into(), s.pub_key)) .collect(); multisig::worker_set::WorkerSet::new( participants, Uint256::from(u128::from(worker_set.quorum)), - worker_set.created_at + worker_set.created_at, ) } } impl WorkerSet { pub fn pub_keys_by_address(&self) -> HashMap { - self - .signers + self.signers .iter() .map(|signer| { ( @@ -69,7 +73,9 @@ impl WorkerSet { fn convert_uint256_to_u16(value: Uint256) -> Result { if value > Uint256::from(u16::MAX) { - return Err(ContractError::GenericError("Overflow, cannot convert value to u16".to_owned())) + return Err(ContractError::GenericError( + "Overflow, cannot convert value to u16".to_owned(), + )); } let bytes = value.to_le_bytes(); Ok(u16::from(bytes[0]) | u16::from(bytes[1]).checked_shl(8).unwrap()) // this unwrap is never supposed to fail @@ -87,9 +93,9 @@ fn convert_or_scale_weights(weights: &[Uint256]) -> Result, ContractErr let scaled = weight.multiply_ratio(max_u16_as_uint256, *max_weight); result.push(convert_uint256_to_u16(scaled)?); } - + Ok(result) - }, + } None => Ok(vec![]), } } @@ -107,12 +113,13 @@ pub fn get_active_worker_set( .filter_map(|result| result.ok()) .collect(); - - let weights = convert_or_scale_weights(participants - .iter() - .map(|participant| Uint256::from(participant.weight)) - .collect::>() - .as_slice())?; + let weights = convert_or_scale_weights( + participants + .iter() + .map(|participant| Uint256::from(participant.weight)) + .collect::>() + .as_slice(), + )?; let mut signers: Vec = vec![]; for (i, participant) in participants.iter().enumerate() { @@ -126,12 +133,14 @@ pub fn get_active_worker_set( let sum_of_weights: u32 = weights.iter().map(|w| u32::from(*w)).sum(); - let quorum = u32::try_from(u64::from(sum_of_weights) - .checked_mul(signing_threshold.numerator().into()) - .unwrap() - .checked_div(signing_threshold.denominator().into()) - .unwrap()) - .unwrap(); + let quorum = u32::try_from( + u64::from(sum_of_weights) + .checked_mul(signing_threshold.numerator().into()) + .unwrap() + .checked_div(signing_threshold.denominator().into()) + .unwrap(), + ) + .unwrap(); let worker_set = WorkerSet { signers: BTreeSet::from_iter(signers), @@ -172,11 +181,20 @@ mod tests { let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![65535, 65535]); - let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128)]; + let weights = vec![ + Uint256::from(1u128), + Uint256::from(2u128), + Uint256::from(3u128), + ]; let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![21845, 43690, 65535]); - let weights = vec![Uint256::from(1u128), Uint256::from(2u128), Uint256::from(3u128), Uint256::from(4u128)]; + let weights = vec![ + Uint256::from(1u128), + Uint256::from(2u128), + Uint256::from(3u128), + Uint256::from(4u128), + ]; let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![16383, 32767, 49151, 65535]); @@ -184,7 +202,7 @@ mod tests { Uint256::MAX - Uint256::from(3u128), Uint256::MAX - Uint256::from(2u128), Uint256::MAX - Uint256::from(1u128), - Uint256::MAX + Uint256::MAX, ]; let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![65534, 65534, 65534, 65535]); @@ -193,7 +211,7 @@ mod tests { Uint256::from(0u128), Uint256::from(1u128), Uint256::MAX - Uint256::from(1u128), - Uint256::MAX + Uint256::MAX, ]; let scaled_weights = convert_or_scale_weights(&weights).unwrap(); assert_eq!(scaled_weights, vec![0, 0, 65534, 65535]); diff --git a/contracts/xrpl-multisig-prover/src/contract.rs b/contracts/xrpl-multisig-prover/src/contract.rs index 898beeaa7..ac57f4389 100644 --- a/contracts/xrpl-multisig-prover/src/contract.rs +++ b/contracts/xrpl-multisig-prover/src/contract.rs @@ -4,7 +4,8 @@ use std::str::FromStr; use axelar_wasm_std::{MajorityThreshold, VerificationStatus}; use connection_router_api::{Address, ChainName, CrossChainId, Message}; use cosmwasm_std::{ - entry_point, to_json_binary, wasm_execute, Addr, Binary, Deps, DepsMut, Env, Fraction, HexBinary, MessageInfo, Reply, Response, StdResult, Storage, SubMsg, Uint64 + entry_point, to_json_binary, wasm_execute, Addr, Binary, Deps, DepsMut, Env, Fraction, + HexBinary, MessageInfo, Reply, Response, StdResult, Storage, SubMsg, Uint64, }; // TODO: create custom message ID format use voting_verifier::events::parse_message_id; @@ -12,7 +13,19 @@ use voting_verifier::events::parse_message_id; use multisig::{key::PublicKey, types::MultisigState}; use crate::{ - axelar_workers, error::ContractError, msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, querier::{Querier, XRPL_CHAIN_NAME}, query, reply, state::{Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_ID_TO_TX_HASH, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, REPLY_MESSAGE_ID, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO}, types::*, xrpl_multisig, xrpl_serialize::XRPLSerialize + axelar_workers, + error::ContractError, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + querier::{Querier, XRPL_CHAIN_NAME}, + query, reply, + state::{ + Config, AVAILABLE_TICKETS, CONFIG, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, + MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_ID_TO_TX_HASH, NEXT_SEQUENCE_NUMBER, + NEXT_WORKER_SET, REPLY_MESSAGE_ID, REPLY_TX_HASH, TOKENS, TRANSACTION_INFO, + }, + types::*, + xrpl_multisig, + xrpl_serialize::XRPLSerialize, }; pub const START_MULTISIG_REPLY_ID: u64 = 1; @@ -32,7 +45,8 @@ pub fn instantiate( AVAILABLE_TICKETS.save(deps.storage, &msg.available_tickets)?; let querier = Querier::new(deps.querier, config.clone()); - let new_worker_set = axelar_workers::get_active_worker_set(&querier, msg.signing_threshold, env.block.height)?; + let new_worker_set = + axelar_workers::get_active_worker_set(&querier, msg.signing_threshold, env.block.height)?; CURRENT_WORKER_SET.save(deps.storage, &new_worker_set.clone())?; @@ -66,7 +80,9 @@ fn make_config( let voting_verifier = deps.api.addr_validate(&msg.voting_verifier_address)?; let service_registry = deps.api.addr_validate(&msg.service_registry_address)?; - if msg.signing_threshold.numerator() > u32::MAX.into() || msg.signing_threshold.denominator() == Uint64::zero() { + if msg.signing_threshold.numerator() > u32::MAX.into() + || msg.signing_threshold.denominator() == Uint64::zero() + { return Err(ContractError::InvalidSigningThreshold.into()); } @@ -104,7 +120,10 @@ pub fn require_governance(deps: &DepsMut, info: MessageInfo) -> Result<(), Contr } } -pub fn require_permissioned_relayer(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { +pub fn require_permissioned_relayer( + deps: &DepsMut, + info: MessageInfo, +) -> Result<(), ContractError> { match CONFIG.load(deps.storage)?.relayer { governance if governance == info.sender => Ok(()), _ => Err(ContractError::Unauthorized), @@ -146,27 +165,49 @@ pub fn execute( let querier = Querier::new(deps.querier, config.clone()); let res = match msg { - ExecuteMsg::RegisterToken { denom, token , decimals } => { + ExecuteMsg::RegisterToken { + denom, + token, + decimals, + } => { require_admin(&deps, info.clone()) .or_else(|_| require_governance(&deps, info.clone()))?; register_token(deps.storage, denom, &token, decimals) - }, + } // TODO: coin should be info.funds ExecuteMsg::ConstructProof { message_id, coin } => { require_permissioned_relayer(&deps, info)?; - construct_payment_proof(deps.storage, &querier, env.contract.address, env.block.height, &config, message_id, &coin) - }, + construct_payment_proof( + deps.storage, + &querier, + env.contract.address, + env.block.height, + &config, + message_id, + &coin, + ) + } ExecuteMsg::UpdateWorkerSet {} => { - require_admin(&deps, info.clone()) - .or_else(|_| require_governance(&deps, info))?; + require_admin(&deps, info.clone()).or_else(|_| require_governance(&deps, info))?; construct_signer_list_set_proof(deps.storage, &querier, env, &config) - }, - ExecuteMsg::UpdateTxStatus { multisig_session_id, signer_public_keys, message_id, message_status } => { - update_tx_status(deps.storage, &querier, &config, &multisig_session_id, &signer_public_keys, &message_id, message_status) - }, + } + ExecuteMsg::UpdateTxStatus { + multisig_session_id, + signer_public_keys, + message_id, + message_status, + } => update_tx_status( + deps.storage, + &querier, + &config, + &multisig_session_id, + &signer_public_keys, + &message_id, + message_status, + ), ExecuteMsg::TicketCreate {} => { construct_ticket_create_proof(deps.storage, env.contract.address, &config) - }, + } ExecuteMsg::UpdateSigningThreshold { new_signing_threshold, } => { @@ -193,24 +234,30 @@ fn construct_payment_proof( } // Prevent creating a duplicate signing session before the previous one expires - if let Some(multisig_session_id) = MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, &message_id)? { + if let Some(multisig_session_id) = + MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, &message_id)? + { let multisig_session = querier.get_multisig_session(&Uint64::from(multisig_session_id))?; - if multisig_session.state == MultisigState::Pending && multisig_session.expires_at <= block_height { - return Err(ContractError::PaymentAlreadyHasActiveSigningSession(multisig_session_id)); + if multisig_session.state == MultisigState::Pending + && multisig_session.expires_at <= block_height + { + return Err(ContractError::PaymentAlreadyHasActiveSigningSession( + multisig_session_id, + )); } }; let message = querier.get_message(&message_id)?; let xrpl_payment_amount = if coin.denom == config.xrp_denom { - let drops = u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { reason: "overflow".to_string() })?; + let drops = + u64::try_from(coin.amount.u128()).map_err(|_| ContractError::InvalidAmount { + reason: "overflow".to_string(), + })?; XRPLPaymentAmount::Drops(drops) } else { let (xrpl_token, decimals) = TOKENS.load(storage, &coin.denom)?; // TODO: handle decimal precision conversion between CosmWasm Coin and XRPLToken - XRPLPaymentAmount::Token( - xrpl_token, - canonicalize_coin_amount(coin.amount, decimals)?, - ) + XRPLPaymentAmount::Token(xrpl_token, canonicalize_coin_amount(coin.amount, decimals)?) }; let tx_hash = xrpl_multisig::issue_payment( @@ -222,12 +269,7 @@ fn construct_payment_proof( )?; REPLY_MESSAGE_ID.save(storage, &message_id)?; - start_signing_session( - storage, - config, - tx_hash, - self_address - ) + start_signing_session(storage, config, tx_hash, self_address) } pub fn start_signing_session( @@ -248,7 +290,7 @@ pub fn start_signing_session( worker_set_id: cur_worker_set_id, chain_name: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), msg: tx_hash.into(), - sig_verifier: Some(self_address.into()) + sig_verifier: Some(self_address.into()), }; let wasm_msg = wasm_execute(&config.axelar_multisig, &start_sig_msg, vec![])?; @@ -263,33 +305,25 @@ fn construct_signer_list_set_proof( config: &Config, ) -> Result { if !CURRENT_WORKER_SET.exists(storage) { - return Err(ContractError::WorkerSetIsNotSet) + return Err(ContractError::WorkerSetIsNotSet); } - let new_worker_set = axelar_workers::get_active_worker_set(querier, config.signing_threshold, env.block.height)?; + let new_worker_set = + axelar_workers::get_active_worker_set(querier, config.signing_threshold, env.block.height)?; let cur_worker_set = CURRENT_WORKER_SET.load(storage)?; if !axelar_workers::should_update_worker_set( &new_worker_set.clone().into(), &cur_worker_set.clone().into(), usize::try_from(config.worker_set_diff_threshold).unwrap(), ) { - return Err(ContractError::WorkerSetUnchanged) + return Err(ContractError::WorkerSetUnchanged); } - let tx_hash = xrpl_multisig::issue_signer_list_set( - storage, - config, - cur_worker_set, - )?; + let tx_hash = xrpl_multisig::issue_signer_list_set(storage, config, cur_worker_set)?; NEXT_WORKER_SET.save(storage, &tx_hash, &new_worker_set)?; - start_signing_session( - storage, - config, - tx_hash, - env.contract.address - ) + start_signing_session(storage, config, tx_hash, env.contract.address) } fn construct_ticket_create_proof( @@ -302,18 +336,9 @@ fn construct_ticket_create_proof( return Err(ContractError::TicketCountThresholdNotReached); } - let tx_hash = xrpl_multisig::issue_ticket_create( - storage, - config, - ticket_count, - )?; + let tx_hash = xrpl_multisig::issue_ticket_create(storage, config, ticket_count)?; - let response = start_signing_session( - storage, - config, - tx_hash, - self_address - )?; + let response = start_signing_session(storage, config, tx_hash, self_address)?; Ok(response) } @@ -327,7 +352,8 @@ fn update_tx_status( message_id: &CrossChainId, status: VerificationStatus, ) -> Result { - let unsigned_tx_hash = MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; + let unsigned_tx_hash = + MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; let multisig_session = querier.get_multisig_session(multisig_session_id)?; @@ -338,16 +364,23 @@ fn update_tx_status( let message = Message { destination_chain: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), - source_address: Address::from_str(&config.xrpl_multisig.to_string()).map_err(|_| ContractError::InvalidAddress)?, - destination_address: Address::from_str(destination_str.as_ref()).map_err(|_| ContractError::InvalidAddress)?, + source_address: Address::from_str(&config.xrpl_multisig.to_string()) + .map_err(|_| ContractError::InvalidAddress)?, + destination_address: Address::from_str(destination_str.as_ref()) + .map_err(|_| ContractError::InvalidAddress)?, cc_id: message_id.clone(), payload_hash: [0; 32], }; - let xrpl_signers: Vec = multisig_session.signers + let xrpl_signers: Vec = multisig_session + .signers .iter() .filter(|(signer, _)| signer_public_keys.contains(&signer.pub_key)) - .filter_map(|(signer, signature)| signature.as_ref().map(|signature| XRPLSigner::try_from((signer.clone(), signature.clone())))) + .filter_map(|(signer, signature)| { + signature + .as_ref() + .map(|signature| XRPLSigner::try_from((signer.clone(), signature.clone()))) + }) .collect::, ContractError>>()?; if xrpl_signers.len() != signer_public_keys.len() { @@ -358,13 +391,18 @@ fn update_tx_status( let tx_blob = HexBinary::from(signed_tx.xrpl_serialize()?); let tx_hash: HexBinary = xrpl_multisig::compute_signed_tx_hash(tx_blob.as_slice())?.into(); - if parse_message_id(&message_id.id).map_err(|_| ContractError::InvalidMessageID(message_id.id.to_string()))?.0.to_string() != tx_hash.to_string() { + if parse_message_id(&message_id.id) + .map_err(|_| ContractError::InvalidMessageID(message_id.id.to_string()))? + .0 + .to_string() + != tx_hash.to_string() + { return Err(ContractError::InvalidMessageID(message_id.id.to_string())); } let actual_status = querier.get_message_status(message)?; if status != actual_status { - return Err(ContractError::InvalidMessageStatus) + return Err(ContractError::InvalidMessageStatus); } xrpl_multisig::update_tx_status(storage, config, unsigned_tx_hash, status.into()) @@ -390,7 +428,11 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GetProof { multisig_session_id, - } => to_json_binary(&query::get_proof(deps.storage, querier, &multisig_session_id)?), + } => to_json_binary(&query::get_proof( + deps.storage, + querier, + &multisig_session_id, + )?), QueryMsg::VerifySignature { session_id, message: _, @@ -401,12 +443,13 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { deps.storage, &session_id, &PublicKey::Ecdsa(public_key), - &multisig::key::Signature::try_from( - (multisig::key::KeyType::Ecdsa, signature) - ).map_err(|_| ContractError::InvalidSignature)?)? - ), + &multisig::key::Signature::try_from((multisig::key::KeyType::Ecdsa, signature)) + .map_err(|_| ContractError::InvalidSignature)?, + )?), QueryMsg::GetWorkerSet {} => to_json_binary(&query::get_worker_set(deps.storage)?), - QueryMsg::GetMultisigSessionId { message_id } => to_json_binary(&query::get_multisig_session_id(deps.storage, &message_id)?), // TODO: rename + QueryMsg::GetMultisigSessionId { message_id } => { + to_json_binary(&query::get_multisig_session_id(deps.storage, &message_id)?) + } // TODO: rename } } diff --git a/contracts/xrpl-multisig-prover/src/events.rs b/contracts/xrpl-multisig-prover/src/events.rs index 551130b11..2fb6474ca 100644 --- a/contracts/xrpl-multisig-prover/src/events.rs +++ b/contracts/xrpl-multisig-prover/src/events.rs @@ -50,8 +50,7 @@ impl From for cosmwasm_std::Event { } => cosmwasm_std::Event::new("proof_under_construction") .add_attribute( "tx_hash", - to_string(&tx_hash) - .expect("violated invariant: tx_hash is not serializable"), + to_string(&tx_hash).expect("violated invariant: tx_hash is not serializable"), ) .add_attribute( "multisig_session_id", diff --git a/contracts/xrpl-multisig-prover/src/lib.rs b/contracts/xrpl-multisig-prover/src/lib.rs index cc40eed3d..66fde0ce3 100644 --- a/contracts/xrpl-multisig-prover/src/lib.rs +++ b/contracts/xrpl-multisig-prover/src/lib.rs @@ -1,12 +1,12 @@ +pub mod axelar_workers; pub mod contract; -pub mod state; pub mod error; -pub mod reply; pub mod events; -pub mod types; +pub mod msg; +pub mod querier; pub mod query; +pub mod reply; +pub mod state; +pub mod types; pub mod xrpl_multisig; -pub mod axelar_workers; -pub mod querier; -pub mod msg; -pub mod xrpl_serialize; \ No newline at end of file +pub mod xrpl_serialize; diff --git a/contracts/xrpl-multisig-prover/src/msg.rs b/contracts/xrpl-multisig-prover/src/msg.rs index e5deb61b4..c096d437d 100644 --- a/contracts/xrpl-multisig-prover/src/msg.rs +++ b/contracts/xrpl-multisig-prover/src/msg.rs @@ -1,7 +1,7 @@ +use axelar_wasm_std::{MajorityThreshold, VerificationStatus}; use connection_router_api::CrossChainId; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{HexBinary, Uint64}; -use axelar_wasm_std::{MajorityThreshold, VerificationStatus}; use multisig::key::PublicKey; use crate::types::{TxHash, XRPLToken}; @@ -53,15 +53,27 @@ pub enum QueryMsg { #[cw_serde] #[serde(tag = "status")] pub enum GetProofResponse { - Completed { unsigned_tx_hash: TxHash, tx_blob: HexBinary}, - Pending { unsigned_tx_hash: TxHash }, + Completed { + unsigned_tx_hash: TxHash, + tx_blob: HexBinary, + }, + Pending { + unsigned_tx_hash: TxHash, + }, } #[cw_serde] pub enum ExecuteMsg { - RegisterToken { denom: String, token: XRPLToken, decimals: u8 }, + RegisterToken { + denom: String, + token: XRPLToken, + decimals: u8, + }, // TODO: remove coin parameter - ConstructProof { message_id: CrossChainId, coin: cosmwasm_std::Coin }, + ConstructProof { + message_id: CrossChainId, + coin: cosmwasm_std::Coin, + }, UpdateTxStatus { multisig_session_id: Uint64, signer_public_keys: Vec, diff --git a/contracts/xrpl-multisig-prover/src/querier.rs b/contracts/xrpl-multisig-prover/src/querier.rs index 970afe784..7e73ef03a 100644 --- a/contracts/xrpl-multisig-prover/src/querier.rs +++ b/contracts/xrpl-multisig-prover/src/querier.rs @@ -1,29 +1,33 @@ use std::str::FromStr; +use axelar_wasm_std::VerificationStatus; use connection_router_api::{ChainName, CrossChainId, Message}; #[cfg(not(feature = "library"))] use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; -use cosmwasm_std::{ - to_json_binary, QueryRequest, WasmQuery, QuerierWrapper, Uint64, -}; +use cosmwasm_std::{to_json_binary, QuerierWrapper, QueryRequest, Uint64, WasmQuery}; use multisig::{key::PublicKey, msg::Multisig}; -use axelar_wasm_std::VerificationStatus; -use crate::{ - error::ContractError, - state::Config, -}; +use crate::{error::ContractError, state::Config}; use service_registry::state::WeightedWorker; pub const XRPL_CHAIN_NAME: &str = "XRPL"; -fn query(querier: QuerierWrapper, contract_addr: String, query_msg: &T) -> Result -where U: DeserializeOwned, T: Serialize + ?Sized { - querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr, - msg: to_json_binary(&query_msg)?, - })).map_err(ContractError::from) +fn query( + querier: QuerierWrapper, + contract_addr: String, + query_msg: &T, +) -> Result +where + U: DeserializeOwned, + T: Serialize + ?Sized, +{ + querier + .query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr, + msg: to_json_binary(&query_msg)?, + })) + .map_err(ContractError::from) } pub struct Querier<'a> { @@ -33,14 +37,13 @@ pub struct Querier<'a> { impl<'a> Querier<'a> { pub fn new(querier: QuerierWrapper<'a>, config: Config) -> Self { - Self { - querier, - config, - } + Self { querier, config } } pub fn get_active_workers(&self) -> Result, ContractError> { - query(self.querier, self.config.service_registry.to_string(), + query( + self.querier, + self.config.service_registry.to_string(), &service_registry::msg::QueryMsg::GetActiveWorkers { service_name: self.config.service_name.clone(), chain_name: ChainName::from_str(XRPL_CHAIN_NAME).unwrap(), @@ -49,7 +52,9 @@ impl<'a> Querier<'a> { } pub fn get_public_key(&self, worker_address: String) -> Result { - query(self.querier, self.config.axelar_multisig.to_string(), + query( + self.querier, + self.config.axelar_multisig.to_string(), &multisig::msg::QueryMsg::GetPublicKey { worker_address, key_type: self.config.key_type, @@ -58,28 +63,47 @@ impl<'a> Querier<'a> { } pub fn get_message(&self, message_id: &CrossChainId) -> Result { - let messages: Vec = query(self.querier, self.config.gateway.to_string(), + let messages: Vec = query( + self.querier, + self.config.gateway.to_string(), &gateway_api::msg::QueryMsg::GetOutgoingMessages { message_ids: vec![message_id.clone()], - } + }, )?; - messages.first().cloned().ok_or(ContractError::InvalidMessageID(message_id.id.to_string())) + messages + .first() + .cloned() + .ok_or(ContractError::InvalidMessageID(message_id.id.to_string())) } - pub fn get_message_status(&self, message: Message) -> Result { - let statuses: Vec<(CrossChainId, VerificationStatus)> = query(self.querier, self.config.voting_verifier.to_string(), + pub fn get_message_status( + &self, + message: Message, + ) -> Result { + let statuses: Vec<(CrossChainId, VerificationStatus)> = query( + self.querier, + self.config.voting_verifier.to_string(), &voting_verifier::msg::QueryMsg::GetMessagesStatus { messages: vec![message], - } + }, )?; - let status = statuses.first().ok_or(ContractError::GenericError("failed fetching message status".to_owned()))?; + let status = statuses.first().ok_or(ContractError::GenericError( + "failed fetching message status".to_owned(), + ))?; Ok(status.1) } - pub fn get_multisig_session(&self, multisig_session_id: &Uint64) -> Result { + pub fn get_multisig_session( + &self, + multisig_session_id: &Uint64, + ) -> Result { let query_msg = multisig::msg::QueryMsg::GetMultisig { session_id: *multisig_session_id, }; - query(self.querier, self.config.axelar_multisig.to_string(), &query_msg) + query( + self.querier, + self.config.axelar_multisig.to_string(), + &query_msg, + ) } } diff --git a/contracts/xrpl-multisig-prover/src/query.rs b/contracts/xrpl-multisig-prover/src/query.rs index eac1f579d..6bea1c02e 100644 --- a/contracts/xrpl-multisig-prover/src/query.rs +++ b/contracts/xrpl-multisig-prover/src/query.rs @@ -1,18 +1,28 @@ use connection_router_api::CrossChainId; -use cosmwasm_std::{StdResult, Uint64, HexBinary, Storage}; +use cosmwasm_std::{HexBinary, StdResult, Storage, Uint64}; -use multisig::{key::Signature, types::MultisigState}; use multisig::key::PublicKey; +use multisig::{key::Signature, types::MultisigState}; use crate::state::{AVAILABLE_TICKETS, MESSAGE_ID_TO_MULTISIG_SESSION_ID, MESSAGE_ID_TO_TICKET}; use crate::{ + error::ContractError, + msg::GetProofResponse, + querier::Querier, + state::{CURRENT_WORKER_SET, MULTISIG_SESSION_ID_TO_TX_HASH, TRANSACTION_INFO}, + types::TransactionStatus, types::*, - state::{MULTISIG_SESSION_ID_TO_TX_HASH, TRANSACTION_INFO, CURRENT_WORKER_SET}, xrpl_multisig::{self, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, querier::Querier, msg::GetProofResponse, types::TransactionStatus, error::ContractError, - xrpl_serialize::XRPLSerialize + xrpl_multisig::{self, HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING}, + xrpl_serialize::XRPLSerialize, }; -pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, signer_xrpl_address: &XRPLAccountId) -> StdResult { - let unsigned_tx_hash = MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; +pub fn get_message_to_sign( + storage: &dyn Storage, + multisig_session_id: &Uint64, + signer_xrpl_address: &XRPLAccountId, +) -> StdResult { + let unsigned_tx_hash = + MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; if tx_info.status != TransactionStatus::Pending { @@ -25,32 +35,48 @@ pub fn get_message_to_sign(storage: &dyn Storage, multisig_session_id: &Uint64, Ok(xrpl_multisig::xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, tx_blob.as_slice()).into()) } -pub fn verify_signature(storage: &dyn Storage, multisig_session_id: &Uint64, public_key: &PublicKey, signature: &Signature) -> StdResult { +pub fn verify_signature( + storage: &dyn Storage, + multisig_session_id: &Uint64, + public_key: &PublicKey, + signature: &Signature, +) -> StdResult { let signer_xrpl_address = XRPLAccountId::from(public_key); let tx_hash = get_message_to_sign(storage, multisig_session_id, &signer_xrpl_address)?; // m.tx_hash is going to be over 32 bytes due to inclusion of the signer address, so it has to be passed unchecked - Ok(signature.verify(multisig::types::MsgToSign::unchecked(tx_hash), public_key).is_ok()) + Ok(signature + .verify(multisig::types::MsgToSign::unchecked(tx_hash), public_key) + .is_ok()) } -pub fn get_proof(storage: &dyn Storage, querier: Querier, multisig_session_id: &Uint64) -> StdResult { - let unsigned_tx_hash = MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; +pub fn get_proof( + storage: &dyn Storage, + querier: Querier, + multisig_session_id: &Uint64, +) -> StdResult { + let unsigned_tx_hash = + MULTISIG_SESSION_ID_TO_TX_HASH.load(storage, multisig_session_id.u64())?; let tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; - let multisig_session= querier.get_multisig_session(multisig_session_id)?; + let multisig_session = querier.get_multisig_session(multisig_session_id)?; let response = match multisig_session.state { MultisigState::Pending => GetProofResponse::Pending { unsigned_tx_hash }, MultisigState::Completed { .. } => { - let xrpl_signers: Vec = multisig_session.signers + let xrpl_signers: Vec = multisig_session + .signers .into_iter() .filter_map(|(signer, sig)| sig.map(|sig| (signer, sig))) .map(XRPLSigner::try_from) .collect::, ContractError>>()?; let signed_tx = XRPLSignedTransaction::new(tx_info.unsigned_contents, xrpl_signers); let tx_blob: HexBinary = HexBinary::from(signed_tx.xrpl_serialize()?); - GetProofResponse::Completed { unsigned_tx_hash, tx_blob } + GetProofResponse::Completed { + unsigned_tx_hash, + tx_blob, + } } }; @@ -61,14 +87,20 @@ pub fn get_worker_set(storage: &dyn Storage) -> StdResult StdResult> { +pub fn get_multisig_session_id( + storage: &dyn Storage, + message_id: &CrossChainId, +) -> StdResult> { let existing_ticket_number = MESSAGE_ID_TO_TICKET.may_load(storage, message_id)?; let available_tickets = AVAILABLE_TICKETS.may_load(storage)?; if existing_ticket_number.is_none() || available_tickets.is_none() { return Ok(None); } - if available_tickets.unwrap().contains(&existing_ticket_number.unwrap()) { + if available_tickets + .unwrap() + .contains(&existing_ticket_number.unwrap()) + { return MESSAGE_ID_TO_MULTISIG_SESSION_ID.may_load(storage, message_id); } diff --git a/contracts/xrpl-multisig-prover/src/reply.rs b/contracts/xrpl-multisig-prover/src/reply.rs index 1fd4dc441..f1eb44b4f 100644 --- a/contracts/xrpl-multisig-prover/src/reply.rs +++ b/contracts/xrpl-multisig-prover/src/reply.rs @@ -2,7 +2,14 @@ use cosmwasm_std::{from_json, Attribute, DepsMut, HexBinary, Reply, Response, Ui use cw_utils::{parse_reply_execute_data, MsgExecuteContractResponse}; use crate::{ - error::ContractError, events::Event, state::{MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_ID_TO_TX_HASH, REPLY_MESSAGE_ID, REPLY_TX_HASH, TRANSACTION_INFO}, types::XRPLUnsignedTx, xrpl_serialize::XRPLSerialize + error::ContractError, + events::Event, + state::{ + MESSAGE_ID_TO_MULTISIG_SESSION_ID, MULTISIG_SESSION_ID_TO_TX_HASH, REPLY_MESSAGE_ID, + REPLY_TX_HASH, TRANSACTION_INFO, + }, + types::XRPLUnsignedTx, + xrpl_serialize::XRPLSerialize, }; pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result { @@ -31,14 +38,17 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result panic!("No reply message ID found for Payment"), + } + None if matches!(tx_info.unsigned_contents, XRPLUnsignedTx::Payment(_)) => { + panic!("No reply message ID found for Payment") + } None => (), } let res = reply.result.unwrap(); - let evt_attributes: Vec = res.events + let evt_attributes: Vec = res + .events .into_iter() .filter(|e| e.ty == "wasm-signing_started") .flat_map(|e| e.attributes) @@ -47,15 +57,20 @@ pub fn start_multisig_reply(deps: DepsMut, reply: Reply) -> Result Err(ContractError::InvalidContractReply { reason: "no data".to_string(), diff --git a/contracts/xrpl-multisig-prover/src/state.rs b/contracts/xrpl-multisig-prover/src/state.rs index 0865c1439..e01c8f80c 100644 --- a/contracts/xrpl-multisig-prover/src/state.rs +++ b/contracts/xrpl-multisig-prover/src/state.rs @@ -1,9 +1,12 @@ +use crate::{ + axelar_workers::WorkerSet, + types::{TransactionInfo, TxHash, XRPLToken}, +}; use axelar_wasm_std::MajorityThreshold; use connection_router_api::CrossChainId; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; use cw_storage_plus::{Item, Map}; -use cosmwasm_schema::cw_serde; -use crate::{types::{TransactionInfo, TxHash, XRPLToken}, axelar_workers::WorkerSet}; #[cw_serde] pub struct Config { @@ -40,7 +43,8 @@ pub const NEXT_SEQUENCE_NUMBER: Item = Item::new("next_sequence_number"); pub const LAST_ASSIGNED_TICKET_NUMBER: Item = Item::new("last_assigned_ticket_number"); pub const MESSAGE_ID_TO_TICKET: Map<&CrossChainId, u32> = Map::new("message_id_to_ticket"); -pub const MESSAGE_ID_TO_MULTISIG_SESSION_ID: Map<&CrossChainId, u64> = Map::new("message_id_to_multisig_session_id"); +pub const MESSAGE_ID_TO_MULTISIG_SESSION_ID: Map<&CrossChainId, u64> = + Map::new("message_id_to_multisig_session_id"); pub const CONFIRMED_TRANSACTIONS: Map<&u32, TxHash> = Map::new("confirmed_transactions"); pub const AVAILABLE_TICKETS: Item> = Item::new("available_tickets"); pub const TRANSACTION_INFO: Map<&TxHash, TransactionInfo> = Map::new("transaction_info"); diff --git a/contracts/xrpl-multisig-prover/src/types.rs b/contracts/xrpl-multisig-prover/src/types.rs index a23e806ec..a6cc4be5f 100644 --- a/contracts/xrpl-multisig-prover/src/types.rs +++ b/contracts/xrpl-multisig-prover/src/types.rs @@ -4,18 +4,18 @@ use std::fmt::Display; use axelar_wasm_std::VerificationStatus; use connection_router_api::CrossChainId; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{from_json, HexBinary, Binary, StdResult, Uint256, Uint128}; +use cosmwasm_std::{from_json, Binary, HexBinary, StdResult, Uint128, Uint256}; use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey}; use k256::ecdsa; use k256::schnorr::signature::SignatureEncoding; -use multisig::key::Signature; use multisig::key::PublicKey; +use multisig::key::Signature; use ripemd::Ripemd160; use sha2::{Digest, Sha256}; -use voting_verifier::events::parse_message_id; use crate::axelar_workers::AxelarSigner; use crate::error::ContractError; +use voting_verifier::events::parse_message_id; #[cw_serde] pub enum TransactionStatus { @@ -31,8 +31,11 @@ pub struct TxHash(pub HexBinary); impl TryFrom for TxHash { type Error = ContractError; fn try_from(cc_id: CrossChainId) -> Result { - let (tx_id, _event_index) = parse_message_id(&cc_id.id).map_err(|_e| ContractError::InvalidMessageID(cc_id.id.to_string()))?; - Ok(Self(HexBinary::from_hex(tx_id.to_ascii_lowercase().as_str())?)) + let (tx_id, _event_index) = parse_message_id(&cc_id.id) + .map_err(|_e| ContractError::InvalidMessageID(cc_id.id.to_string()))?; + Ok(Self(HexBinary::from_hex( + tx_id.to_ascii_lowercase().as_str(), + )?)) } } @@ -160,15 +163,9 @@ pub enum XRPLUnsignedTx { impl XRPLUnsignedTx { pub fn sequence(&self) -> &XRPLSequence { match self { - XRPLUnsignedTx::Payment(tx) => { - &tx.sequence - }, - XRPLUnsignedTx::TicketCreate(tx) => { - &tx.sequence - }, - XRPLUnsignedTx::SignerListSet(tx) => { - &tx.sequence - } + XRPLUnsignedTx::Payment(tx) => &tx.sequence, + XRPLUnsignedTx::TicketCreate(tx) => &tx.sequence, + XRPLUnsignedTx::SignerListSet(tx) => &tx.sequence, } } pub fn sequence_number_increment(&self, status: TransactionStatus) -> u32 { @@ -177,25 +174,18 @@ impl XRPLUnsignedTx { } match self { - XRPLUnsignedTx::Payment(tx ) => { - match tx.sequence { - XRPLSequence::Plain(_) => 1, - XRPLSequence::Ticket(_) => 0, - } - } - XRPLUnsignedTx::SignerListSet(tx) => { - match tx.sequence { - XRPLSequence::Plain(_) => 1, - XRPLSequence::Ticket(_) => 0, - } + XRPLUnsignedTx::Payment(tx) => match tx.sequence { + XRPLSequence::Plain(_) => 1, + XRPLSequence::Ticket(_) => 0, + }, + XRPLUnsignedTx::SignerListSet(tx) => match tx.sequence { + XRPLSequence::Plain(_) => 1, + XRPLSequence::Ticket(_) => 0, }, - XRPLUnsignedTx::TicketCreate(tx) => { - match status { - TransactionStatus::Succeeded => tx.ticket_count + 1, - TransactionStatus::FailedOnChain => 1, - TransactionStatus::Inconclusive | - TransactionStatus::Pending => unreachable!(), - } + XRPLUnsignedTx::TicketCreate(tx) => match status { + TransactionStatus::Succeeded => tx.ticket_count + 1, + TransactionStatus::FailedOnChain => 1, + TransactionStatus::Inconclusive | TransactionStatus::Pending => unreachable!(), }, } } @@ -256,7 +246,7 @@ impl Display for XRPLAccountId { .with_alphabet(bs58::Alphabet::RIPPLE) .into_string(); - write!(f, "{}", str) + write!(f, "{}", str) } } @@ -277,7 +267,10 @@ impl std::str::FromStr for XRPLAccountId { type Err = ContractError; fn from_str(address: &str) -> Result { - let res = bs58::decode(address).with_alphabet(bs58::Alphabet::RIPPLE).into_vec().map_err(|_| ContractError::InvalidAddress)?; + let res = bs58::decode(address) + .with_alphabet(bs58::Alphabet::RIPPLE) + .into_vec() + .map_err(|_| ContractError::InvalidAddress)?; // .map_err(|_| ContractError::InvalidAddress)?; if res.len() != 25 { return Err(ContractError::InvalidAddress); @@ -298,13 +291,19 @@ pub struct XRPLSigner { impl TryFrom<(multisig::msg::Signer, multisig::key::Signature)> for XRPLSigner { type Error = ContractError; - fn try_from((axelar_signer, signature): (multisig::msg::Signer, multisig::key::Signature)) -> Result { + fn try_from( + (axelar_signer, signature): (multisig::msg::Signer, multisig::key::Signature), + ) -> Result { let txn_signature = match signature { - multisig::key::Signature::Ecdsa(_) | - multisig::key::Signature::EcdsaRecoverable(_) => HexBinary::from(ecdsa::Signature::to_der( - &ecdsa::Signature::try_from(signature.clone().as_ref()) - .map_err(|_| ContractError::FailedToEncodeSignature)? - ).to_vec()), + multisig::key::Signature::Ecdsa(_) | multisig::key::Signature::EcdsaRecoverable(_) => { + HexBinary::from( + ecdsa::Signature::to_der( + &ecdsa::Signature::try_from(signature.clone().as_ref()) + .map_err(|_| ContractError::FailedToEncodeSignature)?, + ) + .to_vec(), + ) + } _ => unimplemented!("Unsupported signature type"), }; @@ -319,12 +318,15 @@ impl TryFrom<(multisig::msg::Signer, multisig::key::Signature)> for XRPLSigner { #[cw_serde] pub struct XRPLSignedTransaction { pub unsigned_tx: XRPLUnsignedTx, - pub signers: Vec + pub signers: Vec, } impl XRPLSignedTransaction { pub fn new(unsigned_tx: XRPLUnsignedTx, signers: Vec) -> Self { - Self { unsigned_tx, signers } + Self { + unsigned_tx, + signers, + } } } @@ -345,7 +347,8 @@ impl Display for XRPLCurrency { } } -const ALLOWED_CURRENCY_CHARS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!@#$%^&*<>(){}[]|"; +const ALLOWED_CURRENCY_CHARS: &str = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?!@#$%^&*<>(){}[]|"; impl TryFrom for XRPLCurrency { type Error = ContractError; @@ -369,12 +372,16 @@ pub const MAX_EXPONENT: i64 = 80; #[cw_serde] pub struct XRPLTokenAmount { mantissa: u64, - exponent: i64 + exponent: i64, } impl XRPLTokenAmount { pub fn new(mantissa: u64, exponent: i64) -> Self { - assert!(mantissa == 0 || ((MIN_MANTISSA..=MAX_MANTISSA).contains(&mantissa) && (MIN_EXPONENT..=MAX_EXPONENT).contains(&exponent))); + assert!( + mantissa == 0 + || ((MIN_MANTISSA..=MAX_MANTISSA).contains(&mantissa) + && (MIN_EXPONENT..=MAX_EXPONENT).contains(&exponent)) + ); Self { mantissa, exponent } } @@ -383,19 +390,26 @@ impl XRPLTokenAmount { 0x8000000000000000u64.to_be_bytes() } else { // not xrp-bit | positive bit | 8 bits exponent | 54 bits mantissa - (0xC000000000000000u64 | ((self.exponent + 97) as u64) << 54 | self.mantissa).to_be_bytes() + (0xC000000000000000u64 | ((self.exponent + 97) as u64) << 54 | self.mantissa) + .to_be_bytes() } } } -pub fn canonicalize_coin_amount(amount: Uint128, decimals: u8) -> Result{ +pub fn canonicalize_coin_amount( + amount: Uint128, + decimals: u8, +) -> Result { let (mantissa, exponent) = canonicalize_mantissa(amount, -i64::from(decimals))?; Ok(XRPLTokenAmount::new(mantissa, exponent)) } // always called when XRPLTokenAmount instantiated // see https://github.com/XRPLF/xrpl-dev-portal/blob/82da0e53a8d6cdf2b94a80594541d868b4d03b94/content/_code-samples/tx-serialization/py/xrpl_num.py#L19 -pub fn canonicalize_mantissa(mut mantissa: Uint128, mut exponent: i64) -> Result<(u64, i64), ContractError> { +pub fn canonicalize_mantissa( + mut mantissa: Uint128, + mut exponent: i64, +) -> Result<(u64, i64), ContractError> { let ten = Uint128::from(10u128); while mantissa < MIN_MANTISSA.into() && exponent > MIN_EXPONENT { @@ -405,7 +419,9 @@ pub fn canonicalize_mantissa(mut mantissa: Uint128, mut exponent: i64) -> Result while mantissa > MAX_MANTISSA.into() && exponent > MIN_EXPONENT { if exponent > MAX_EXPONENT { - return Err(ContractError::InvalidAmount { reason: "overflow".to_string() }); + return Err(ContractError::InvalidAmount { + reason: "overflow".to_string(), + }); } mantissa /= ten; exponent += 1; @@ -416,7 +432,9 @@ pub fn canonicalize_mantissa(mut mantissa: Uint128, mut exponent: i64) -> Result } if exponent > MAX_EXPONENT || mantissa > MAX_MANTISSA.into() { - return Err(ContractError::InvalidAmount { reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string() }); + return Err(ContractError::InvalidAmount { + reason: format!("overflow exponent {} mantissa {}", exponent, mantissa).to_string(), + }); } let mantissa = u64::from_be_bytes(mantissa.to_be_bytes()[8..].try_into().unwrap()); diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 4434c9f97..794f3e95b 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -1,11 +1,18 @@ -use std::str::FromStr; use axelar_wasm_std::nonempty; use connection_router_api::CrossChainId; use cosmwasm_std::{wasm_execute, HexBinary, Response, Storage}; -use sha2::{Sha512, Digest, Sha256}; +use sha2::{Digest, Sha256, Sha512}; +use std::str::FromStr; use crate::{ - axelar_workers::WorkerSet, error::ContractError, state::{Config, AVAILABLE_TICKETS, CONFIRMED_TRANSACTIONS, CURRENT_WORKER_SET, LAST_ASSIGNED_TICKET_NUMBER, LATEST_SEQUENTIAL_TX_HASH, MESSAGE_ID_TO_TICKET, NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, TRANSACTION_INFO}, types::* + axelar_workers::WorkerSet, + error::ContractError, + state::{ + Config, AVAILABLE_TICKETS, CONFIRMED_TRANSACTIONS, CURRENT_WORKER_SET, + LAST_ASSIGNED_TICKET_NUMBER, LATEST_SEQUENTIAL_TX_HASH, MESSAGE_ID_TO_TICKET, + NEXT_SEQUENCE_NUMBER, NEXT_WORKER_SET, TRANSACTION_INFO, + }, + types::*, }; fn issue_tx( @@ -22,16 +29,16 @@ fn issue_tx( status: TransactionStatus::Pending, unsigned_contents: tx.clone(), original_message_id: message_id, - } + }, )?; match tx.sequence() { XRPLSequence::Ticket(ticket_number) => { LAST_ASSIGNED_TICKET_NUMBER.save(storage, ticket_number)?; - }, + } XRPLSequence::Plain(_) => { LATEST_SEQUENTIAL_TX_HASH.save(storage, &tx_hash)?; - }, + } }; Ok(tx_hash) @@ -42,7 +49,7 @@ pub fn issue_payment( config: &Config, destination: nonempty::String, amount: &XRPLPaymentAmount, - message_id: &CrossChainId + message_id: &CrossChainId, ) -> Result { let ticket_number = assign_ticket_number(storage, message_id)?; @@ -51,7 +58,7 @@ pub fn issue_payment( fee: config.xrpl_fee, sequence: XRPLSequence::Ticket(ticket_number), amount: amount.clone(), - destination: XRPLAccountId::from_str(destination.as_str())? + destination: XRPLAccountId::from_str(destination.as_str())?, }; issue_tx( @@ -64,7 +71,7 @@ pub fn issue_payment( pub fn issue_ticket_create( storage: &mut dyn Storage, config: &Config, - ticket_count: u32 + ticket_count: u32, ) -> Result { let sequence_number = get_next_sequence_number(storage)?; @@ -75,17 +82,13 @@ pub fn issue_ticket_create( ticket_count, }; - issue_tx( - storage, - XRPLUnsignedTx::TicketCreate(tx), - None, - ) + issue_tx(storage, XRPLUnsignedTx::TicketCreate(tx), None) } pub fn issue_signer_list_set( storage: &mut dyn Storage, config: &Config, - workers: WorkerSet + workers: WorkerSet, ) -> Result { let sequence_number = get_next_sequence_number(storage)?; @@ -94,21 +97,21 @@ pub fn issue_signer_list_set( fee: config.xrpl_fee, sequence: XRPLSequence::Plain(sequence_number), signer_quorum: workers.quorum, - signer_entries: workers.signers.into_iter().map(XRPLSignerEntry::from).collect(), + signer_entries: workers + .signers + .into_iter() + .map(XRPLSignerEntry::from) + .collect(), }; - issue_tx( - storage, - XRPLUnsignedTx::SignerListSet(tx), - None, - ) + issue_tx(storage, XRPLUnsignedTx::SignerListSet(tx), None) } pub fn update_tx_status( storage: &mut dyn Storage, config: &Config, unsigned_tx_hash: TxHash, - new_status: TransactionStatus + new_status: TransactionStatus, ) -> Result { let mut tx_info = TRANSACTION_INFO.load(storage, &unsigned_tx_hash)?; if tx_info.status != TransactionStatus::Pending { @@ -119,12 +122,15 @@ pub fn update_tx_status( let tx_sequence_number: u32 = tx_info.unsigned_contents.sequence().clone().into(); - let sequence_number_increment = tx_info.unsigned_contents.sequence_number_increment(new_status.clone()); + let sequence_number_increment = tx_info + .unsigned_contents + .sequence_number_increment(new_status.clone()); if sequence_number_increment > 0 && tx_sequence_number == NEXT_SEQUENCE_NUMBER.load(storage)? { NEXT_SEQUENCE_NUMBER.save(storage, &(tx_sequence_number + sequence_number_increment))?; } - if new_status == TransactionStatus::Succeeded || new_status == TransactionStatus::FailedOnChain { + if new_status == TransactionStatus::Succeeded || new_status == TransactionStatus::FailedOnChain + { CONFIRMED_TRANSACTIONS.save(storage, &tx_sequence_number, &unsigned_tx_hash)?; mark_ticket_unavailable(storage, tx_sequence_number)?; } @@ -142,7 +148,7 @@ pub fn update_tx_status( (tx_sequence_number + 1)..(tx_sequence_number + tx.ticket_count), )?; Response::default() - }, + } XRPLUnsignedTx::SignerListSet(_tx) => { let next_worker_set = NEXT_WORKER_SET.load(storage, &unsigned_tx_hash)?; CURRENT_WORKER_SET.save(storage, &next_worker_set)?; @@ -163,8 +169,8 @@ pub fn update_tx_status( }, vec![], )?) - }, - XRPLUnsignedTx::Payment(_) => Response::default() + } + XRPLUnsignedTx::Payment(_) => Response::default(), }) } @@ -173,7 +179,10 @@ pub fn update_tx_status( // A message ID can be ticketed a different ticket number // only if the previous ticket number has been consumed // by a TX that doesn't correspond to this message. -pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId) -> Result { +pub fn assign_ticket_number( + storage: &mut dyn Storage, + message_id: &CrossChainId, +) -> Result { // If this message ID has already been ticketed, // then use the same ticket number as before, if let Some(ticket_number) = MESSAGE_ID_TO_TICKET.may_load(storage, message_id)? { @@ -181,7 +190,8 @@ pub fn assign_ticket_number(storage: &mut dyn Storage, message_id: &CrossChainId // as long as it has not already been consumed if confirmed_tx_hash.is_none() // or if it has been consumed by the same message. - || TRANSACTION_INFO.load(storage, &confirmed_tx_hash.unwrap())?.original_message_id.as_ref() == Some(message_id) { + || TRANSACTION_INFO.load(storage, &confirmed_tx_hash.unwrap())?.original_message_id.as_ref() == Some(message_id) + { return Ok(ticket_number); } } @@ -203,23 +213,33 @@ pub fn get_next_ticket_number(storage: &dyn Storage) -> Result &last_assigned_ticket_number).unwrap_or(&available_tickets[0]); + let ticket_number = available_tickets + .iter() + .find(|&x| x > &last_assigned_ticket_number) + .unwrap_or(&available_tickets[0]); Ok(*ticket_number) } pub fn tickets_available_to_request(storage: &mut dyn Storage) -> Result { let available_tickets = AVAILABLE_TICKETS.load(storage)?; - let available_ticket_count = u32::try_from(available_tickets.len()).map_err(|e| ContractError::GenericError(e.to_string()))?; + let available_ticket_count = u32::try_from(available_tickets.len()) + .map_err(|e| ContractError::GenericError(e.to_string()))?; assert!(available_ticket_count <= 250); Ok(250 - available_ticket_count) } fn get_next_sequence_number(storage: &dyn Storage) -> Result { match load_latest_sequential_tx_info(storage)? { - Some(latest_sequential_tx_info) if latest_sequential_tx_info.status == TransactionStatus::Pending => { - Ok(latest_sequential_tx_info.unsigned_contents.sequence().clone().into()) - }, - _ => NEXT_SEQUENCE_NUMBER.load(storage).map_err(|e| e.into()) + Some(latest_sequential_tx_info) + if latest_sequential_tx_info.status == TransactionStatus::Pending => + { + Ok(latest_sequential_tx_info + .unsigned_contents + .sequence() + .clone() + .into()) + } + _ => NEXT_SEQUENCE_NUMBER.load(storage).map_err(|e| e.into()), } } @@ -227,11 +247,16 @@ fn load_latest_sequential_tx_info( storage: &dyn Storage, ) -> Result, ContractError> { LATEST_SEQUENTIAL_TX_HASH - .may_load(storage)? - .map_or(Ok(None), |tx_hash| Ok(TRANSACTION_INFO.may_load(storage, &tx_hash)?)) + .may_load(storage)? + .map_or(Ok(None), |tx_hash| { + Ok(TRANSACTION_INFO.may_load(storage, &tx_hash)?) + }) } -fn mark_tickets_available(storage: &mut dyn Storage, tickets: impl Iterator) -> Result<(), ContractError> { +fn mark_tickets_available( + storage: &mut dyn Storage, + tickets: impl Iterator, +) -> Result<(), ContractError> { AVAILABLE_TICKETS.update(storage, |available_tickets| -> Result<_, ContractError> { let mut new_available_tickets = available_tickets.clone(); new_available_tickets.extend(tickets); @@ -255,10 +280,7 @@ fn mark_ticket_unavailable(storage: &mut dyn Storage, ticket: u32) -> Result<(), pub const HASH_PREFIX_SIGNED_TRANSACTION: [u8; 4] = [0x54, 0x58, 0x4E, 0x00]; pub const HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING: [u8; 4] = [0x53, 0x4D, 0x54, 0x00]; -pub fn xrpl_hash( - prefix: [u8; 4], - tx_blob: &[u8], -) -> [u8; 32] { +pub fn xrpl_hash(prefix: [u8; 4], tx_blob: &[u8]) -> [u8; 32] { let mut hasher = Sha512::new_with_prefix(prefix); hasher.update(tx_blob); let hash: [u8; 64] = hasher.finalize().into(); @@ -266,20 +288,30 @@ pub fn xrpl_hash( } pub fn compute_unsigned_tx_hash(unsigned_tx: &XRPLUnsignedTx) -> Result { - let encoded_unsigned_tx = serde_json::to_vec(unsigned_tx).map_err(|_| ContractError::FailedToSerialize)?; + let encoded_unsigned_tx = + serde_json::to_vec(unsigned_tx).map_err(|_| ContractError::FailedToSerialize)?; let d = Sha256::digest(encoded_unsigned_tx); Ok(TxHash(HexBinary::from(d.to_vec()))) } pub fn compute_signed_tx_hash(encoded_signed_tx: &[u8]) -> Result { - Ok(TxHash(HexBinary::from(xrpl_hash(HASH_PREFIX_SIGNED_TRANSACTION, encoded_signed_tx)))) + Ok(TxHash(HexBinary::from(xrpl_hash( + HASH_PREFIX_SIGNED_TRANSACTION, + encoded_signed_tx, + )))) } -pub fn message_to_sign(encoded_unsigned_tx: &HexBinary, signer_address: &XRPLAccountId) -> Result<[u8; 32], ContractError> { +pub fn message_to_sign( + encoded_unsigned_tx: &HexBinary, + signer_address: &XRPLAccountId, +) -> Result<[u8; 32], ContractError> { let mut msg = encoded_unsigned_tx.to_vec(); msg.extend_from_slice(&signer_address.to_bytes()); - Ok(xrpl_hash(HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, msg.as_slice())) + Ok(xrpl_hash( + HASH_PREFIX_UNSIGNED_TX_MULTI_SIGNING, + msg.as_slice(), + )) } #[cfg(test)] @@ -289,13 +321,27 @@ mod tests { #[test] fn test_account_id_to_bytes_address() { - assert_eq!("rrrrrrrrrrrrrrrrrrrrrhoLvTp", XRPLAccountId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).to_string()); - assert_eq!("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi", XRPLAccountId::from_bytes([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]).to_string()); + assert_eq!( + "rrrrrrrrrrrrrrrrrrrrrhoLvTp", + XRPLAccountId::from_bytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + .to_string() + ); + assert_eq!( + "rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi", + XRPLAccountId::from_bytes([ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255 + ]) + .to_string() + ); } #[test] fn ed25519_public_key_to_xrpl_address() -> Result<(), ContractError> { assert_eq!( - XRPLAccountId::from(&PublicKey::Ed25519(HexBinary::from_hex("ED9434799226374926EDA3B54B1B461B4ABF7237962EAE18528FEA67595397FA32")?)).to_string(), + XRPLAccountId::from(&PublicKey::Ed25519(HexBinary::from_hex( + "ED9434799226374926EDA3B54B1B461B4ABF7237962EAE18528FEA67595397FA32" + )?)) + .to_string(), "rDTXLQ7ZKZVKz33zJbHjgVShjsBnqMBhmN" ); Ok(()) @@ -304,9 +350,12 @@ mod tests { #[test] fn secp256k1_public_key_to_xrpl_address() -> Result<(), ContractError> { assert_eq!( - XRPLAccountId::from(&PublicKey::Ecdsa(HexBinary::from_hex("0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D")?)).to_string(), + XRPLAccountId::from(&PublicKey::Ecdsa(HexBinary::from_hex( + "0303E20EC6B4A39A629815AE02C0A1393B9225E3B890CAE45B59F42FA29BE9668D" + )?)) + .to_string(), "rnBFvgZphmN39GWzUJeUitaP22Fr9be75H" ); Ok(()) } -} \ No newline at end of file +} diff --git a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs index 7a7ca9586..118a12aa9 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_serialize.rs @@ -31,7 +31,7 @@ pub enum Field { Memo, TicketCount, Signers, - Signer + Signer, } impl Field { @@ -56,7 +56,7 @@ impl Field { Field::Memo => 10, Field::TicketCount => 40, Field::Signers => 3, - Field::Signer => 16 + Field::Signer => 16, } } } @@ -108,9 +108,11 @@ impl XRPLSerialize for XRPLPaymentAmount { if value <= 10u64.pow(17) { Ok((value | POSITIVE_BIT).to_be_bytes().to_vec()) } else { - Err(ContractError::InvalidAmount { reason: "more than maximum amount of drops".to_string() }) + Err(ContractError::InvalidAmount { + reason: "more than maximum amount of drops".to_string(), + }) } - }, + } XRPLPaymentAmount::Token(token, amount) => { let mut buf = Vec::with_capacity(48); buf.extend_from_slice(&amount.to_bytes()); @@ -143,12 +145,12 @@ pub fn encode_length(mut length: usize) -> Result, ContractError> { } else if length <= 12480 { length -= 193; Ok(vec![193 + (length >> 8) as u8, (length & 0xff) as u8]) - } else if length <= 918744 { + } else if length <= 918744 { length -= 12481; Ok(vec![ 241 + (length >> 16) as u8, ((length >> 8) & 0xff) as u8, - (length & 0xff) as u8 + (length & 0xff) as u8, ]) } else { Err(ContractError::InvalidBlob) @@ -161,7 +163,9 @@ impl XRPLSerialize for PublicKey { match self.clone() { // rippled prefixes Ed25519 public keys with the byte 0xED so both types of public key are 33 bytes. // https://xrpl.org/cryptographic-keys.html - Self::Ed25519(hex) => HexBinary::from_hex(format!("ED{}", hex.to_hex()).as_str())?.xrpl_serialize(), + Self::Ed25519(hex) => { + HexBinary::from_hex(format!("ED{}", hex.to_hex()).as_str())?.xrpl_serialize() + } Self::Ecdsa(hex) => hex.xrpl_serialize(), } } @@ -221,7 +225,14 @@ impl XRPLSerialize for XRPLAccountId { } pub fn hex_encode_session_id(session_id: Uint64) -> HexBinary { - HexBinary::from(session_id.to_be_bytes().iter().skip_while(|&&byte| byte == 0).cloned().collect::>()) + HexBinary::from( + session_id + .to_be_bytes() + .iter() + .skip_while(|&&byte| byte == 0) + .cloned() + .collect::>(), + ) } impl TryInto for XRPLPaymentTx { @@ -284,7 +295,7 @@ impl TryInto for XRPLUnsignedTx { match self { XRPLUnsignedTx::Payment(tx) => tx.try_into(), XRPLUnsignedTx::TicketCreate(tx) => tx.try_into(), - XRPLUnsignedTx::SignerListSet(tx) => tx.try_into() + XRPLUnsignedTx::SignerListSet(tx) => tx.try_into(), } } } @@ -300,14 +311,20 @@ impl TryInto for XRPLSignedTransaction { a.account.to_bytes().cmp(&b.account.to_bytes()) }); let mut obj: XRPLObject = self.unsigned_tx.clone().try_into()?; - obj.add_field(Field::Signers, XRPLArray{ field: Field::Signer, items: sorted_signers })?; + obj.add_field( + Field::Signers, + XRPLArray { + field: Field::Signer, + items: sorted_signers, + }, + )?; Ok(obj) } } struct XRPLArray { field: Field, - items: Vec + items: Vec, } impl XRPLSerialize for XRPLArray { @@ -329,12 +346,16 @@ impl XRPLSerialize for XRPLArray { struct SerializedField { type_code: u8, field_code: u8, - serialized_value: Vec + serialized_value: Vec, } impl SerializedField { fn new(type_code: u8, field_code: u8, serialized_value: Vec) -> Self { - Self { type_code, field_code, serialized_value } + Self { + type_code, + field_code, + serialized_value, + } } } @@ -352,26 +373,30 @@ impl Ord for SerializedField { #[derive(Debug, Clone, Default)] pub struct XRPLObject { - fields: Vec + fields: Vec, } impl XRPLObject { pub fn new() -> XRPLObject { - Self { - fields: Vec::new() - } - } - - pub fn add_field(&mut self, field: Field, value: T) -> Result<(), ContractError> { - self.fields.push(SerializedField::new(T::TYPE_CODE, field.to_u8(), value.xrpl_serialize()?)); + Self { fields: Vec::new() } + } + + pub fn add_field( + &mut self, + field: Field, + value: T, + ) -> Result<(), ContractError> { + self.fields.push(SerializedField::new( + T::TYPE_CODE, + field.to_u8(), + value.xrpl_serialize()?, + )); Ok(()) } pub fn add_sequence(&mut self, sequence: XRPLSequence) -> Result<(), ContractError> { match sequence { - XRPLSequence::Plain(seq) => { - self.add_field(Field::Sequence, seq) - }, + XRPLSequence::Plain(seq) => self.add_field(Field::Sequence, seq), XRPLSequence::Ticket(seq) => { self.add_field(Field::Sequence, 0u32)?; self.add_field(Field::TicketSequence, seq) @@ -419,8 +444,6 @@ pub fn field_id(type_code: u8, field_code: u8) -> Vec { } } - - #[cfg(test)] mod tests { use std::str::FromStr; @@ -459,7 +482,10 @@ mod tests { assert_hex_eq!("00000005", 5u32.xrpl_serialize()?); assert_hex_eq!("FFFFFFFF", 0xffffffffu32.xrpl_serialize()?); assert_hex_eq!("00", HexBinary::from_hex("")?.xrpl_serialize()?); - assert_hex_eq!("04DEADBEEF", HexBinary::from_hex("DEADBEEF")?.xrpl_serialize()?); + assert_hex_eq!( + "04DEADBEEF", + HexBinary::from_hex("DEADBEEF")?.xrpl_serialize()? + ); assert_hex_eq!( "800000000000000000000000000000000000000055534400000000005B812C9D57731E27A2DA8B1830195F88EF32A3B6", XRPLPaymentAmount::Token(XRPLToken { @@ -509,77 +535,75 @@ mod tests { // minimum XRP assert_hex_eq!( "4000000000000000", - XRPLPaymentAmount::Drops(0) - .xrpl_serialize()? + XRPLPaymentAmount::Drops(0).xrpl_serialize()? ); assert_hex_eq!( "4000000000000001", - XRPLPaymentAmount::Drops(1) - .xrpl_serialize()? + XRPLPaymentAmount::Drops(1).xrpl_serialize()? ); assert_hex_eq!( "40000000499602D2", - XRPLPaymentAmount::Drops(1234567890) - .xrpl_serialize()? + XRPLPaymentAmount::Drops(1234567890).xrpl_serialize()? ); // maximum XRP assert_hex_eq!( "416345785D8A0000", - XRPLPaymentAmount::Drops(100_000_000_000_000_000) - .xrpl_serialize()? + XRPLPaymentAmount::Drops(100_000_000_000_000_000).xrpl_serialize()? ); // more than maximum XRP fails - assert!( - XRPLPaymentAmount::Drops(100_000_000_000_000_001) + assert!(XRPLPaymentAmount::Drops(100_000_000_000_000_001) .xrpl_serialize() - .is_err() - ); + .is_err()); // account "0" (with length prefix) assert_hex_eq!( "140000000000000000000000000000000000000000", - XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrrhoLvTp")? - .xrpl_serialize()? + XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrrhoLvTp")?.xrpl_serialize()? ); // account "1" (with length prefix) assert_hex_eq!( "140000000000000000000000000000000000000001", - XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrBZbvji")? - .xrpl_serialize()? + XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrBZbvji")?.xrpl_serialize()? ); // max acccount assert_hex_eq!( "14FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", - XRPLAccountId::from_str("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi")? - .xrpl_serialize()? + XRPLAccountId::from_str("rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi")?.xrpl_serialize()? ); assert_hex_eq!( "13000081140000000000000000000000000000000000000000", - XRPLSignerEntry{ + XRPLSignerEntry { account: XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrrhoLvTp")?, signer_weight: 0 - }.xrpl_serialize()? + } + .xrpl_serialize()? ); // { "NetworkID": 0 } assert_hex_eq!( "2100000000", - XRPLObject { fields: vec![SerializedField::new(2, 1, 0u32.xrpl_serialize()?)]} + XRPLObject { + fields: vec![SerializedField::new(2, 1, 0u32.xrpl_serialize()?)] + } .xrpl_serialize()? ); // empty array assert_hex_eq!( "F1", - XRPLArray::{ field: Field::Memo, items: vec![] } + XRPLArray:: { + field: Field::Memo, + items: vec![] + } .xrpl_serialize()? ); // array with 1 element assert_hex_eq!( "EA13000081140000000000000000000000000000000000000000E1F1", - XRPLArray::{ field: Field::Memo, items: vec![ - XRPLSignerEntry{ + XRPLArray:: { + field: Field::Memo, + items: vec![XRPLSignerEntry { account: XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrrhoLvTp")?, signer_weight: 0 - }, - ] } + },] + } .xrpl_serialize()? ); Ok(()) @@ -596,7 +620,7 @@ mod tests { currency: "JPY".to_string().try_into()?, issuer: XRPLAccountId::from_str("rrrrrrrrrrrrrrrrrrrrBZbvji")?, }, - XRPLTokenAmount::new(3369568318000000u64, -16) + XRPLTokenAmount::new(3369568318000000u64, -16), ), destination: XRPLAccountId::from_str("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")?, }; @@ -802,5 +826,4 @@ mod tests { ); Ok(()) } - -} \ No newline at end of file +} From 4215dc96ffd9bdd21bfa5b58ce57931aa36cd978 Mon Sep 17 00:00:00 2001 From: k4m4 Date: Wed, 3 Jul 2024 11:32:00 +0300 Subject: [PATCH 80/80] Fix off-by-one TicketCreate count --- contracts/xrpl-multisig-prover/src/xrpl_multisig.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs index 794f3e95b..3afc74437 100644 --- a/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs +++ b/contracts/xrpl-multisig-prover/src/xrpl_multisig.rs @@ -145,7 +145,7 @@ pub fn update_tx_status( XRPLUnsignedTx::TicketCreate(tx) => { mark_tickets_available( storage, - (tx_sequence_number + 1)..(tx_sequence_number + tx.ticket_count), + (tx_sequence_number + 1)..(tx_sequence_number + tx.ticket_count + 1) )?; Response::default() }