diff --git a/Cargo.lock b/Cargo.lock index f4fab49ea7..8a4ca1be26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,11 +49,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.2.1", + "cipher 0.3.0", + "cpufeatures 0.2.11", "opaque-debug", ] +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures 0.2.11", +] + [[package]] name = "aes-gcm" version = "0.9.2" @@ -61,8 +72,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.7.5", + "cipher 0.3.0", "ctr", "ghash", "subtle", @@ -138,6 +149,19 @@ version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" +[[package]] +name = "argon2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.11", + "password-hash", + "zeroize", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -379,15 +403,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" version = "0.11.0" @@ -481,6 +496,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes", + "rand_core 0.5.1", + "zeroize", +] + [[package]] name = "bitcoin" version = "0.29.2" @@ -507,7 +533,7 @@ dependencies = [ "ripemd160", "serialization", "sha-1", - "sha2 0.9.9", + "sha2 0.10.7", "sha3 0.9.1", "siphasher", ] @@ -604,7 +630,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", + "block-padding 0.2.1", "generic-array", ] @@ -623,8 +649,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ - "block-padding", - "cipher", + "block-padding 0.2.1", + "cipher 0.3.0", ] [[package]] @@ -633,6 +659,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "blocking" version = "0.4.6" @@ -835,6 +870,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "cc" version = "1.0.74" @@ -863,8 +907,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.2.1", + "cipher 0.3.0", + "cpufeatures 0.2.11", "zeroize", ] @@ -876,7 +920,7 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -918,6 +962,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "2.33.3" @@ -977,7 +1031,7 @@ dependencies = [ "async-std", "async-trait", "base58", - "base64 0.10.1", + "base64 0.21.7", "bincode", "bip32", "bitcoin", @@ -1063,7 +1117,7 @@ dependencies = [ "serde_json", "serialization", "serialization_derive", - "sha2 0.9.9", + "sha2 0.10.7", "sha3 0.9.1", "solana-client", "solana-sdk", @@ -1074,7 +1128,6 @@ dependencies = [ "tendermint-config", "tendermint-rpc", "time 0.3.20", - "tiny-bip39", "tokio", "tokio-rustls", "tokio-tungstenite-wasm", @@ -1178,7 +1231,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_repr", - "sha2 0.9.9", + "sha2 0.10.7", "shared_ref_counter", "tokio", "uuid 1.2.2", @@ -1303,7 +1356,7 @@ dependencies = [ "k256", "prost", "prost-types", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", "serde_json", "subtle-encoding", @@ -1322,9 +1375,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -1487,17 +1540,25 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" name = "crypto" version = "1.0.0" dependencies = [ + "aes 0.8.3", + "argon2", "arrayref", "async-trait", + "base64 0.21.7", "bip32", + "bip39", "bitcrypto", "bs58 0.4.0", + "cbc", + "cfg-if 1.0.0", + "cipher 0.4.4", "common", "derive_more", "enum-primitive-derive", "enum_derives", "futures 0.3.28", "hex", + "hmac 0.12.1", "http 0.2.7", "hw_common", "keys", @@ -1518,10 +1579,12 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "tiny-bip39", + "sha2 0.10.7", + "tokio", "trezor", "wasm-bindgen-test", "web3", + "zeroize", ] [[package]] @@ -1531,16 +1594,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -1597,7 +1660,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] @@ -2021,7 +2084,7 @@ dependencies = [ "ff 0.11.1", "generic-array", "group 0.11.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -2298,7 +2361,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -2393,7 +2456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher", + "cipher 0.3.0", "libm 0.2.7", "num-bigint", "num-integer", @@ -2694,7 +2757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff 0.11.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -2853,7 +2916,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f2a5541afe0725f0b95619d6af614f48c1b176385b8aa30918cfb8c4bfafc8" dependencies = [ "hmac 0.11.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "sha2 0.9.9", "zeroize", ] @@ -3211,6 +3274,16 @@ dependencies = [ "regex", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding 0.3.3", + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -3425,9 +3498,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" @@ -4444,6 +4517,7 @@ dependencies = [ "either", "enum-primitive-derive", "enum_derives", + "ethabi", "ethereum-types", "futures 0.1.29", "futures 0.3.28", @@ -4542,7 +4616,7 @@ dependencies = [ name = "mm2_metrics" version = "0.1.0" dependencies = [ - "base64 0.10.1", + "base64 0.21.7", "common", "derive_more", "futures 0.3.28", @@ -4638,7 +4712,7 @@ dependencies = [ "secp256k1 0.20.3", "serde", "serde_bytes", - "sha2 0.9.9", + "sha2 0.10.7", "smallvec 1.6.1", "syn 2.0.38", "tokio", @@ -5203,6 +5277,17 @@ dependencies = [ "windows-sys 0.32.0", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.7" @@ -5756,7 +5841,7 @@ checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "rand_hc 0.3.1", ] @@ -5787,7 +5872,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5816,9 +5901,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.9", ] @@ -5847,7 +5932,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -6772,7 +6857,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "digest 0.9.0", "opaque-debug", ] @@ -6784,7 +6869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "digest 0.10.7", ] @@ -6796,7 +6881,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "digest 0.9.0", "opaque-debug", ] @@ -6808,7 +6893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures 0.2.11", "digest 0.10.7", ] @@ -6857,7 +6942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -6927,7 +7012,7 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek 4.0.0-rc.1", - "rand_core 0.6.3", + "rand_core 0.6.4", "ring", "rustc_version 0.4.0", "sha2 0.10.7", @@ -7781,7 +7866,7 @@ dependencies = [ "serde", "serde_json", "serialization", - "sha2 0.9.9", + "sha2 0.10.7", "test_helpers", ] @@ -9548,7 +9633,7 @@ name = "zcash_primitives" version = "0.5.0" source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.4.0#debae777f20f5b5fd263e26877258f7600b52cab" dependencies = [ - "aes", + "aes 0.7.5", "bitvec 0.18.5", "blake2b_simd", "blake2s_simd", diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index 35ee092f9f..4a3fa3fec0 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -23,7 +23,6 @@ dependencies = [ "directories", "env_logger 0.7.1", "gstuff", - "h2", "http 0.2.9", "hyper", "hyper-rustls", @@ -159,6 +158,28 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote 1.0.27", + "syn 2.0.16", +] + [[package]] name = "async-trait" version = "0.1.52" @@ -217,15 +238,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" version = "0.13.1" @@ -234,9 +246,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bech32" @@ -282,7 +294,7 @@ dependencies = [ "ripemd160", "serialization", "sha-1", - "sha2", + "sha2 0.10.8", "sha3", "siphasher", ] @@ -326,6 +338,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + [[package]] name = "block-padding" version = "0.2.1" @@ -516,6 +537,7 @@ dependencies = [ "primitive-types", "rand 0.7.3", "regex", + "rustc-hash", "ser_error", "ser_error_derive", "serde", @@ -523,7 +545,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_repr", - "sha2", + "sha2 0.10.8", "tokio", "uuid", "wasm-bindgen", @@ -673,6 +695,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.8.0" @@ -762,6 +794,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + [[package]] name = "directories" version = "5.0.1" @@ -1116,8 +1158,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2432787a9b8f0d58dca43fe2240399479b7582dc8afa2126dc7652b864029e47" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "opaque-debug", ] @@ -1218,7 +1260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ "crypto-mac", - "digest", + "digest 0.9.0", ] [[package]] @@ -1654,7 +1696,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a4964177ddfdab1e3a2b37aec7cf320e14169abb0ed73999f558136409178d5" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", "hyper", "indexmap 1.9.3", "ipnet", @@ -1776,7 +1818,7 @@ dependencies = [ name = "mm2_metrics" version = "0.1.0" dependencies = [ - "base64 0.10.1", + "base64 0.21.7", "common", "derive_more", "futures 0.3.28", @@ -1797,6 +1839,7 @@ name = "mm2_net" version = "0.1.0" dependencies = [ "async-trait", + "base64 0.21.7", "bytes 1.4.0", "cfg-if 1.0.0", "common", @@ -1806,19 +1849,25 @@ dependencies = [ "futures-util", "gstuff", "http 0.2.9", + "http-body 0.4.5", + "httparse", "hyper", "js-sys", "lazy_static", "mm2_core", "mm2_err_handle", "mm2_state_machine", + "pin-project", "prost", "rand 0.7.3", "rustls 0.20.4", "serde", "serde_json", + "thiserror", "tokio", "tokio-rustls", + "tonic", + "tower-service", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -2075,6 +2124,32 @@ dependencies = [ "crypto-mac", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote 1.0.27", + "syn 2.0.16", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -2385,6 +2460,7 @@ dependencies = [ "libc", "rand_core 0.4.2", "rdrand", + "wasm-bindgen", "winapi", ] @@ -2550,8 +2626,8 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "opaque-debug", ] @@ -2679,7 +2755,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", ] [[package]] @@ -2920,10 +2996,10 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] @@ -2933,21 +3009,32 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if 1.0.0", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha3" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "keccak", "opaque-debug", ] @@ -3187,7 +3274,7 @@ dependencies = [ "pbkdf2", "rand 0.7.3", "rustc-hash", - "sha2", + "sha2 0.9.9", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -3277,6 +3364,17 @@ dependencies = [ "webpki 0.22.0", ] +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.8" @@ -3300,6 +3398,37 @@ dependencies = [ "serde", ] +[[package]] +name = "tonic" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9d60db39854b30b835107500cf0aca0b0d14d6e1c3de124217c23a29c2ddb" +dependencies = [ + "async-stream", + "async-trait", + "base64 0.13.1", + "bytes 1.4.0", + "futures-core", + "futures-util", + "http 0.2.9", + "http-body 0.4.5", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio-stream", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index f10db94c6c..a34c7a06b1 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -26,7 +26,7 @@ doctest = false [dependencies] async-std = { version = "1.5", features = ["unstable"] } async-trait = "0.1.52" -base64 = "0.10.0" +base64 = "0.21.2" base58 = "0.2.0" bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } bitcoin_hashes = "0.11" @@ -95,13 +95,12 @@ serde_json = { version = "1", features = ["preserve_order", "raw_value"] } serialization = { path = "../mm2_bitcoin/serialization" } serialization_derive = { path = "../mm2_bitcoin/serialization_derive" } spv_validation = { path = "../mm2_bitcoin/spv_validation" } -sha2 = "0.9" +sha2 = "0.10" sha3 = "0.9" utxo_signer = { path = "utxo_signer" } # using the same version as cosmrs tendermint-rpc = { version = "=0.23.7", default-features = false } tokio-tungstenite-wasm = { git = "https://github.com/KomodoPlatform/tokio-tungstenite-wasm", rev = "d20abdb", features = ["rustls-tls-native-roots"]} -tiny-bip39 = "0.8.0" url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } # One of web3 dependencies is the old `tokio-uds 0.1.7` which fails cross-compiling to ARM. diff --git a/mm2src/coins/coin_errors.rs b/mm2src/coins/coin_errors.rs index ef4ff6792c..73fea8aff9 100644 --- a/mm2src/coins/coin_errors.rs +++ b/mm2src/coins/coin_errors.rs @@ -1,5 +1,7 @@ -use crate::{eth::Web3RpcError, my_tx_history_v2::MyTxHistoryErrorV2, utxo::rpc_clients::UtxoRpcError, DelegationError, - NumConversError, TxHistoryError, UnexpectedDerivationMethod, WithdrawError}; +use crate::eth::nft_swap_v2::errors::{Erc721FunctionError, HtlcParamsError, PaymentStatusErr, PrepareTxDataError}; +use crate::eth::{EthAssocTypesError, EthNftAssocTypesError, Web3RpcError}; +use crate::{utxo::rpc_clients::UtxoRpcError, NumConversError, UnexpectedDerivationMethod}; +use enum_derives::EnumFromStringify; use futures01::Future; use mm2_err_handle::prelude::MmError; use spv_validation::helpers_validation::SPVError; @@ -11,11 +13,21 @@ pub type ValidatePaymentFut = Box = Result>; /// Enum covering possible error cases of swap payment validation -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum ValidatePaymentError { /// Should be used to indicate internal MM2 state problems (e.g., DB errors, etc.). + #[from_stringify( + "EthAssocTypesError", + "Erc721FunctionError", + "EthNftAssocTypesError", + "NumConversError", + "UnexpectedDerivationMethod", + "keys::Error", + "PrepareTxDataError" + )] InternalError(String), /// Problem with deserializing the transaction, or one of the transaction parts is invalid. + #[from_stringify("rlp::DecoderError", "serialization::Error")] TxDeserializationError(String), /// One of the input parameters is invalid. InvalidParameter(String), @@ -28,6 +40,7 @@ pub enum ValidatePaymentError { /// Payment transaction is in unexpected state. E.g., `Uninitialized` instead of `Sent` for ETH payment. UnexpectedPaymentState(String), /// Transport (RPC) error. + #[from_stringify("web3::Error")] Transport(String), /// Transaction has wrong properties, for example, it has been sent to a wrong address. WrongPaymentTx(String), @@ -39,30 +52,10 @@ pub enum ValidatePaymentError { NftProtocolNotSupported, } -impl From for ValidatePaymentError { - fn from(err: rlp::DecoderError) -> Self { Self::TxDeserializationError(err.to_string()) } -} - -impl From for ValidatePaymentError { - fn from(err: web3::Error) -> Self { Self::Transport(err.to_string()) } -} - -impl From for ValidatePaymentError { - fn from(err: NumConversError) -> Self { Self::InternalError(err.to_string()) } -} - impl From for ValidatePaymentError { fn from(err: SPVError) -> Self { Self::SPVError(err) } } -impl From for ValidatePaymentError { - fn from(err: serialization::Error) -> Self { Self::TxDeserializationError(err.to_string()) } -} - -impl From for ValidatePaymentError { - fn from(err: UnexpectedDerivationMethod) -> Self { Self::InternalError(err.to_string()) } -} - impl From for ValidatePaymentError { fn from(err: UtxoRpcError) -> Self { match err { @@ -86,36 +79,29 @@ impl From for ValidatePaymentError { } } -impl From for ValidatePaymentError { - fn from(err: keys::Error) -> Self { Self::InternalError(err.to_string()) } +impl From for ValidatePaymentError { + fn from(err: PaymentStatusErr) -> Self { + match err { + PaymentStatusErr::Transport(e) => Self::Transport(e), + PaymentStatusErr::AbiError(e) + | PaymentStatusErr::Internal(e) + | PaymentStatusErr::TxDeserializationError(e) => Self::InternalError(e), + } + } +} + +impl From for ValidatePaymentError { + fn from(err: HtlcParamsError) -> Self { + match err { + HtlcParamsError::WrongPaymentTx(e) => ValidatePaymentError::WrongPaymentTx(e), + HtlcParamsError::TxDeserializationError(e) => ValidatePaymentError::TxDeserializationError(e), + } + } } -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum MyAddressError { + #[from_stringify("UnexpectedDerivationMethod")] UnexpectedDerivationMethod(String), InternalError(String), } - -impl From for MyAddressError { - fn from(err: UnexpectedDerivationMethod) -> Self { Self::UnexpectedDerivationMethod(err.to_string()) } -} - -impl From for WithdrawError { - fn from(err: MyAddressError) -> Self { Self::InternalError(err.to_string()) } -} - -impl From for UtxoRpcError { - fn from(err: MyAddressError) -> Self { Self::Internal(err.to_string()) } -} - -impl From for DelegationError { - fn from(err: MyAddressError) -> Self { Self::InternalError(err.to_string()) } -} - -impl From for TxHistoryError { - fn from(err: MyAddressError) -> Self { Self::InternalError(err.to_string()) } -} - -impl From for MyTxHistoryErrorV2 { - fn from(err: MyAddressError) -> Self { Self::Internal(err.to_string()) } -} diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 479e4020bf..90d2f3e97d 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -26,13 +26,15 @@ use crate::eth::web3_transport::websocket_transport::{WebsocketTransport, Websoc use crate::lp_price::get_base_price_in_rel; use crate::nft::nft_structs::{ContractType, ConvertChain, NftInfo, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; -use crate::{DexFee, EthGasLimitOption, RpcCommonOps, ValidateWatcherSpendInput, WatcherSpendType}; +use crate::{DexFee, EthGasLimitOption, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, + RefundMakerPaymentArgs, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, ToBytes, + ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; use common::custom_futures::timeout::FutureTimerExt; -use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError, Timer}; -use common::executor::{AbortOnDropHandle, AbortSettings, SpawnAbortable}; +use common::executor::{abortable_queue::AbortableQueue, AbortOnDropHandle, AbortSettings, AbortableSystem, + AbortedError, SpawnAbortable, Timer}; use common::log::{debug, error, info, warn}; use common::number_type_casting::SafeTypeCastingNumbers; use common::{get_utc_timestamp, now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -49,19 +51,19 @@ use ethcore_transaction::{Action, TransactionWrapper, TransactionWrapperBuilder UnverifiedEip1559Transaction, UnverifiedEip2930Transaction, UnverifiedLegacyTransaction, UnverifiedTransactionWrapper}; use ethereum_types::{Address, H160, H256, U256}; -use ethkey::{public_to_address, KeyPair, Public, Signature}; -use ethkey::{sign, verify_address}; +use ethkey::{public_to_address, sign, verify_address, KeyPair, Public, Signature}; use futures::compat::Future01CompatExt; use futures::future::{join, join_all, select_ok, try_join_all, Either, FutureExt, TryFutureExt}; use futures01::Future; use http::Uri; use instant::Instant; +use keys::Public as HtlcPubKey; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator}; use mm2_number::bigdecimal_custom::CheckedDivision; -use mm2_number::{BigDecimal, MmNumber}; +use mm2_number::{BigDecimal, BigUint, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; @@ -73,6 +75,7 @@ use std::collections::HashMap; use std::convert::{TryFrom, TryInto}; use std::ops::Deref; #[cfg(not(target_arch = "wasm32"))] use std::path::PathBuf; +use std::str::from_utf8; use std::str::FromStr; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::sync::{Arc, Mutex}; @@ -110,20 +113,22 @@ use super::{coin_conf, lp_coinfind_or_err, AsyncMutex, BalanceError, BalanceFut, WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG}; pub use rlp; +use rlp::{DecoderError, Encodable, RlpStream}; mod eth_balance_events; mod eth_rpc; #[cfg(test)] mod eth_tests; #[cfg(target_arch = "wasm32")] mod eth_wasm_tests; +pub(crate) mod nft_swap_v2; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; -use crate::nft::WithdrawNftResult; use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; mod nonce; use crate::coin_errors::ValidatePaymentResult; -use crate::nft::nft_errors::GetNftInfoError; +use crate::nft::nft_errors::{GetNftInfoError, ParseContractTypeError}; +use crate::nft::WithdrawNftResult; use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom}; use nonce::ParityNonce; @@ -143,6 +148,8 @@ pub const ERC20_ABI: &str = include_str!("eth/erc20_abi.json"); const ERC721_ABI: &str = include_str!("eth/erc721_abi.json"); /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md const ERC1155_ABI: &str = include_str!("eth/erc1155_abi.json"); +const NFT_SWAP_CONTRACT_ABI: &str = include_str!("eth/nft_swap_contract_abi.json"); + /// Payment states from etomic swap smart contract: https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol#L5 pub enum PaymentState { Uninitialized, @@ -150,6 +157,24 @@ pub enum PaymentState { Spent, Refunded, } + +#[allow(dead_code)] +pub(crate) enum MakerPaymentStateV2 { + Uninitialized, + PaymentSent, + TakerSpent, + MakerRefunded, +} + +#[allow(dead_code)] +pub(crate) enum TakerPaymentStateV2 { + Uninitialized, + PaymentSent, + TakerApproved, + MakerSpent, + TakerRefunded, +} + /// It can change 12.5% max each block according to https://www.blocknative.com/blog/eip-1559-fees const BASE_BLOCK_FEE_DIFF_PCT: u64 = 13; const DEFAULT_LOGS_BLOCK_RANGE: u64 = 1000; @@ -195,6 +220,7 @@ lazy_static! { pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); pub static ref ERC721_CONTRACT: Contract = Contract::load(ERC721_ABI.as_bytes()).unwrap(); pub static ref ERC1155_CONTRACT: Contract = Contract::load(ERC1155_ABI.as_bytes()).unwrap(); + pub static ref NFT_SWAP_CONTRACT: Contract = Contract::load(NFT_SWAP_CONTRACT_ABI.as_bytes()).unwrap(); } pub type Web3RpcFut = Box> + Send>; @@ -239,10 +265,11 @@ impl PayForGasOption { type GasDetails = (U256, PayForGasOption); -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum Web3RpcError { #[display(fmt = "Transport: {}", _0)] Transport(String), + #[from_stringify("serde_json::Error")] #[display(fmt = "Invalid response: {}", _0)] InvalidResponse(String), #[display(fmt = "Timeout: {}", _0)] @@ -255,10 +282,6 @@ pub enum Web3RpcError { NftProtocolNotSupported, } -impl From for Web3RpcError { - fn from(e: serde_json::Error) -> Self { Web3RpcError::InvalidResponse(e.to_string()) } -} - impl From for Web3RpcError { fn from(e: web3::Error) -> Self { let error_str = e.to_string(); @@ -274,10 +297,6 @@ impl From for Web3RpcError { } } -impl From for RawTransactionError { - fn from(e: web3::Error) -> Self { RawTransactionError::Transport(e.to_string()) } -} - impl From for RawTransactionError { fn from(e: Web3RpcError) -> Self { match e { @@ -640,7 +659,7 @@ impl EthCoinImpl { } /// The id used to differentiate payments on Etomic swap smart contract - fn etomic_swap_id(&self, time_lock: u32, secret_hash: &[u8]) -> Vec { + pub(crate) fn etomic_swap_id(&self, time_lock: u32, secret_hash: &[u8]) -> Vec { let mut input = vec![]; input.extend_from_slice(&time_lock.to_le_bytes()); input.extend_from_slice(secret_hash); @@ -2295,7 +2314,7 @@ impl MarketCoinOps for EthCoin { EthCoinType::Eth => get_function_name("ethPayment", args.watcher_reward), EthCoinType::Erc20 { .. } => get_function_name("erc20Payment", args.watcher_reward), EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::NftProtocolNotSupported(ERRL!( + return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" )))) }, @@ -3476,7 +3495,7 @@ impl EthCoin { #[cfg_attr(test, mockable)] impl EthCoin { - fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256) -> EthTxFut { + pub(crate) fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256) -> EthTxFut { let ctx = try_tx_fus!(MmArc::from_weak(&self.ctx).ok_or("!ctx")); let coin = self.clone(); let fut = async move { @@ -3509,7 +3528,7 @@ impl EthCoin { self.sign_and_send_transaction(0.into(), Action::Call(*token_addr), data, U256::from(210_000)) }, EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::NftProtocolNotSupported(ERRL!( + return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" )))) }, @@ -3674,7 +3693,7 @@ impl EthCoin { })) }, EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::NftProtocolNotSupported(ERRL!( + return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" )))) }, @@ -3795,7 +3814,7 @@ impl EthCoin { ) }, EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::NftProtocolNotSupported(ERRL!( + return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" )))) }, @@ -3920,7 +3939,7 @@ impl EthCoin { ) }, EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::NftProtocolNotSupported(ERRL!( + return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" )))) }, @@ -4042,8 +4061,8 @@ impl EthCoin { ) }, EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::NftProtocolNotSupported(ERRL!( - "Nft Protocol is not supported yet!" + return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( + "Nft Protocol is not supported!" )))) }, } @@ -4165,7 +4184,7 @@ impl EthCoin { ) }, EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::NftProtocolNotSupported(ERRL!( + return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" )))) }, @@ -4333,7 +4352,12 @@ impl EthCoin { Box::new(fut.boxed().compat().map_to_mm_fut(BalanceError::from)) } - async fn call_request(&self, to: Address, value: Option, data: Option) -> Result { + pub(crate) async fn call_request( + &self, + to: Address, + value: Option, + data: Option, + ) -> Result { let request = CallRequest { from: Some(self.my_address), to: Some(to), @@ -4416,7 +4440,7 @@ impl EthCoin { EthCoinType::Eth => return TX_PLAIN_ERR!("'approve' is expected to be call for ERC20 coins only"), EthCoinType::Erc20 { token_addr, .. } => token_addr, EthCoinType::Nft { .. } => { - return Err(TransactionErr::NftProtocolNotSupported(ERRL!( + return Err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" ))) }, @@ -6134,7 +6158,8 @@ pub async fn eth_coin_from_conf_and_request( }; (EthCoinType::Erc20 { platform, token_addr }, decimals) }, - _ => return ERR!("Expect ETH or ERC20 protocol"), + CoinProtocol::NFT { platform } => (EthCoinType::Nft { platform }, ETH_DECIMALS), + _ => return ERR!("Expect ETH, ERC20 or NFT protocol"), }; // param from request should override the config @@ -6161,8 +6186,7 @@ pub async fn eth_coin_from_conf_and_request( let key_lock = match &coin_type { EthCoinType::Eth => String::from(ticker), - EthCoinType::Erc20 { ref platform, .. } => String::from(platform), - EthCoinType::Nft { .. } => return ERR!("Does not support NFT protocol"), + EthCoinType::Erc20 { platform, .. } | EthCoinType::Nft { platform } => String::from(platform), }; let nonce_lock = { @@ -6239,7 +6263,7 @@ pub fn checksum_address(addr: &str) -> String { /// `eth_addr_to_hex` converts Address to hex format. /// Note: the result will be in lowercase. -pub(crate) fn eth_addr_to_hex(address: &Address) -> String { format!("{:#02x}", address) } +pub fn eth_addr_to_hex(address: &Address) -> String { format!("{:#02x}", address) } /// Checks that input is valid mixed-case checksum form address /// The input must be 0x prefixed hex string @@ -6525,3 +6549,166 @@ fn call_request_with_pay_for_gas_option(call_request: CallRequest, pay_for_gas_o }, } } + +impl ToBytes for Signature { + fn to_bytes(&self) -> Vec { self.to_vec() } +} + +impl ToBytes for SignedEthTx { + fn to_bytes(&self) -> Vec { + let mut stream = RlpStream::new(); + self.rlp_append(&mut stream); + // Handle potential panicking. + if stream.is_finished() { + Vec::from(stream.out()) + } else { + // TODO: Consider returning Result, Error> in future refactoring for better error handling. + warn!("RlpStream was not finished; returning an empty Vec as a fail-safe."); + vec![] + } + } +} + +#[derive(Debug, Display)] +pub enum EthAssocTypesError { + InvalidHexString(String), + TxParseError(String), + ParseSignatureError(String), + KeysError(keys::Error), +} + +impl From for EthAssocTypesError { + fn from(e: DecoderError) -> Self { EthAssocTypesError::TxParseError(e.to_string()) } +} + +impl From for EthAssocTypesError { + fn from(e: keys::Error) -> Self { EthAssocTypesError::KeysError(e) } +} + +#[derive(Debug, Display)] +pub enum EthNftAssocTypesError { + Utf8Error(String), + ParseContractTypeError(ParseContractTypeError), + ParseTokenContractError(String), +} + +impl From for EthNftAssocTypesError { + fn from(e: ParseContractTypeError) -> Self { EthNftAssocTypesError::ParseContractTypeError(e) } +} + +impl ParseCoinAssocTypes for EthCoin { + type Address = Address; + type AddressParseError = MmError; + type Pubkey = HtlcPubKey; + type PubkeyParseError = MmError; + type Tx = SignedEthTx; + type TxParseError = MmError; + type Preimage = SignedEthTx; + type PreimageParseError = MmError; + type Sig = Signature; + type SigParseError = MmError; + + fn my_addr(&self) -> &Self::Address { &self.my_address } + + fn parse_address(&self, address: &str) -> Result { + Address::from_str(address).map_to_mm(|e| EthAssocTypesError::InvalidHexString(e.to_string())) + } + + fn parse_pubkey(&self, pubkey: &[u8]) -> Result { + HtlcPubKey::from_slice(pubkey).map_to_mm(EthAssocTypesError::from) + } + + fn parse_tx(&self, tx: &[u8]) -> Result { + let unverified: UnverifiedTransactionWrapper = rlp::decode(tx).map_err(EthAssocTypesError::from)?; + SignedEthTx::new(unverified).map_to_mm(|e| EthAssocTypesError::TxParseError(e.to_string())) + } + + fn parse_preimage(&self, tx: &[u8]) -> Result { self.parse_tx(tx) } + + fn parse_signature(&self, sig: &[u8]) -> Result { + if sig.len() != 65 { + return MmError::err(EthAssocTypesError::ParseSignatureError( + "Signature slice is not 65 bytes long".to_string(), + )); + }; + + let mut arr = [0; 65]; + arr.copy_from_slice(sig); + Ok(Signature::from(arr)) // Assuming `Signature::from([u8; 65])` exists + } +} + +impl ToBytes for Address { + fn to_bytes(&self) -> Vec { self.0.to_vec() } +} + +impl ToBytes for BigUint { + fn to_bytes(&self) -> Vec { self.to_bytes_be() } +} + +impl ToBytes for ContractType { + fn to_bytes(&self) -> Vec { self.to_string().into_bytes() } +} + +impl ParseNftAssocTypes for EthCoin { + type ContractAddress = Address; + type TokenId = BigUint; + type ContractType = ContractType; + type NftAssocTypesError = MmError; + + fn parse_contract_address( + &self, + contract_address: &[u8], + ) -> Result { + contract_address + .try_to_address() + .map_to_mm(EthNftAssocTypesError::ParseTokenContractError) + } + + fn parse_token_id(&self, token_id: &[u8]) -> Result { + Ok(BigUint::from_bytes_be(token_id)) + } + + fn parse_contract_type(&self, contract_type: &[u8]) -> Result { + let contract_str = from_utf8(contract_type).map_err(|e| EthNftAssocTypesError::Utf8Error(e.to_string()))?; + ContractType::from_str(contract_str).map_to_mm(EthNftAssocTypesError::from) + } +} + +#[async_trait] +impl MakerNftSwapOpsV2 for EthCoin { + async fn send_nft_maker_payment_v2( + &self, + args: SendNftMakerPaymentArgs<'_, Self>, + ) -> Result { + self.send_nft_maker_payment_v2_impl(args).await + } + + async fn validate_nft_maker_payment_v2( + &self, + args: ValidateNftMakerPaymentArgs<'_, Self>, + ) -> ValidatePaymentResult<()> { + self.validate_nft_maker_payment_v2_impl(args).await + } + + async fn spend_nft_maker_payment_v2( + &self, + args: SpendNftMakerPaymentArgs<'_, Self>, + ) -> Result { + self.spend_nft_maker_payment_v2_impl(args).await + } + + async fn refund_nft_maker_payment_v2_timelock( + &self, + args: RefundPaymentArgs<'_>, + ) -> Result { + self.refund_nft_maker_payment_v2_timelock_impl(args).await + } + + async fn refund_nft_maker_payment_v2_secret( + &self, + _args: RefundMakerPaymentArgs<'_, Self>, + ) -> Result { + todo!() + } +} diff --git a/mm2src/coins/eth/eth_rpc.rs b/mm2src/coins/eth/eth_rpc.rs index 36b2c3221c..922e219fbd 100644 --- a/mm2src/coins/eth/eth_rpc.rs +++ b/mm2src/coins/eth/eth_rpc.rs @@ -18,6 +18,7 @@ impl EthCoin { async fn try_rpc_send(&self, method: &str, params: Vec) -> Result { let mut clients = self.web3_instances.lock().await; + let mut error = web3::Error::Unreachable; for (i, client) in clients.clone().into_iter().enumerate() { let execute_fut = match client.web3.transport() { Web3Transport::Http(http) => http.execute(method, params.clone()), @@ -35,8 +36,9 @@ impl EthCoin { clients.rotate_left(i); return Ok(r); }, - Ok(Err(rpc_error)) => { - debug!("Request on '{method}' failed. Error: {rpc_error}"); + Ok(Err(err)) => { + debug!("Request on '{method}' failed. Error: {err}"); + error = err; if let Web3Transport::Websocket(socket_transport) = client.web3.transport() { socket_transport.stop_connection_loop().await; @@ -52,9 +54,7 @@ impl EthCoin { }; } - Err(web3::Error::Transport(web3::error::TransportError::Message(format!( - "Request '{method}' failed due to not being able to find a living RPC client" - )))) + Err(error) } } diff --git a/mm2src/coins/eth/nft_swap_contract_abi.json b/mm2src/coins/eth/nft_swap_contract_abi.json new file mode 100644 index 0000000000..fd17b4cd8c --- /dev/null +++ b/mm2src/coins/eth/nft_swap_contract_abi.json @@ -0,0 +1,898 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "feeAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "MakerPaymentRefundedSecret", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "MakerPaymentRefundedTimelock", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "MakerPaymentSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "MakerPaymentSpent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "TakerPaymentApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + } + ], + "name": "TakerPaymentRefundedSecret", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "TakerPaymentRefundedTimelock", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "TakerPaymentSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + } + ], + "name": "TakerPaymentSpent", + "type": "event" + }, + { + "inputs": [], + "name": "dexFeeAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dexFee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "preApproveLockTime", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "paymentLockTime", + "type": "uint32" + } + ], + "name": "erc20TakerPayment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "dexFee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "preApproveLockTime", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "paymentLockTime", + "type": "uint32" + } + ], + "name": "ethTakerPayment", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "makerPayments", + "outputs": [ + { + "internalType": "bytes20", + "name": "paymentHash", + "type": "bytes20" + }, + { + "internalType": "uint32", + "name": "paymentLockTime", + "type": "uint32" + }, + { + "internalType": "enum EtomicSwapNft.MakerPaymentState", + "name": "state", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecret", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "refundErc1155MakerPaymentSecret", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "refundErc1155MakerPaymentTimelock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecret", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "refundErc721MakerPaymentSecret", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "refundErc721MakerPaymentTimelock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dexFee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecret", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "refundTakerPaymentSecret", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dexFee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "refundTakerPaymentTimelock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "spendErc1155MakerPayment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "spendErc721MakerPayment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dexFee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "spendTakerPayment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dexFee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "takerSecretHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "makerSecretHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + } + ], + "name": "takerPaymentApprove", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "takerPayments", + "outputs": [ + { + "internalType": "bytes20", + "name": "paymentHash", + "type": "bytes20" + }, + { + "internalType": "uint32", + "name": "preApproveLockTime", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "paymentLockTime", + "type": "uint32" + }, + { + "internalType": "enum EtomicSwapNft.TakerPaymentState", + "name": "state", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/mm2src/coins/eth/nft_swap_v2/errors.rs b/mm2src/coins/eth/nft_swap_v2/errors.rs new file mode 100644 index 0000000000..e66cd437d0 --- /dev/null +++ b/mm2src/coins/eth/nft_swap_v2/errors.rs @@ -0,0 +1,41 @@ +use enum_derives::EnumFromStringify; + +#[derive(Debug, Display)] +pub(crate) enum Erc721FunctionError { + AbiError(String), + FunctionNotFound(String), +} + +#[derive(Debug, Display)] +pub(crate) enum HtlcParamsError { + WrongPaymentTx(String), + TxDeserializationError(String), +} + +#[derive(Debug, Display, EnumFromStringify)] +pub(crate) enum PaymentStatusErr { + #[from_stringify("ethabi::Error")] + #[display(fmt = "Abi error: {}", _0)] + AbiError(String), + #[from_stringify("web3::Error")] + #[display(fmt = "Transport error: {}", _0)] + Transport(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), + #[display(fmt = "Tx deserialization error: {}", _0)] + TxDeserializationError(String), +} + +#[derive(Debug, Display, EnumFromStringify)] +pub(crate) enum PrepareTxDataError { + #[from_stringify("ethabi::Error")] + #[display(fmt = "Abi error: {}", _0)] + AbiError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), + Erc721FunctionError(Erc721FunctionError), +} + +impl From for PrepareTxDataError { + fn from(e: Erc721FunctionError) -> Self { Self::Erc721FunctionError(e) } +} diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs new file mode 100644 index 0000000000..c110a38fc5 --- /dev/null +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -0,0 +1,539 @@ +use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult}; +use ethabi::{Contract, Token}; +use ethcore_transaction::{Action, UnverifiedTransactionWrapper}; +use ethereum_types::{Address, U256}; +use futures::compat::Future01CompatExt; +use mm2_err_handle::prelude::{MapToMmResult, MmError, MmResult}; +use mm2_number::BigDecimal; +use std::convert::TryInto; +use web3::types::{Transaction as Web3Tx, TransactionId}; + +pub(crate) mod errors; +use errors::{Erc721FunctionError, HtlcParamsError, PaymentStatusErr, PrepareTxDataError}; +mod structs; +use structs::{ExpectedHtlcParams, PaymentType, ValidationParams}; + +use super::ContractType; +use crate::eth::{addr_from_raw_pubkey, decode_contract_call, EthCoin, EthCoinType, MakerPaymentStateV2, SignedEthTx, + TryToAddress, ERC1155_CONTRACT, ERC721_CONTRACT, ETH_MAX_TRADE_GAS, NFT_SWAP_CONTRACT}; +use crate::{ParseCoinAssocTypes, RefundPaymentArgs, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, TransactionErr, + ValidateNftMakerPaymentArgs}; + +impl EthCoin { + pub(crate) async fn send_nft_maker_payment_v2_impl( + &self, + args: SendNftMakerPaymentArgs<'_, Self>, + ) -> Result { + try_tx_s!(validate_payment_args( + args.taker_secret_hash, + args.maker_secret_hash, + &args.amount, + args.nft_swap_info.contract_type + )); + let htlc_data = try_tx_s!(self.prepare_htlc_data(&args)); + + match &self.coin_type { + EthCoinType::Nft { .. } => { + let data = try_tx_s!(self.prepare_nft_maker_payment_v2_data(&args, htlc_data)); + self.sign_and_send_transaction( + 0.into(), + Action::Call(*args.nft_swap_info.token_address), + data, + U256::from(ETH_MAX_TRADE_GAS), + ) + .compat() + .await + }, + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported( + "ETH and ERC20 Protocols are not supported for NFT Swaps".to_string(), + )), + } + } + + pub(crate) async fn validate_nft_maker_payment_v2_impl( + &self, + args: ValidateNftMakerPaymentArgs<'_, Self>, + ) -> ValidatePaymentResult<()> { + let contract_type = args.nft_swap_info.contract_type; + validate_payment_args( + args.taker_secret_hash, + args.maker_secret_hash, + &args.amount, + contract_type, + ) + .map_err(ValidatePaymentError::InternalError)?; + let etomic_swap_contract = args.nft_swap_info.swap_contract_address; + let token_address = args.nft_swap_info.token_address; + let maker_address = addr_from_raw_pubkey(args.maker_pub).map_to_mm(ValidatePaymentError::InternalError)?; + let time_lock_u32 = args + .time_lock + .try_into() + .map_err(ValidatePaymentError::TimelockOverflow)?; + let swap_id = self.etomic_swap_id(time_lock_u32, args.maker_secret_hash); + let maker_status = self + .payment_status_v2( + *etomic_swap_contract, + Token::FixedBytes(swap_id.clone()), + &NFT_SWAP_CONTRACT, + PaymentType::MakerPayments, + ) + .await?; + let tx_from_rpc = self + .transaction(TransactionId::Hash(args.maker_payment_tx.tx_hash())) + .await?; + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidatePaymentError::TxDoesNotExist(format!( + "Didn't find provided tx {:?} on ETH node", + args.maker_payment_tx.tx_hash() + )) + })?; + validate_from_to_and_maker_status(tx_from_rpc, maker_address, *token_address, maker_status).await?; + match self.coin_type { + EthCoinType::Nft { .. } => { + let (decoded, index_bytes) = get_decoded_tx_data_and_index_bytes(contract_type, &tx_from_rpc.input.0)?; + + let amount = if matches!(contract_type, &ContractType::Erc1155) { + Some(args.amount.to_string()) + } else { + None + }; + + let validation_params = ValidationParams { + maker_address, + etomic_swap_contract: *etomic_swap_contract, + token_id: args.nft_swap_info.token_id, + amount, + }; + validate_decoded_data(&decoded, &validation_params)?; + + let taker_address = + addr_from_raw_pubkey(args.taker_pub).map_to_mm(ValidatePaymentError::InternalError)?; + let htlc_params = ExpectedHtlcParams { + swap_id, + taker_address, + token_address: *token_address, + taker_secret_hash: args.taker_secret_hash.to_vec(), + maker_secret_hash: args.maker_secret_hash.to_vec(), + time_lock: U256::from(args.time_lock), + }; + decode_and_validate_htlc_params(decoded, index_bytes, htlc_params)?; + }, + EthCoinType::Eth | EthCoinType::Erc20 { .. } => { + return MmError::err(ValidatePaymentError::InternalError( + "EthCoinType must be Nft".to_string(), + )) + }, + } + Ok(()) + } + + pub(crate) async fn spend_nft_maker_payment_v2_impl( + &self, + args: SpendNftMakerPaymentArgs<'_, Self>, + ) -> Result { + let etomic_swap_contract = args.swap_contract_address; + if args.maker_secret.len() != 32 { + return Err(TransactionErr::Plain(ERRL!("maker_secret must be 32 bytes"))); + } + let contract_type = args.contract_type; + let (decoded, index_bytes) = try_tx_s!(get_decoded_tx_data_and_index_bytes( + contract_type, + &args.maker_payment_tx.unsigned().data() + )); + + let (state, htlc_params) = try_tx_s!( + self.status_and_htlc_params_from_tx_data( + *etomic_swap_contract, + &NFT_SWAP_CONTRACT, + &decoded, + index_bytes, + PaymentType::MakerPayments, + ) + .await + ); + match self.coin_type { + EthCoinType::Nft { .. } => { + let data = try_tx_s!(self.prepare_spend_nft_maker_v2_data(&args, decoded, htlc_params, state)); + self.sign_and_send_transaction( + 0.into(), + Action::Call(*etomic_swap_contract), + data, + U256::from(ETH_MAX_TRADE_GAS), + ) + .compat() + .await + }, + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported( + "ETH and ERC20 Protocols are not supported for NFT Swaps".to_string(), + )), + } + } + + pub(crate) async fn refund_nft_maker_payment_v2_timelock_impl( + &self, + args: RefundPaymentArgs<'_>, + ) -> Result { + let _etomic_swap_contract = try_tx_s!(args.swap_contract_address.try_to_address()); + let tx: UnverifiedTransactionWrapper = try_tx_s!(rlp::decode(args.payment_tx)); + let _payment = try_tx_s!(SignedEthTx::new(tx)); + todo!() + } + + fn prepare_nft_maker_payment_v2_data( + &self, + args: &SendNftMakerPaymentArgs<'_, Self>, + htlc_data: Vec, + ) -> Result, PrepareTxDataError> { + match args.nft_swap_info.contract_type { + ContractType::Erc1155 => { + let function = ERC1155_CONTRACT.function("safeTransferFrom")?; + let amount_u256 = U256::from_dec_str(&args.amount.to_string()) + .map_err(|e| PrepareTxDataError::Internal(e.to_string()))?; + let data = function.encode_input(&[ + Token::Address(*self.my_addr()), + Token::Address(*args.nft_swap_info.swap_contract_address), + Token::Uint(U256::from(args.nft_swap_info.token_id)), + Token::Uint(amount_u256), + Token::Bytes(htlc_data), + ])?; + Ok(data) + }, + ContractType::Erc721 => { + let function = erc721_transfer_with_data()?; + let data = function.encode_input(&[ + Token::Address(*self.my_addr()), + Token::Address(*args.nft_swap_info.swap_contract_address), + Token::Uint(U256::from(args.nft_swap_info.token_id)), + Token::Bytes(htlc_data), + ])?; + Ok(data) + }, + } + } + + fn prepare_htlc_data(&self, args: &SendNftMakerPaymentArgs<'_, Self>) -> Result, PrepareTxDataError> { + let taker_address = + addr_from_raw_pubkey(args.taker_pub).map_err(|e| PrepareTxDataError::Internal(ERRL!("{}", e)))?; + let time_lock_u32 = args + .time_lock + .try_into() + .map_err(|e| PrepareTxDataError::Internal(ERRL!("{}", e)))?; + let id = self.etomic_swap_id(time_lock_u32, args.maker_secret_hash); + let encoded = ethabi::encode(&[ + Token::FixedBytes(id), + Token::Address(taker_address), + Token::Address(*args.nft_swap_info.token_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(U256::from(time_lock_u32)), + ]); + Ok(encoded) + } + + /// Retrieves the payment status from a given smart contract address based on the swap ID and state type. + async fn payment_status_v2( + &self, + swap_address: Address, + swap_id: Token, + contract_abi: &Contract, + state_type: PaymentType, + ) -> Result { + let function_name = state_type.as_str(); + let function = contract_abi.function(function_name)?; + let data = function.encode_input(&[swap_id])?; + let bytes = self.call_request(swap_address, None, Some(data.into())).await?; + let decoded_tokens = function.decode_output(&bytes.0)?; + let state = decoded_tokens + .get(2) + .ok_or_else(|| PaymentStatusErr::Internal(ERRL!("Payment status must contain 'state' as the 2nd token")))?; + match state { + Token::Uint(state) => Ok(*state), + _ => Err(PaymentStatusErr::Internal(ERRL!( + "Payment status must be Uint, got {:?}", + state + ))), + } + } + + /// Prepares the encoded transaction data for spending a maker's NFT payment on the blockchain. + /// + /// This function selects the appropriate contract function based on the NFT's contract type (ERC1155 or ERC721) + /// and encodes the input parameters required for the blockchain transaction. + fn prepare_spend_nft_maker_v2_data( + &self, + args: &SpendNftMakerPaymentArgs<'_, Self>, + decoded: Vec, + htlc_params: Vec, + state: U256, + ) -> Result, PrepareTxDataError> { + let spend_func = match args.contract_type { + ContractType::Erc1155 => NFT_SWAP_CONTRACT.function("spendErc1155MakerPayment")?, + ContractType::Erc721 => NFT_SWAP_CONTRACT.function("spendErc721MakerPayment")?, + }; + + if state != U256::from(MakerPaymentStateV2::PaymentSent as u8) { + return Err(PrepareTxDataError::Internal(ERRL!( + "Payment {:?} state is not PAYMENT_STATE_SENT, got {}", + args.maker_payment_tx, + state + ))); + } + + let input_tokens = match args.contract_type { + ContractType::Erc1155 => vec![ + htlc_params[0].clone(), // swap_id + Token::Address(args.maker_payment_tx.sender()), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret.to_vec()), + htlc_params[2].clone(), // tokenAddress + decoded[2].clone(), // tokenId + decoded[3].clone(), // amount + ], + ContractType::Erc721 => vec![ + htlc_params[0].clone(), // swap_id + Token::Address(args.maker_payment_tx.sender()), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret.to_vec()), + htlc_params[2].clone(), // tokenAddress + decoded[2].clone(), // tokenId + ], + }; + + let data = spend_func.encode_input(&input_tokens)?; + Ok(data) + } + + async fn status_and_htlc_params_from_tx_data( + &self, + swap_address: Address, + contract_abi: &Contract, + decoded_data: &[Token], + index: usize, + state_type: PaymentType, + ) -> Result<(U256, Vec), PaymentStatusErr> { + let data_bytes = match decoded_data.get(index) { + Some(Token::Bytes(data_bytes)) => data_bytes, + _ => { + return Err(PaymentStatusErr::TxDeserializationError(ERRL!( + "Failed to decode HTLCParams from data_bytes" + ))) + }, + }; + + let htlc_params = match ethabi::decode(htlc_params(), data_bytes) { + Ok(htlc_params) => htlc_params, + Err(_) => { + return Err(PaymentStatusErr::TxDeserializationError(ERRL!( + "Failed to decode HTLCParams from data_bytes" + ))) + }, + }; + + let state = self + .payment_status_v2(swap_address, htlc_params[0].clone(), contract_abi, state_type) + .await?; + + Ok((state, htlc_params)) + } +} + +/// Validates decoded data from tx input, related to `safeTransferFrom` contract call +fn validate_decoded_data(decoded: &[Token], params: &ValidationParams) -> Result<(), MmError> { + let checks = vec![ + (0, Token::Address(params.maker_address), "maker_address"), + (1, Token::Address(params.etomic_swap_contract), "etomic_swap_contract"), + (2, Token::Uint(U256::from(params.token_id)), "token_id"), + ]; + + for (index, expected_token, field_name) in checks { + if decoded.get(index) != Some(&expected_token) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "NFT Maker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + if let Some(amount) = ¶ms.amount { + let value = U256::from_dec_str(amount).map_to_mm(|e| ValidatePaymentError::InternalError(e.to_string()))?; + if decoded[3] != Token::Uint(value) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "NFT Maker Payment `amount` {:?} is invalid, expected {:?}", + decoded[3], + Token::Uint(value) + ))); + } + } + Ok(()) +} + +fn decode_and_validate_htlc_params( + decoded: Vec, + index: usize, + expected_params: ExpectedHtlcParams, +) -> MmResult<(), HtlcParamsError> { + let data_bytes = match decoded.get(index) { + Some(Token::Bytes(bytes)) => bytes, + _ => { + return MmError::err(HtlcParamsError::TxDeserializationError( + "Expected Bytes for HTLCParams data".to_string(), + )) + }, + }; + + let decoded_params = match ethabi::decode(htlc_params(), data_bytes) { + Ok(params) => params, + Err(_) => { + return MmError::err(HtlcParamsError::TxDeserializationError( + "Failed to decode HTLCParams from data_bytes".to_string(), + )) + }, + }; + + let expected_taker_secret_hash = Token::FixedBytes(expected_params.taker_secret_hash.clone()); + let expected_maker_secret_hash = Token::FixedBytes(expected_params.maker_secret_hash.clone()); + + let checks = vec![ + (0, Token::FixedBytes(expected_params.swap_id.clone()), "swap_id"), + (1, Token::Address(expected_params.taker_address), "taker_address"), + (2, Token::Address(expected_params.token_address), "token_address"), + (3, expected_taker_secret_hash, "taker_secret_hash"), + (4, expected_maker_secret_hash, "maker_secret_hash"), + (5, Token::Uint(expected_params.time_lock), "time_lock"), + ]; + + for (index, expected_token, param_name) in checks.into_iter() { + if decoded_params[index] != expected_token { + return MmError::err(HtlcParamsError::WrongPaymentTx(format!( + "Invalid '{}' {:?}, expected {:?}", + param_name, decoded_params[index], expected_token + ))); + } + } + + Ok(()) +} + +/// Representation of the Solidity HTLCParams struct. +/// +/// struct HTLCParams { +/// bytes32 id; +/// address taker; +/// address tokenAddress; +/// bytes32 takerSecretHash; +/// bytes32 makerSecretHash; +/// uint32 paymentLockTime; +/// } +fn htlc_params() -> &'static [ethabi::ParamType] { + &[ + ethabi::ParamType::FixedBytes(32), + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32), + ethabi::ParamType::FixedBytes(32), + ethabi::ParamType::Uint(256), + ] +} + +/// function to check if BigDecimal is a positive integer +#[inline(always)] +fn is_positive_integer(amount: &BigDecimal) -> bool { amount == &amount.with_scale(0) && amount > &BigDecimal::from(0) } + +fn validate_payment_args<'a>( + taker_secret_hash: &'a [u8], + maker_secret_hash: &'a [u8], + amount: &BigDecimal, + contract_type: &ContractType, +) -> Result<(), String> { + match contract_type { + ContractType::Erc1155 => { + if !is_positive_integer(amount) { + return Err("ERC-1155 amount must be a positive integer".to_string()); + } + }, + ContractType::Erc721 => { + if amount != &BigDecimal::from(1) { + return Err("ERC-721 amount must be 1".to_string()); + } + }, + } + if taker_secret_hash.len() != 32 { + return Err("taker_secret_hash must be 32 bytes".to_string()); + } + if maker_secret_hash.len() != 32 { + return Err("maker_secret_hash must be 32 bytes".to_string()); + } + + Ok(()) +} + +async fn validate_from_to_and_maker_status( + tx_from_rpc: &Web3Tx, + expected_from: Address, + expected_to: Address, + maker_status: U256, +) -> ValidatePaymentResult<()> { + if maker_status != U256::from(MakerPaymentStateV2::PaymentSent as u8) { + return MmError::err(ValidatePaymentError::UnexpectedPaymentState(format!( + "NFT Maker Payment state is not PAYMENT_STATE_SENT, got {}", + maker_status + ))); + } + if tx_from_rpc.from != Some(expected_from) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "NFT Maker Payment tx {:?} was sent from wrong address, expected {:?}", + tx_from_rpc, expected_from + ))); + } + // As NFT owner calls "safeTransferFrom" directly, then in Transaction 'to' field we expect token_address + if tx_from_rpc.to != Some(expected_to) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "NFT Maker Payment tx {:?} was sent to wrong address, expected {:?}", + tx_from_rpc, expected_to, + ))); + } + Ok(()) +} + +/// Identifies the correct "safeTransferFrom" function based on the contract type (either ERC1155 or ERC721) +/// and decodes the provided contract call bytes using the ABI of the identified function. Additionally, it returns +/// the index position of the "bytes" field within the function's parameters. +pub(crate) fn get_decoded_tx_data_and_index_bytes( + contract_type: &ContractType, + contract_call_bytes: &[u8], +) -> Result<(Vec, usize), PrepareTxDataError> { + let (send_func, index_bytes) = match contract_type { + ContractType::Erc1155 => (ERC1155_CONTRACT.function("safeTransferFrom")?, 4), + ContractType::Erc721 => (erc721_transfer_with_data()?, 3), + }; + let decoded = decode_contract_call(send_func, contract_call_bytes)?; + Ok((decoded, index_bytes)) +} + +/// ERC721 contract has overloaded versions of the `safeTransferFrom` function, +/// but `Contract::function` method returns only the first if there are overloaded versions of the same function. +/// Provided function retrieves the `safeTransferFrom` variant that includes a `bytes` parameter. +/// This variant is specifically used for transferring ERC721 tokens with additional data. +fn erc721_transfer_with_data<'a>() -> Result<&'a ethabi::Function, Erc721FunctionError> { + let functions = ERC721_CONTRACT + .functions_by_name("safeTransferFrom") + .map_err(|e| Erc721FunctionError::AbiError(ERRL!("{}", e)))?; + + // Find the correct function variant by inspecting the input parameters. + let function = functions + .iter() + .find(|f| { + f.inputs.len() == 4 + && matches!( + f.inputs.last().map(|input| &input.kind), + Some(ðabi::ParamType::Bytes) + ) + }) + .ok_or_else(|| { + Erc721FunctionError::FunctionNotFound( + "Failed to find the correct safeTransferFrom function variant".to_string(), + ) + })?; + Ok(function) +} diff --git a/mm2src/coins/eth/nft_swap_v2/structs.rs b/mm2src/coins/eth/nft_swap_v2/structs.rs new file mode 100644 index 0000000000..a0c129bf4b --- /dev/null +++ b/mm2src/coins/eth/nft_swap_v2/structs.rs @@ -0,0 +1,33 @@ +use ethereum_types::{Address, U256}; + +pub(crate) struct ExpectedHtlcParams { + pub(crate) swap_id: Vec, + pub(crate) taker_address: Address, + pub(crate) token_address: Address, + pub(crate) taker_secret_hash: Vec, + pub(crate) maker_secret_hash: Vec, + pub(crate) time_lock: U256, +} + +pub(crate) struct ValidationParams<'a> { + pub(crate) maker_address: Address, + pub(crate) etomic_swap_contract: Address, + pub(crate) token_id: &'a [u8], + // Optional, as it's not needed for ERC721 + pub(crate) amount: Option, +} + +#[allow(dead_code)] +pub(crate) enum PaymentType { + MakerPayments, + TakerPayments, +} + +impl PaymentType { + pub(crate) fn as_str(&self) -> &'static str { + match self { + PaymentType::MakerPayments => "makerPayments", + PaymentType::TakerPayments => "takerPayments", + } + } +} diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 3e68bc83b6..817fda70c1 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -341,13 +341,14 @@ pub const INVALID_SWAP_ID_ERR_LOG: &str = "Invalid swap id"; pub const INVALID_SCRIPT_ERR_LOG: &str = "Invalid script"; pub const INVALID_REFUND_TX_ERR_LOG: &str = "Invalid refund transaction"; -#[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] +#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum RawTransactionError { #[display(fmt = "No such coin {}", coin)] NoSuchCoin { coin: String }, #[display(fmt = "Invalid hash: {}", _0)] InvalidHashError(String), + #[from_stringify("web3::Error")] #[display(fmt = "Transport error: {}", _0)] Transport(String), #[display(fmt = "Hash does not exist: {}", _0)] @@ -356,6 +357,7 @@ pub enum RawTransactionError { InternalError(String), #[display(fmt = "Transaction decode error: {}", _0)] DecodeError(String), + #[from_stringify("NumConversError", "FromHexError")] #[display(fmt = "Invalid param: {}", _0)] InvalidParam(String), #[display(fmt = "Non-existent previous output: {}", _0)] @@ -395,14 +397,6 @@ impl From for RawTransactionError { } } -impl From for RawTransactionError { - fn from(e: NumConversError) -> Self { RawTransactionError::InvalidParam(e.to_string()) } -} - -impl From for RawTransactionError { - fn from(e: FromHexError) -> Self { RawTransactionError::InvalidParam(e.to_string()) } -} - #[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum GetMyAddressError { @@ -519,7 +513,7 @@ pub struct MyWalletAddress { pub type SignatureResult = Result>; pub type VerificationResult = Result>; -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum TxHistoryError { ErrorSerializing(String), ErrorDeserializing(String), @@ -531,6 +525,7 @@ pub enum TxHistoryError { internal_id: BytesJson, }, NotSupported(String), + #[from_stringify("MyAddressError")] InternalError(String), } @@ -633,14 +628,15 @@ pub enum TxMarshalingErr { Internal(String), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, EnumFromStringify)] #[allow(clippy::large_enum_variant)] pub enum TransactionErr { /// Keeps transactions while throwing errors. TxRecoverable(TransactionEnum, String), /// Simply for plain error messages. + #[from_stringify("keys::Error")] Plain(String), - NftProtocolNotSupported(String), + ProtocolNotSupported(String), } impl TransactionErr { @@ -658,16 +654,11 @@ impl TransactionErr { pub fn get_plain_text_format(&self) -> String { match self { TransactionErr::TxRecoverable(_, err) => err.to_string(), - TransactionErr::Plain(err) => err.to_string(), - TransactionErr::NftProtocolNotSupported(err) => err.to_string(), + TransactionErr::Plain(err) | TransactionErr::ProtocolNotSupported(err) => err.to_string(), } } } -impl From for TransactionErr { - fn from(e: keys::Error) -> Self { TransactionErr::Plain(e.to_string()) } -} - #[derive(Debug, PartialEq)] pub enum FoundSwapTxSpend { Spent(TransactionEnum), @@ -1010,17 +1001,14 @@ pub struct PaymentInstructionArgs<'a> { pub wait_until: u64, } -#[derive(Display)] +#[derive(Display, EnumFromStringify)] pub enum PaymentInstructionsErr { LightningInvoiceErr(String), WatcherRewardErr(String), + #[from_stringify("NumConversError")] InternalError(String), } -impl From for PaymentInstructionsErr { - fn from(e: NumConversError) -> Self { PaymentInstructionsErr::InternalError(e.to_string()) } -} - #[derive(Display)] pub enum ValidateInstructionsErr { ValidateLightningInvoiceErr(String), @@ -1250,7 +1238,7 @@ pub struct SendTakerFundingArgs<'a> { } /// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::refund_taker_funding_secret] -pub struct RefundFundingSecretArgs<'a, Coin: CoinAssocTypes + ?Sized> { +pub struct RefundFundingSecretArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub funding_tx: &'a Coin::Tx, pub time_lock: u64, pub maker_pubkey: &'a Coin::Pubkey, @@ -1262,7 +1250,7 @@ pub struct RefundFundingSecretArgs<'a, Coin: CoinAssocTypes + ?Sized> { } /// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::gen_taker_funding_spend_preimage] -pub struct GenTakerFundingSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { +pub struct GenTakerFundingSpendArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Taker payment transaction serialized to raw bytes pub funding_tx: &'a Coin::Tx, /// Maker's pubkey @@ -1280,7 +1268,7 @@ pub struct GenTakerFundingSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { } /// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::validate_taker_funding] -pub struct ValidateTakerFundingArgs<'a, Coin: CoinAssocTypes + ?Sized> { +pub struct ValidateTakerFundingArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Taker funding transaction pub funding_tx: &'a Coin::Tx, /// Taker will be able to refund the payment after this timestamp @@ -1302,7 +1290,7 @@ pub struct ValidateTakerFundingArgs<'a, Coin: CoinAssocTypes + ?Sized> { /// Helper struct wrapping arguments for taker payment's spend generation, used in /// [TakerCoinSwapOpsV2::gen_taker_payment_spend_preimage], [TakerCoinSwapOpsV2::validate_taker_payment_spend_preimage] and /// [TakerCoinSwapOpsV2::sign_and_broadcast_taker_payment_spend] -pub struct GenTakerPaymentSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { +pub struct GenTakerPaymentSpendArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Taker payment transaction serialized to raw bytes pub taker_tx: &'a Coin::Tx, /// Taker will be able to refund the payment after this timestamp @@ -1326,7 +1314,7 @@ pub struct GenTakerPaymentSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { } /// Taker payment spend preimage with taker's signature -pub struct TxPreimageWithSig { +pub struct TxPreimageWithSig { /// The preimage, might be () for certain coin types (only signature might be used) pub preimage: Coin::Preimage, /// Taker's signature @@ -1397,7 +1385,7 @@ impl From for ValidateSwapV2TxError { } /// Enum covering error cases that can happen during taker funding spend preimage validation. -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum ValidateTakerFundingSpendPreimageError { /// Funding tx has no outputs FundingTxNoOutputs, @@ -1408,37 +1396,30 @@ pub enum ValidateTakerFundingSpendPreimageError { /// Error during preimage comparison to an expected one. InvalidPreimage(String), /// Error during taker's signature check. + #[from_stringify("UtxoSignWithKeyPairError")] SignatureVerificationFailure(String), /// Error during generation of an expected preimage. TxGenError(String), /// Input payment timelock overflows the type used by specific coin. LocktimeOverflow(String), /// Coin's RPC error + #[from_stringify("UtxoRpcError")] Rpc(String), } -impl From for ValidateTakerFundingSpendPreimageError { - fn from(err: UtxoSignWithKeyPairError) -> Self { - ValidateTakerFundingSpendPreimageError::SignatureVerificationFailure(err.to_string()) - } -} - impl From for ValidateTakerFundingSpendPreimageError { fn from(err: TxGenError) -> Self { ValidateTakerFundingSpendPreimageError::TxGenError(format!("{:?}", err)) } } -impl From for ValidateTakerFundingSpendPreimageError { - fn from(err: UtxoRpcError) -> Self { ValidateTakerFundingSpendPreimageError::Rpc(err.to_string()) } -} - /// Enum covering error cases that can happen during taker payment spend preimage validation. -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum ValidateTakerPaymentSpendPreimageError { /// Error during signature deserialization. InvalidTakerSignature, /// Error during preimage comparison to an expected one. InvalidPreimage(String), /// Error during taker's signature check. + #[from_stringify("UtxoSignWithKeyPairError")] SignatureVerificationFailure(String), /// Error during generation of an expected preimage. TxGenError(String), @@ -1446,12 +1427,6 @@ pub enum ValidateTakerPaymentSpendPreimageError { LocktimeOverflow(String), } -impl From for ValidateTakerPaymentSpendPreimageError { - fn from(err: UtxoSignWithKeyPairError) -> Self { - ValidateTakerPaymentSpendPreimageError::SignatureVerificationFailure(err.to_string()) - } -} - impl From for ValidateTakerPaymentSpendPreimageError { fn from(err: TxGenError) -> Self { ValidateTakerPaymentSpendPreimageError::TxGenError(format!("{:?}", err)) } } @@ -1462,7 +1437,7 @@ pub trait ToBytes { } /// Defines associated types specific to each coin (Pubkey, Address, etc.) -pub trait CoinAssocTypes { +pub trait ParseCoinAssocTypes { type Address: Send + Sync + fmt::Display; type AddressParseError: fmt::Debug + Send + fmt::Display; type Pubkey: ToBytes + Send + Sync; @@ -1487,7 +1462,51 @@ pub trait CoinAssocTypes { fn parse_signature(&self, sig: &[u8]) -> Result; } -pub struct SendMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { +/// Defines associated types specific to Non-Fungible Tokens (Token Address, Token Id, etc.) +pub trait ParseNftAssocTypes { + type ContractAddress: Send + Sync + fmt::Display; + type TokenId: ToBytes + Send + Sync; + type ContractType: ToBytes + Send + Sync; + type NftAssocTypesError: fmt::Debug + Send + fmt::Display; + + fn parse_contract_address( + &self, + contract_address: &[u8], + ) -> Result; + + fn parse_token_id(&self, token_id: &[u8]) -> Result; + + fn parse_contract_type(&self, contract_type: &[u8]) -> Result; +} + +pub struct SendMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { + /// Maker will be able to refund the payment after this timestamp + pub time_lock: u64, + /// The hash of the secret generated by taker, this is used for immediate refund + pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by maker, taker needs it to spend the payment + pub maker_secret_hash: &'a [u8], + /// Payment amount + pub amount: BigDecimal, + /// Taker's HTLC pubkey + pub taker_pub: &'a Coin::Pubkey, + /// Unique data of specific swap + pub swap_unique_data: &'a [u8], +} + +/// Structure representing necessary NFT info for Swap +pub struct NftSwapInfo<'a, Coin: ParseNftAssocTypes + ?Sized> { + /// The address of the NFT token + pub token_address: &'a Coin::ContractAddress, + /// The ID of the NFT token. + pub token_id: &'a [u8], + /// The type of smart contract that governs this NFT + pub contract_type: &'a Coin::ContractType, + /// Etomic swap contract address + pub swap_contract_address: &'a Coin::ContractAddress, +} + +pub struct SendNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { /// Maker will be able to refund the payment after this timestamp pub time_lock: u64, /// The hash of the secret generated by taker, this is used for immediate refund @@ -1500,9 +1519,28 @@ pub struct SendMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { pub taker_pub: &'a Coin::Pubkey, /// Unique data of specific swap pub swap_unique_data: &'a [u8], + /// Structure representing necessary NFT info for Swap + pub nft_swap_info: &'a NftSwapInfo<'a, Coin>, +} + +pub struct ValidateMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { + /// Maker payment tx + pub maker_payment_tx: &'a Coin::Tx, + /// Maker will be able to refund the payment after this timestamp + pub time_lock: u64, + /// The hash of the secret generated by taker, this is used for immediate refund + pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by maker, taker needs it to spend the payment + pub maker_secret_hash: &'a [u8], + /// Payment amount + pub amount: BigDecimal, + /// Maker's HTLC pubkey + pub maker_pub: &'a Coin::Pubkey, + /// Unique data of specific swap + pub swap_unique_data: &'a [u8], } -pub struct ValidateMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { +pub struct ValidateNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { /// Maker payment tx pub maker_payment_tx: &'a Coin::Tx, /// Maker will be able to refund the payment after this timestamp @@ -1513,13 +1551,17 @@ pub struct ValidateMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { pub maker_secret_hash: &'a [u8], /// Payment amount pub amount: BigDecimal, + /// Taker's HTLC pubkey + pub taker_pub: &'a Coin::Pubkey, /// Maker's HTLC pubkey pub maker_pub: &'a Coin::Pubkey, /// Unique data of specific swap pub swap_unique_data: &'a [u8], + /// Structure representing necessary NFT info for Swap + pub nft_swap_info: &'a NftSwapInfo<'a, Coin>, } -pub struct RefundMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { +pub struct RefundMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Maker payment tx pub maker_payment_tx: &'a Coin::Tx, /// Maker will be able to refund the payment after this timestamp @@ -1536,7 +1578,24 @@ pub struct RefundMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { pub swap_unique_data: &'a [u8], } -pub struct SpendMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { +pub struct SpendMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { + /// Maker payment tx + pub maker_payment_tx: &'a Coin::Tx, + /// Maker will be able to refund the payment after this timestamp + pub time_lock: u64, + /// The hash of the secret generated by taker, this is used for immediate refund + pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by maker, taker needs it to spend the payment + pub maker_secret_hash: &'a [u8], + /// The secret generated by maker, revealed when maker spends taker's payment + pub maker_secret: &'a [u8], + /// Maker's HTLC pubkey + pub maker_pub: &'a Coin::Pubkey, + /// Unique data of specific swap + pub swap_unique_data: &'a [u8], +} + +pub struct SpendNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { /// Maker payment tx pub maker_payment_tx: &'a Coin::Tx, /// Maker will be able to refund the payment after this timestamp @@ -1551,11 +1610,15 @@ pub struct SpendMakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { pub maker_pub: &'a Coin::Pubkey, /// Unique data of specific swap pub swap_unique_data: &'a [u8], + /// The type of smart contract that governs this NFT + pub contract_type: &'a Coin::ContractType, + /// Etomic swap contract address + pub swap_contract_address: &'a Coin::ContractAddress, } /// Operations specific to maker coin in [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait MakerCoinSwapOpsV2: CoinAssocTypes + Send + Sync + 'static { +pub trait MakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { /// Generate and broadcast maker payment transaction async fn send_maker_payment_v2(&self, args: SendMakerPaymentArgs<'_, Self>) -> Result; @@ -1575,6 +1638,38 @@ pub trait MakerCoinSwapOpsV2: CoinAssocTypes + Send + Sync + 'static { async fn spend_maker_payment_v2(&self, args: SpendMakerPaymentArgs<'_, Self>) -> Result; } +#[async_trait] +pub trait MakerNftSwapOpsV2: ParseCoinAssocTypes + ParseNftAssocTypes + Send + Sync + 'static { + async fn send_nft_maker_payment_v2( + &self, + args: SendNftMakerPaymentArgs<'_, Self>, + ) -> Result; + + /// Validate NFT maker payment transaction + async fn validate_nft_maker_payment_v2( + &self, + args: ValidateNftMakerPaymentArgs<'_, Self>, + ) -> ValidatePaymentResult<()>; + + /// Spend NFT maker payment transaction + async fn spend_nft_maker_payment_v2( + &self, + args: SpendNftMakerPaymentArgs<'_, Self>, + ) -> Result; + + /// Refund NFT maker payment transaction using timelock path + async fn refund_nft_maker_payment_v2_timelock( + &self, + args: RefundPaymentArgs<'_>, + ) -> Result; + + /// Refund NFT maker payment transaction using immediate refund path + async fn refund_nft_maker_payment_v2_secret( + &self, + args: RefundMakerPaymentArgs<'_, Self>, + ) -> Result; +} + /// Enum representing errors that can occur while waiting for taker payment spend. #[derive(Display)] pub enum WaitForTakerPaymentSpendError { @@ -1610,8 +1705,8 @@ impl From for WaitForTakerPaymentSpendError { /// Enum representing different ways a funding transaction can be spent. /// -/// This enum is generic over types that implement the `CoinAssocTypes` trait. -pub enum FundingTxSpend { +/// This enum is generic over types that implement the `ParseCoinAssocTypes` trait. +pub enum FundingTxSpend { /// Variant indicating that the funding transaction has been spent through a timelock path. RefundedTimelock(T::Tx), /// Variant indicating that the funding transaction has been spent by revealing a taker's secret (immediate refund path). @@ -1626,7 +1721,7 @@ pub enum FundingTxSpend { TransferredToTakerPayment(T::Tx), } -impl fmt::Debug for FundingTxSpend { +impl fmt::Debug for FundingTxSpend { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FundingTxSpend::RefundedTimelock(tx) => { @@ -1657,7 +1752,7 @@ pub enum SearchForFundingSpendErr { /// Operations specific to taker coin in [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait TakerCoinSwapOpsV2: CoinAssocTypes + Send + Sync + 'static { +pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { /// Generate and broadcast taker funding transaction that includes dex fee, maker premium and actual trading volume. /// Funding tx can be reclaimed immediately if maker back-outs (doesn't send maker payment) async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result; @@ -2254,19 +2349,11 @@ pub struct SwapTxFeePolicyRequest { swap_tx_fee_policy: SwapTxFeePolicy, } -#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[derive(Debug, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum SwapTxFeePolicyError { - #[display(fmt = "No such coin {}", coin)] - NoSuchCoin { coin: String }, -} - -impl From for SwapTxFeePolicyError { - fn from(e: CoinFindError) -> Self { - match e { - CoinFindError::NoSuchCoin { coin } => SwapTxFeePolicyError::NoSuchCoin { coin }, - } - } + #[from_stringify("CoinFindError")] + NoSuchCoin(String), } impl HttpStatusCode for SwapTxFeePolicyError { @@ -2279,7 +2366,7 @@ impl HttpStatusCode for SwapTxFeePolicyError { pub type SwapTxFeePolicyResult = Result>; -#[derive(Debug, Display, PartialEq)] +#[derive(Debug, Display, EnumFromStringify, PartialEq)] pub enum TradePreimageError { #[display( fmt = "Not enough {} to preimage the trade: available {}, required at least {}", @@ -2296,20 +2383,13 @@ pub enum TradePreimageError { AmountIsTooSmall { amount: BigDecimal, threshold: BigDecimal }, #[display(fmt = "Transport error: {}", _0)] Transport(String), + #[from_stringify("NumConversError", "UnexpectedDerivationMethod")] #[display(fmt = "Internal error: {}", _0)] InternalError(String), #[display(fmt = "Nft Protocol is not supported yet!")] NftProtocolNotSupported, } -impl From for TradePreimageError { - fn from(e: NumConversError) -> Self { TradePreimageError::InternalError(e.to_string()) } -} - -impl From for TradePreimageError { - fn from(e: UnexpectedDerivationMethod) -> Self { TradePreimageError::InternalError(e.to_string()) } -} - impl TradePreimageError { /// Construct [`TradePreimageError`] from [`GenerateTxError`] using additional `coin` and `decimals`. pub fn from_generate_tx_error( @@ -2378,7 +2458,7 @@ impl TradePreimageError { } /// The reason of unsuccessful conversion of two internal numbers, e.g. `u64` from `BigNumber`. -#[derive(Debug, Display)] +#[derive(Clone, Debug, Display)] pub struct NumConversError(String); impl From for NumConversError { @@ -2391,7 +2471,7 @@ impl NumConversError { pub fn description(&self) -> &str { &self.0 } } -#[derive(Clone, Debug, Display, PartialEq, Serialize, SerializeErrorType)] +#[derive(Clone, Debug, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum BalanceError { #[display(fmt = "Transport: {}", _0)] @@ -2401,6 +2481,7 @@ pub enum BalanceError { UnexpectedDerivationMethod(UnexpectedDerivationMethod), #[display(fmt = "Wallet storage error: {}", _0)] WalletStorageError(String), + #[from_stringify("Bip32Error", "NumConversError")] #[display(fmt = "Internal: {}", _0)] Internal(String), } @@ -2417,25 +2498,18 @@ impl From for GetNonZeroBalance { fn from(e: BalanceError) -> Self { GetNonZeroBalance::MyBalanceError(e) } } -impl From for BalanceError { - fn from(e: NumConversError) -> Self { BalanceError::Internal(e.to_string()) } -} - impl From for BalanceError { fn from(e: UnexpectedDerivationMethod) -> Self { BalanceError::UnexpectedDerivationMethod(e) } } -impl From for BalanceError { - fn from(e: Bip32Error) -> Self { BalanceError::Internal(e.to_string()) } -} - -#[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] +#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum StakingInfosError { #[display(fmt = "Staking infos not available for: {}", coin)] CoinDoesntSupportStakingInfos { coin: String }, #[display(fmt = "No such coin {}", coin)] NoSuchCoin { coin: String }, + #[from_stringify("UnexpectedDerivationMethod")] #[display(fmt = "Derivation method is not supported: {}", _0)] UnexpectedDerivationMethod(String), #[display(fmt = "Transport error: {}", _0)] @@ -2456,10 +2530,6 @@ impl From for StakingInfosError { } } -impl From for StakingInfosError { - fn from(e: UnexpectedDerivationMethod) -> Self { StakingInfosError::UnexpectedDerivationMethod(e.to_string()) } -} - impl From for StakingInfosError { fn from(e: Qrc20AddressError) -> Self { match e { @@ -2491,7 +2561,7 @@ impl From for StakingInfosError { } } -#[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] +#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum DelegationError { #[display( @@ -2513,6 +2583,7 @@ pub enum DelegationError { NoSuchCoin { coin: String }, #[display(fmt = "{}", _0)] CannotInteractWithSmartContract(String), + #[from_stringify("ScriptHashTypeNotSupported")] #[display(fmt = "{}", _0)] AddressError(String), #[display(fmt = "Already delegating to: {}", _0)] @@ -2521,6 +2592,7 @@ pub enum DelegationError { DelegationOpsNotSupported { reason: String }, #[display(fmt = "Transport error: {}", _0)] Transport(String), + #[from_stringify("MyAddressError")] #[display(fmt = "Internal error: {}", _0)] InternalError(String), } @@ -2591,10 +2663,6 @@ impl From for DelegationError { } } -impl From for DelegationError { - fn from(e: ScriptHashTypeNotSupported) -> Self { DelegationError::AddressError(e.to_string()) } -} - impl HttpStatusCode for DelegationError { fn status_code(&self) -> StatusCode { match self { @@ -2719,7 +2787,12 @@ pub enum WithdrawError { #[display(fmt = "Transport error: {}", _0)] Transport(String), #[from_trait(WithInternal::internal)] - #[from_stringify("NumConversError", "UnexpectedDerivationMethod", "PrivKeyPolicyNotAllowed")] + #[from_stringify( + "MyAddressError", + "NumConversError", + "UnexpectedDerivationMethod", + "PrivKeyPolicyNotAllowed" + )] #[display(fmt = "Internal error: {}", _0)] InternalError(String), #[display(fmt = "Unsupported error: {}", _0)] @@ -2928,17 +3001,21 @@ impl HttpStatusCode for SignatureError { } } -#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[derive(Debug, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum VerificationError { #[display(fmt = "Invalid request: {}", _0)] InvalidRequest(String), + #[from_stringify("ethkey::Error", "keys::Error")] #[display(fmt = "Internal error: {}", _0)] InternalError(String), + #[from_stringify("base64::DecodeError")] #[display(fmt = "Signature decoding error: {}", _0)] SignatureDecodingError(String), + #[from_stringify("hex::FromHexError")] #[display(fmt = "Address decoding error: {}", _0)] AddressDecodingError(String), + #[from_stringify("CoinFindError")] #[display(fmt = "Coin is not found: {}", _0)] CoinIsNotFound(String), #[display(fmt = "sign_message_prefix is not set in coin config")] @@ -2958,14 +3035,6 @@ impl HttpStatusCode for VerificationError { } } -impl From for VerificationError { - fn from(e: base64::DecodeError) -> Self { VerificationError::SignatureDecodingError(e.to_string()) } -} - -impl From for VerificationError { - fn from(e: hex::FromHexError) -> Self { VerificationError::AddressDecodingError(e.to_string()) } -} - impl From for VerificationError { fn from(e: FromBase58Error) -> Self { match e { @@ -2979,18 +3048,6 @@ impl From for VerificationError { } } -impl From for VerificationError { - fn from(e: keys::Error) -> Self { VerificationError::InternalError(e.to_string()) } -} - -impl From for VerificationError { - fn from(e: ethkey::Error) -> Self { VerificationError::InternalError(e.to_string()) } -} - -impl From for VerificationError { - fn from(e: CoinFindError) -> Self { VerificationError::CoinIsNotFound(e.to_string()) } -} - /// NB: Implementations are expected to follow the pImpl idiom, providing cheap reference-counted cloning and garbage collection. #[async_trait] pub trait MmCoin: @@ -3844,7 +3901,7 @@ pub enum CoinProtocol { decimals: u8, }, ZHTLC(ZcoinProtocolInfo), - Nft { + NFT { platform: String, }, } @@ -4099,7 +4156,7 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result return ERR!("TENDERMINT protocol is not supported by lp_coininit"), CoinProtocol::TENDERMINTTOKEN(_) => return ERR!("TENDERMINTTOKEN protocol is not supported by lp_coininit"), CoinProtocol::ZHTLC { .. } => return ERR!("ZHTLC protocol is not supported by lp_coininit"), - CoinProtocol::Nft { .. } => return ERR!("NFT protocol is not supported by lp_coininit"), + CoinProtocol::NFT { .. } => return ERR!("NFT protocol is not supported by lp_coininit"), #[cfg(not(target_arch = "wasm32"))] CoinProtocol::LIGHTNING { .. } => return ERR!("Lightning protocol is not supported by lp_coininit"), #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] @@ -4643,7 +4700,7 @@ pub fn address_by_coin_conf_and_pubkey_str( ) -> Result { let protocol: CoinProtocol = try_s!(json::from_value(conf["protocol"].clone())); match protocol { - CoinProtocol::ERC20 { .. } | CoinProtocol::ETH | CoinProtocol::Nft { .. } => eth::addr_from_pubkey_str(pubkey), + CoinProtocol::ERC20 { .. } | CoinProtocol::ETH | CoinProtocol::NFT { .. } => eth::addr_from_pubkey_str(pubkey), CoinProtocol::UTXO | CoinProtocol::QTUM | CoinProtocol::QRC20 { .. } | CoinProtocol::BCH { .. } => { utxo::address_by_conf_and_pubkey_str(coin, conf, pubkey, addr_format) }, diff --git a/mm2src/coins/my_tx_history_v2.rs b/mm2src/coins/my_tx_history_v2.rs index 6a0ddeaa15..5ce2a90035 100644 --- a/mm2src/coins/my_tx_history_v2.rs +++ b/mm2src/coins/my_tx_history_v2.rs @@ -3,6 +3,7 @@ use crate::tendermint::{TENDERMINT_ASSET_PROTOCOL_TYPE, TENDERMINT_COIN_PROTOCOL use crate::tx_history_storage::{CreateTxHistoryStorageError, FilteringAddresses, GetTxHistoryFilters, TxHistoryStorageBuilder, WalletId}; use crate::utxo::utxo_common::big_decimal_from_sat_unsigned; +use crate::MyAddressError; use crate::{coin_conf, lp_coinfind_or_err, BlockHeightAndTime, CoinFindError, HDAccountAddressId, HistorySyncState, MmCoin, MmCoinEnum, Transaction, TransactionDetails, TransactionType, TxFeeDetails, UtxoRpcError}; use async_trait::async_trait; @@ -10,6 +11,7 @@ use bitcrypto::sha256; use common::{calc_total_pages, ten, HttpStatusCode, PagingOptionsEnum, StatusCode}; use crypto::StandardHDPath; use derive_more::Display; +use enum_derives::EnumFromStringify; use futures::compat::Future01CompatExt; use keys::{Address, CashAddress}; use mm2_core::mm_ctx::MmArc; @@ -304,15 +306,19 @@ pub struct MyTxHistoryResponseV2 { pub(crate) paging_options: PagingOptionsEnum, } -#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[derive(Debug, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum MyTxHistoryErrorV2 { CoinIsNotActive(String), + #[from_stringify("InvalidBip44ChainError")] InvalidTarget(String), StorageIsNotInitialized(String), + #[from_stringify("CreateTxHistoryStorageError")] StorageError(String), + #[from_stringify("UtxoRpcError")] RpcError(String), NotSupportedFor(String), + #[from_stringify("MyAddressError")] Internal(String), } @@ -350,14 +356,6 @@ impl From for MyTxHistoryErrorV2 { } } -impl From for MyTxHistoryErrorV2 { - fn from(e: CreateTxHistoryStorageError) -> Self { MyTxHistoryErrorV2::StorageError(e.to_string()) } -} - -impl From for MyTxHistoryErrorV2 { - fn from(err: UtxoRpcError) -> Self { MyTxHistoryErrorV2::RpcError(err.to_string()) } -} - impl From for MyTxHistoryErrorV2 { fn from(e: AddressDerivingError) -> Self { match e { @@ -368,10 +366,6 @@ impl From for MyTxHistoryErrorV2 { } } -impl From for MyTxHistoryErrorV2 { - fn from(e: InvalidBip44ChainError) -> Self { MyTxHistoryErrorV2::InvalidTarget(e.to_string()) } -} - #[async_trait] pub trait CoinWithTxHistoryV2 { fn history_wallet_id(&self) -> WalletId; diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index ba1c31c0d2..8a6fb90f19 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -36,6 +36,7 @@ pub enum GetNftInfoError { token_address: String, token_id: String, }, + #[from_stringify("LockDBError")] #[display(fmt = "DB error {}", _0)] DbError(String), ParseRfc3339Err(ParseRfc3339Err), @@ -43,6 +44,7 @@ pub enum GetNftInfoError { ContractTypeIsNull, ProtectFromSpamError(ProtectFromSpamError), TransferConfirmationsError(TransferConfirmationsError), + #[from_stringify("NumConversError")] NumConversError(String), } @@ -102,10 +104,6 @@ impl From for GetNftInfoError { fn from(e: ProtectFromSpamError) -> Self { GetNftInfoError::ProtectFromSpamError(e) } } -impl From for GetNftInfoError { - fn from(e: LockDBError) -> Self { GetNftInfoError::DbError(e.to_string()) } -} - impl From for GetNftInfoError { fn from(e: TransferConfirmationsError) -> Self { GetNftInfoError::TransferConfirmationsError(e) } } @@ -118,10 +116,6 @@ impl From for GetNftInfoError { } } -impl From for GetNftInfoError { - fn from(e: NumConversError) -> Self { GetNftInfoError::NumConversError(e.to_string()) } -} - impl HttpStatusCode for GetNftInfoError { fn status_code(&self) -> StatusCode { match self { @@ -154,6 +148,7 @@ impl HttpStatusCode for GetNftInfoError { #[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum UpdateNftError { + #[from_stringify("LockDBError")] #[display(fmt = "DB error {}", _0)] DbError(String), #[display(fmt = "Internal: {}", _0)] @@ -213,10 +208,6 @@ pub enum UpdateNftError { CoinDoesntSupportNft { coin: String, }, - #[display(fmt = "Global NFT type mismatch for token '{}'", token)] - GlobalNftTypeMismatch { - token: String, - }, } impl From for UpdateNftError { @@ -243,10 +234,6 @@ impl From for UpdateNftError { fn from(e: ProtectFromSpamError) -> Self { UpdateNftError::ProtectFromSpamError(e) } } -impl From for UpdateNftError { - fn from(e: LockDBError) -> Self { UpdateNftError::DbError(e.to_string()) } -} - impl From for UpdateNftError { fn from(e: CoinFindError) -> Self { match e { @@ -273,8 +260,7 @@ impl HttpStatusCode for UpdateNftError { | UpdateNftError::SerdeError(_) | UpdateNftError::ProtectFromSpamError(_) | UpdateNftError::NoSuchCoin { .. } - | UpdateNftError::CoinDoesntSupportNft { .. } - | UpdateNftError::GlobalNftTypeMismatch { .. } => StatusCode::INTERNAL_SERVER_ERROR, + | UpdateNftError::CoinDoesntSupportNft { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } } @@ -394,10 +380,11 @@ impl From for TransferConfirmationsError { } /// Enumerates errors that can occur while clearing NFT data from the database. -#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize, SerializeErrorType)] +#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum ClearNftDbError { /// Represents errors related to database operations. + #[from_stringify("LockDBError")] #[display(fmt = "DB error {}", _0)] DbError(String), /// Indicates internal errors not directly associated with database operations. @@ -412,10 +399,6 @@ impl From for ClearNftDbError { fn from(err: T) -> Self { ClearNftDbError::DbError(format!("{:?}", err)) } } -impl From for ClearNftDbError { - fn from(e: LockDBError) -> Self { ClearNftDbError::DbError(e.to_string()) } -} - impl HttpStatusCode for ClearNftDbError { fn status_code(&self) -> StatusCode { match self { diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index baf6c0c65d..0096e8f2fe 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -114,9 +114,9 @@ pub enum Chain { pub trait ConvertChain { fn to_ticker(&self) -> &'static str; - fn from_ticker(s: &str) -> MmResult; + fn from_ticker(s: &str) -> Result; fn to_nft_ticker(&self) -> &'static str; - fn from_nft_ticker(s: &str) -> MmResult; + fn from_nft_ticker(s: &str) -> Result; } impl ConvertChain for Chain { @@ -133,14 +133,14 @@ impl ConvertChain for Chain { /// Converts a coin ticker string to a `Chain` enum. #[inline(always)] - fn from_ticker(s: &str) -> MmResult { + fn from_ticker(s: &str) -> Result { match s { "AVAX" | "avax" => Ok(Chain::Avalanche), "BNB" | "bnb" => Ok(Chain::Bsc), "ETH" | "eth" => Ok(Chain::Eth), "FTM" | "ftm" => Ok(Chain::Fantom), "MATIC" | "matic" => Ok(Chain::Polygon), - _ => MmError::err(ParseChainTypeError::UnsupportedChainType), + _ => Err(ParseChainTypeError::UnsupportedChainType), } } @@ -157,14 +157,14 @@ impl ConvertChain for Chain { /// Converts a NFT ticker string to a `Chain` enum. #[inline(always)] - fn from_nft_ticker(s: &str) -> MmResult { + fn from_nft_ticker(s: &str) -> Result { match s.to_uppercase().as_str() { "NFT_AVAX" => Ok(Chain::Avalanche), "NFT_BNB" => Ok(Chain::Bsc), "NFT_ETH" => Ok(Chain::Eth), "NFT_FTM" => Ok(Chain::Fantom), "NFT_MATIC" => Ok(Chain::Polygon), - _ => MmError::err(ParseChainTypeError::UnsupportedChainType), + _ => Err(ParseChainTypeError::UnsupportedChainType), } } } @@ -817,15 +817,15 @@ pub struct ClearNftDbReq { #[derive(Clone, Debug, Serialize)] pub struct NftInfo { /// The address of the NFT token. - pub(crate) token_address: Address, + pub token_address: Address, /// The ID of the NFT token. #[serde(serialize_with = "serialize_token_id")] - pub(crate) token_id: BigUint, + pub token_id: BigUint, /// The blockchain where the NFT exists. - pub(crate) chain: Chain, + pub chain: Chain, /// The type of smart contract that governs this NFT. - pub(crate) contract_type: ContractType, + pub contract_type: ContractType, /// The quantity of this type of NFT owned. Particularly relevant for ERC-1155 tokens, /// where a single token ID can represent multiple assets. - pub(crate) amount: BigDecimal, + pub amount: BigDecimal, } diff --git a/mm2src/coins/solana/solana_common_tests.rs b/mm2src/coins/solana/solana_common_tests.rs index 32b030b425..7706cc8abf 100644 --- a/mm2src/coins/solana/solana_common_tests.rs +++ b/mm2src/coins/solana/solana_common_tests.rs @@ -1,7 +1,6 @@ use super::*; use crate::solana::spl::{SplToken, SplTokenFields}; -use bip39::Language; -use crypto::privkey::key_pair_from_seed; +use crypto::privkey::{bip39_seed_from_passphrase, key_pair_from_seed}; use ed25519_dalek_bip32::{DerivationPath, ExtendedSecretKey}; use mm2_core::mm_ctx::MmCtxBuilder; use solana_client::rpc_client::RpcClient; @@ -22,13 +21,11 @@ pub fn solana_net_to_url(net_type: SolanaNet) -> String { } } -pub fn generate_key_pair_from_seed(seed: String) -> Keypair { +pub fn generate_key_pair_from_seed(seed: &str) -> Keypair { let derivation_path = DerivationPath::from_str("m/44'/501'/0'").unwrap(); - let mnemonic = bip39::Mnemonic::from_phrase(seed.as_str(), Language::English).unwrap(); - let seed = bip39::Seed::new(&mnemonic, ""); - let seed_bytes: &[u8] = seed.as_bytes(); + let seed = bip39_seed_from_passphrase(seed).unwrap(); - let ext = ExtendedSecretKey::from_seed(seed_bytes) + let ext = ExtendedSecretKey::from_seed(&seed.0) .unwrap() .derive(&derivation_path) .unwrap(); diff --git a/mm2src/coins/solana/solana_tests.rs b/mm2src/coins/solana/solana_tests.rs index 05939b68c5..2f88f1df0d 100644 --- a/mm2src/coins/solana/solana_tests.rs +++ b/mm2src/coins/solana/solana_tests.rs @@ -35,9 +35,8 @@ fn solana_keypair_from_secp() { fn solana_prerequisites() { // same test as trustwallet { - let fin = generate_key_pair_from_seed( - "hood vacant left trim hard mushroom device flavor ask better arrest again".to_string(), - ); + let fin = + generate_key_pair_from_seed("hood vacant left trim hard mushroom device flavor ask better arrest again"); let public_address = fin.pubkey().to_string(); let priv_key = &fin.secret().to_bytes()[..].to_base58(); assert_eq!(public_address.len(), 44); diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index bdbb18abb2..dcd33f9e91 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -4,10 +4,10 @@ use super::{CoinBalance, FundingTxSpend, HistorySyncState, MarketCoinOps, MmCoin RawTransactionRequest, SearchForFundingSpendErr, SwapOps, TradeFee, TransactionEnum, TransactionFut, WaitForTakerPaymentSpendError}; use crate::coin_errors::ValidatePaymentResult; -use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, - CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, - GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerSwapTakerCoin, MmCoinEnum, - NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, +use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, + ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, + GenTakerPaymentSpendArgs, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + ParseCoinAssocTypes, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RawTransactionResult, RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapTxFeePolicy, TakerCoinSwapOpsV2, TakerSwapMakerCoin, @@ -430,7 +430,7 @@ impl ToBytes for TestSig { fn to_bytes(&self) -> Vec { vec![] } } -impl CoinAssocTypes for TestCoin { +impl ParseCoinAssocTypes for TestCoin { type Address = String; type AddressParseError = String; type Pubkey = TestPubkey; diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 8fb5c34a0a..0c70a25063 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -114,7 +114,7 @@ use crate::hd_wallet::{HDAccountOps, HDAccountsMutex, HDAddress, HDAddressId, HD InvalidBip44ChainError}; use crate::hd_wallet_storage::{HDAccountStorageItem, HDWalletCoinStorage, HDWalletStorageError, HDWalletStorageResult}; use crate::utxo::tx_cache::UtxoVerboseCacheShared; -use crate::{CoinAssocTypes, ToBytes}; +use crate::{ParseCoinAssocTypes, ToBytes}; pub mod tx_cache; @@ -1048,7 +1048,7 @@ impl ToBytes for Signature { fn to_bytes(&self) -> Vec { self.to_vec() } } -impl CoinAssocTypes for T { +impl ParseCoinAssocTypes for T { type Address = Address; type AddressParseError = MmError; type Pubkey = Public; diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index 56a1c9289c..a2ed5a8aa5 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -4,7 +4,8 @@ use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; use crate::utxo::{output_script, sat_from_big_decimal, GetBlockHeaderError, GetConfirmedTxError, GetTxError, GetTxHeightError, ScripthashNotification}; -use crate::{big_decimal_from_sat_unsigned, NumConversError, RpcTransportEventHandler, RpcTransportEventHandlerShared}; +use crate::{big_decimal_from_sat_unsigned, MyAddressError, NumConversError, RpcTransportEventHandler, + RpcTransportEventHandlerShared}; use async_trait::async_trait; use chain::{BlockHeader, BlockHeaderBits, BlockHeaderNonce, OutPoint, Transaction as UtxoTx, TransactionInput, TxHashAlgo}; @@ -18,6 +19,7 @@ use common::log::{debug, LogOnError}; use common::log::{error, info, warn}; use common::{median, now_float, now_ms, now_sec, OrdRange}; use derive_more::Display; +use enum_derives::EnumFromStringify; use futures::channel::oneshot as async_oneshot; use futures::compat::{Future01CompatExt, Stream01CompatExt}; use futures::future::{join_all, FutureExt, TryFutureExt}; @@ -292,11 +294,12 @@ pub struct SpentOutputInfo { pub type UtxoRpcResult = Result>; pub type UtxoRpcFut = Box> + Send + 'static>; -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum UtxoRpcError { Transport(JsonRpcError), ResponseParseError(JsonRpcError), InvalidResponse(String), + #[from_stringify("MyAddressError")] Internal(String), } diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 6d29e948e9..1b4cba990e 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -28,6 +28,8 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; use bitcrypto::dhash160; use chain::constants::SEQUENCE_FINAL; use chain::{OutPoint, TransactionOutput}; @@ -1117,7 +1119,7 @@ impl MarketCoinOps for SlpToken { let message_hash = self .sign_message_hash(message) .ok_or(VerificationError::PrefixNotFound)?; - let signature = CompactSignature::from(base64::decode(signature)?); + let signature = CompactSignature::from(STANDARD.decode(signature)?); let pubkey = Public::recover_compact(&H256::from(message_hash), &signature)?; let address_from_pubkey = self.platform_coin.address_from_pubkey(&pubkey); let slp_address = self @@ -2176,8 +2178,7 @@ mod slp_tests { let err = match tx_err.clone() { TransactionErr::TxRecoverable(_tx, err) => err, - TransactionErr::Plain(err) => err, - TransactionErr::NftProtocolNotSupported(err) => err, + TransactionErr::Plain(err) | TransactionErr::ProtocolNotSupported(err) => err, }; println!("{:?}", err); diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index 188a644ba6..3657144a66 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -632,7 +632,8 @@ pub trait UtxoCoinBuilderCommonOps { #[cfg(not(target_arch = "wasm32"))] fn native_client(&self) -> UtxoCoinBuildResult { - use base64::{encode_config as base64_encode, URL_SAFE}; + use base64::engine::general_purpose::URL_SAFE; + use base64::Engine; let native_conf_path = self.confpath()?; let network = self.network()?; @@ -655,7 +656,7 @@ pub trait UtxoCoinBuilderCommonOps { let client = Arc::new(NativeClientImpl { coin_ticker, uri: format!("http://127.0.0.1:{}", rpc_port), - auth: format!("Basic {}", base64_encode(&auth_str, URL_SAFE)), + auth: format!("Basic {}", URL_SAFE.encode(auth_str)), event_handlers, request_id: 0u64.into(), list_unspent_concurrent_map: ConcurrentRequestMap::new(), diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 03a4421297..fa5913319c 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -32,6 +32,8 @@ use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPayment WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; use crate::{MmCoinEnum, WatcherReward, WatcherRewardError}; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use bitcrypto::{dhash256, ripemd160}; use chain::constants::SEQUENCE_FINAL; @@ -2952,7 +2954,7 @@ pub fn sign_message(coin: &UtxoCoinFields, message: &str) -> SignatureResult( @@ -2962,7 +2964,7 @@ pub fn verify_message( address: &str, ) -> VerificationResult { let message_hash = sign_message_hash(coin.as_ref(), message).ok_or(VerificationError::PrefixNotFound)?; - let signature = CompactSignature::from(base64::decode(signature_base64)?); + let signature = CompactSignature::from(STANDARD.decode(signature_base64)?); let recovered_pubkey = Public::recover_compact(&H256::from(message_hash), &signature)?; let received_address = checked_address_from_str(coin, address)?; Ok(AddressHashEnum::from(recovered_pubkey.address_hash()) == *received_address.hash()) diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 383d689422..5f8b5847ce 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -297,6 +297,11 @@ impl ZCoin { .await } + #[inline] + pub async fn first_sync_block(&self) -> Result> { + self.z_fields.sync_state_connector.lock().await.first_sync_block().await + } + #[inline] fn secp_keypair(&self) -> &KeyPair { self.utxo_arc diff --git a/mm2src/coins/z_coin/z_rpc.rs b/mm2src/coins/z_coin/z_rpc.rs index bfdc61695f..8807e6bb82 100644 --- a/mm2src/coins/z_coin/z_rpc.rs +++ b/mm2src/coins/z_coin/z_rpc.rs @@ -552,6 +552,11 @@ pub(super) async fn init_light_client<'a>( blocks_db.rewind_to_height(u32::MIN.into()).await?; }; + let first_sync_block = FirstSyncBlock { + requested: sync_height, + is_pre_sapling: sync_height < sapling_activation_height, + actual: sync_height.max(sapling_activation_height), + }; let sync_handle = SaplingSyncLoopHandle { coin, current_block: BlockHeight::from_u32(0), @@ -559,22 +564,19 @@ pub(super) async fn init_light_client<'a>( wallet_db: wallet_db.clone(), consensus_params: builder.protocol_info.consensus_params.clone(), sync_status_notifier, + main_sync_state_finished: false, on_tx_gen_watcher, watch_for_tx: None, scan_blocks_per_iteration: builder.z_coin_params.scan_blocks_per_iteration, scan_interval_ms: builder.z_coin_params.scan_interval_ms, - first_sync_block: FirstSyncBlock { - requested: sync_height, - is_pre_sapling: sync_height < sapling_activation_height, - actual: sync_height.max(sapling_activation_height), - }, + first_sync_block: first_sync_block.clone(), z_balance_event_sender, }; let abort_handle = spawn_abortable(light_wallet_db_sync_loop(sync_handle, Box::new(light_rpc_clients))); Ok(( - SaplingSyncConnector::new_mutex_wrapped(sync_watcher, on_tx_gen_notifier, abort_handle), + SaplingSyncConnector::new_mutex_wrapped(sync_watcher, on_tx_gen_notifier, abort_handle, first_sync_block), wallet_db, )) } @@ -590,6 +592,7 @@ pub(super) async fn init_native_client<'a>( let coin = builder.ticker.to_string(); let (sync_status_notifier, sync_watcher) = channel(1); let (on_tx_gen_notifier, on_tx_gen_watcher) = channel(1); + let checkpoint_block = builder.protocol_info.check_point_block.clone(); let sapling_height = builder.protocol_info.consensus_params.sapling_activation_height; let checkpoint_height = checkpoint_block.clone().map(|b| b.height).unwrap_or(sapling_height) as u64; @@ -609,17 +612,18 @@ pub(super) async fn init_native_client<'a>( wallet_db: wallet_db.clone(), consensus_params: builder.protocol_info.consensus_params.clone(), sync_status_notifier, + main_sync_state_finished: false, on_tx_gen_watcher, watch_for_tx: None, scan_blocks_per_iteration: builder.z_coin_params.scan_blocks_per_iteration, scan_interval_ms: builder.z_coin_params.scan_interval_ms, - first_sync_block, + first_sync_block: first_sync_block.clone(), z_balance_event_sender, }; let abort_handle = spawn_abortable(light_wallet_db_sync_loop(sync_handle, Box::new(native_client))); Ok(( - SaplingSyncConnector::new_mutex_wrapped(sync_watcher, on_tx_gen_notifier, abort_handle), + SaplingSyncConnector::new_mutex_wrapped(sync_watcher, on_tx_gen_notifier, abort_handle, first_sync_block), wallet_db, )) } @@ -661,20 +665,18 @@ impl SaplingSyncRespawnGuard { /// - `TemporaryError(String)`: Represents a temporary error state, with an associated error message /// providing details about the error. /// - `Finishing`: Represents the finishing state of an operation. +#[derive(Debug)] pub enum SyncStatus { UpdatingBlocksCache { - first_sync_block: FirstSyncBlock, current_scanned_block: u64, latest_block: u64, }, BuildingWalletDb { - first_sync_block: FirstSyncBlock, current_scanned_block: u64, latest_block: u64, }, TemporaryError(String), Finished { - first_sync_block: FirstSyncBlock, block_number: u64, }, } @@ -706,6 +708,8 @@ pub struct SaplingSyncLoopHandle { consensus_params: ZcoinConsensusParams, /// Notifies about sync status without stopping the loop, e.g. on coin activation sync_status_notifier: AsyncSender, + /// Signal to determine if main sync state is finished. + main_sync_state_finished: bool, /// If new tx is required to be generated, we stop the sync and respawn it after tx is sent /// This watcher waits for such notification on_tx_gen_watcher: AsyncReceiver)>>, @@ -717,39 +721,49 @@ pub struct SaplingSyncLoopHandle { } impl SaplingSyncLoopHandle { - fn first_sync_block(&self) -> FirstSyncBlock { self.first_sync_block.clone() } - + #[inline] fn notify_blocks_cache_status(&mut self, current_scanned_block: u64, latest_block: u64) { + if self.main_sync_state_finished { + return; + } self.sync_status_notifier .try_send(SyncStatus::UpdatingBlocksCache { current_scanned_block, latest_block, - first_sync_block: self.first_sync_block(), }) .debug_log_with_msg("No one seems interested in SyncStatus"); } fn notify_building_wallet_db(&mut self, current_scanned_block: u64, latest_block: u64) { + if self.main_sync_state_finished { + return; + } self.sync_status_notifier .try_send(SyncStatus::BuildingWalletDb { current_scanned_block, latest_block, - first_sync_block: self.first_sync_block(), }) .debug_log_with_msg("No one seems interested in SyncStatus"); } fn notify_on_error(&mut self, error: String) { + if self.main_sync_state_finished { + return; + } self.sync_status_notifier .try_send(SyncStatus::TemporaryError(error)) .debug_log_with_msg("No one seems interested in SyncStatus"); } fn notify_sync_finished(&mut self) { + if self.main_sync_state_finished { + return; + } else { + self.main_sync_state_finished = true + } self.sync_status_notifier .try_send(SyncStatus::Finished { block_number: self.current_block.into(), - first_sync_block: self.first_sync_block(), }) .debug_log_with_msg("No one seems interested in SyncStatus"); } @@ -781,7 +795,7 @@ impl SaplingSyncLoopHandle { /// For more notes on the process, check https://github.com/zcash/librustzcash/blob/master/zcash_client_backend/src/data_api/chain.rs#L2 async fn scan_validate_and_update_blocks(&mut self) -> Result<(), MmError> { let blocks_db = self.blocks_db.clone(); - let wallet_db = self.wallet_db.clone().db; + let wallet_db = self.wallet_db.db.clone(); let mut wallet_ops = wallet_db.get_update_ops().expect("get_update_ops always returns Ok"); if let Err(e) = blocks_db @@ -929,23 +943,31 @@ pub(super) struct SaplingSyncConnector { sync_watcher: SyncWatcher, on_tx_gen_notifier: NewTxNotifier, abort_handle: Arc>, + first_sync_block: FirstSyncBlock, } impl SaplingSyncConnector { #[allow(unused)] #[inline] pub(super) fn new_mutex_wrapped( - simple_sync_watcher: SyncWatcher, + sync_watcher: SyncWatcher, on_tx_gen_notifier: NewTxNotifier, abort_handle: AbortOnDropHandle, + first_sync_block: FirstSyncBlock, ) -> AsyncMutex { AsyncMutex::new(SaplingSyncConnector { - sync_watcher: simple_sync_watcher, + sync_watcher, on_tx_gen_notifier, abort_handle: Arc::new(Mutex::new(abort_handle)), + first_sync_block, }) } + #[inline] + pub(super) async fn first_sync_block(&self) -> Result> { + Ok(self.first_sync_block.clone()) + } + #[inline] pub(super) async fn current_sync_status(&mut self) -> Result> { self.sync_watcher.next().await.or_mm_err(|| BlockchainScanStopped {}) diff --git a/mm2src/coins_activation/src/erc20_token_activation.rs b/mm2src/coins_activation/src/erc20_token_activation.rs index 027f767539..173092c65d 100644 --- a/mm2src/coins_activation/src/erc20_token_activation.rs +++ b/mm2src/coins_activation/src/erc20_token_activation.rs @@ -88,7 +88,7 @@ impl TryFromCoinProtocol for EthTokenProtocol { let erc20_protocol = Erc20Protocol::try_from_coin_protocol(proto)?; Ok(EthTokenProtocol::Erc20(erc20_protocol)) }, - CoinProtocol::Nft { platform } => Ok(EthTokenProtocol::Nft(NftProtocol { platform })), + CoinProtocol::NFT { platform } => Ok(EthTokenProtocol::Nft(NftProtocol { platform })), proto => MmError::err(proto), } } diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index 21a04ed8ad..967a4cae68 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -82,7 +82,7 @@ pub fn coin_conf_with_protocol( Ok(chain) => { let platform = chain.to_ticker(); let platform_conf = coin_conf(ctx, platform); - let nft_protocol = CoinProtocol::Nft { + let nft_protocol = CoinProtocol::NFT { platform: platform.to_string(), }; (platform_conf, nft_protocol) diff --git a/mm2src/coins_activation/src/z_coin_activation.rs b/mm2src/coins_activation/src/z_coin_activation.rs index f8140e2685..dc8c062a84 100644 --- a/mm2src/coins_activation/src/z_coin_activation.rs +++ b/mm2src/coins_activation/src/z_coin_activation.rs @@ -44,7 +44,7 @@ pub struct ZcoinActivationResult { pub ticker: String, pub current_block: u64, pub wallet_balance: CoinBalanceReport, - pub first_sync_block: Option, + pub first_sync_block: FirstSyncBlock, } impl CurrentBlock for ZcoinActivationResult { @@ -77,12 +77,10 @@ impl GetAddressesBalances for ZcoinActivationResult { pub enum ZcoinInProgressStatus { ActivatingCoin, UpdatingBlocksCache { - first_sync_block: FirstSyncBlock, current_scanned_block: u64, latest_block: u64, }, BuildingWalletDb { - first_sync_block: FirstSyncBlock, current_scanned_block: u64, latest_block: u64, }, @@ -247,20 +245,16 @@ impl InitStandaloneCoinActivationOps for ZCoin { loop { let in_progress_status = match coin.sync_status().await? { SyncStatus::UpdatingBlocksCache { - first_sync_block, current_scanned_block, latest_block, } => ZcoinInProgressStatus::UpdatingBlocksCache { - first_sync_block, current_scanned_block, latest_block, }, SyncStatus::BuildingWalletDb { - first_sync_block, current_scanned_block, latest_block, } => ZcoinInProgressStatus::BuildingWalletDb { - first_sync_block, current_scanned_block, latest_block, }, @@ -287,12 +281,7 @@ impl InitStandaloneCoinActivationOps for ZCoin { .map_to_mm(ZcoinInitError::CouldNotGetBlockCount)?; let balance = self.my_balance().compat().await?; - let first_sync_block = match self.sync_status().await? { - SyncStatus::Finished { first_sync_block, .. } - | SyncStatus::BuildingWalletDb { first_sync_block, .. } - | SyncStatus::UpdatingBlocksCache { first_sync_block, .. } => Some(first_sync_block), - _ => None, - }; + let first_sync_block = self.first_sync_block().await?; Ok(ZcoinActivationResult { ticker: self.ticker().into(), diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index 4637818b35..788947b002 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -42,7 +42,7 @@ serde_derive = "1" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } ser_error = { path = "../derives/ser_error" } ser_error_derive = { path = "../derives/ser_error_derive" } -sha2 = "0.9" +sha2 = "0.10" shared_ref_counter = { path = "shared_ref_counter", optional = true } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } instant = { version = "0.1.12" } diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index 8fd9a26cde..dd3bbec752 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -7,17 +7,24 @@ edition = "2018" doctest = false [dependencies] +aes = "0.8.3" +argon2 = { version = "0.5.2", features = ["zeroize"] } arrayref = "0.3" async-trait = "0.1" +base64 = "0.21.2" bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } +bip39 = { version = "2.0.0", features = ["rand_core", "zeroize"], default-features = false } bitcrypto = { path = "../mm2_bitcoin/crypto" } bs58 = "0.4.0" +cbc = "0.1.2" +cipher = "0.4.4" common = { path = "../common" } derive_more = "0.99" enum_derives = { path = "../derives/enum_derives" } enum-primitive-derive = "0.2" futures = "0.3" hex = "0.4.2" +hmac = "0.12.1" http = "0.2" hw_common = { path = "../hw_common" } keys = { path = "../mm2_bitcoin/keys" } @@ -36,14 +43,20 @@ ser_error_derive = { path = "../derives/ser_error_derive" } serde = "1.0" serde_derive = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } -tiny-bip39 = "0.8.0" +sha2 = "0.10" trezor = { path = "../trezor" } +zeroize = { version = "1.5", features = ["zeroize_derive"] } [target.'cfg(target_arch = "wasm32")'.dependencies] +cfg-if = "1.0" mm2_eth = { path = "../mm2_eth" } mm2_metamask = { path = "../mm2_metamask" } wasm-bindgen-test = { version = "0.3.2" } web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false } +[dev-dependencies] +cfg-if = "1.0" +tokio = { version = "1.20", default-features = false } + [features] trezor-udp = ["trezor/trezor-udp"] diff --git a/mm2src/crypto/src/decrypt.rs b/mm2src/crypto/src/decrypt.rs new file mode 100644 index 0000000000..a0c750f58c --- /dev/null +++ b/mm2src/crypto/src/decrypt.rs @@ -0,0 +1,63 @@ +use crate::EncryptedData; +use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}; +use aes::Aes256; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use derive_more::Display; +use hmac::{Hmac, Mac}; +use mm2_err_handle::prelude::*; +use sha2::Sha256; + +type Aes256CbcDec = cbc::Decryptor; + +#[derive(Debug, Display, PartialEq)] +pub enum DecryptionError { + #[display(fmt = "AES cipher error: {}", _0)] + AESCipherError(String), + #[display(fmt = "Error decoding string: {}", _0)] + DecodeError(String), + #[display(fmt = "HMAC error: {}", _0)] + HMACError(String), + Internal(String), +} + +impl From for DecryptionError { + fn from(e: base64::DecodeError) -> Self { DecryptionError::DecodeError(e.to_string()) } +} + +/// Decrypts the provided encrypted data using AES-256-CBC decryption and HMAC for integrity check. +/// +/// This function performs several operations: +/// - It decodes the Base64-encoded values of the IV, ciphertext, and HMAC tag from the `EncryptedData`. +/// - It verifies the HMAC tag before decrypting to ensure the integrity of the data. +/// - It creates an AES-256-CBC cipher instance and decrypts the ciphertext with the provided key and the decoded IV. +/// +/// # Returns +/// `MmResult, DecryptionError>` - The result is either a byte vector containing the decrypted data, +/// or a [`DecryptionError`] in case of failure. +/// +/// # Errors +/// This function can return various errors related to Base64 decoding, HMAC verification, and AES decryption. +pub fn decrypt_data( + encrypted_data: &EncryptedData, + key_aes: &[u8; 32], + key_hmac: &[u8; 32], +) -> MmResult, DecryptionError> { + // Decode the Base64-encoded values + let iv = STANDARD.decode(&encrypted_data.iv)?; + let mut ciphertext = STANDARD.decode(&encrypted_data.ciphertext)?; + let tag = STANDARD.decode(&encrypted_data.tag)?; + + // Verify HMAC tag before decrypting + let mut mac = Hmac::::new_from_slice(key_hmac).map_to_mm(|e| DecryptionError::Internal(e.to_string()))?; + mac.update(&ciphertext); + mac.update(&iv); + mac.verify_slice(&tag) + .map_to_mm(|e| DecryptionError::HMACError(e.to_string()))?; + + // Decrypt the ciphertext and return the result + Aes256CbcDec::new(key_aes.into(), iv.as_slice().into()) + .decrypt_padded_mut::(&mut ciphertext) + .map_to_mm(|e| DecryptionError::AESCipherError(e.to_string())) + .map(|plaintext| plaintext.to_vec()) +} diff --git a/mm2src/crypto/src/encrypt.rs b/mm2src/crypto/src/encrypt.rs new file mode 100644 index 0000000000..30c5246aa1 --- /dev/null +++ b/mm2src/crypto/src/encrypt.rs @@ -0,0 +1,118 @@ +use crate::key_derivation::KeyDerivationDetails; +use aes::cipher::{block_padding::Pkcs7, BlockEncryptMut, KeyIvInit}; +use aes::Aes256; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use common::drop_mutability; +use derive_more::Display; +use hmac::{Hmac, Mac}; +use mm2_err_handle::prelude::*; +use sha2::Sha256; + +type Aes256CbcEnc = cbc::Encryptor; + +#[derive(Debug, Display, PartialEq)] +pub enum EncryptionError { + #[display(fmt = "Error generating random bytes: {}", _0)] + UnableToGenerateRandomBytes(String), + #[display(fmt = "AES cipher error: {}", _0)] + AESCipherError(String), + Internal(String), +} + +/// Enum representing different encryption algorithms. +#[derive(Serialize, Deserialize, Debug)] +pub enum EncryptionAlgorithm { + /// AES-256-CBC algorithm. + AES256CBC, + // Placeholder for future algorithms. +} + +/// `EncryptedData` represents encrypted data for a wallet. +/// +/// This struct encapsulates all essential components required to securely encrypt +/// and subsequently decrypt a wallet mnemonic and data. It is designed to be self-contained, +/// meaning it includes not only the encrypted data but also all the necessary metadata +/// and parameters for decryption. This makes the struct portable and convenient for +/// use in various scenarios, allowing decryption of the mnemonic in different +/// environments or applications, provided the correct password or seed is supplied. +/// +/// The `EncryptedData` struct is typically used for wallet encryption in blockchain-based applications, +/// providing a robust and comprehensive approach to securing sensitive mnemonic data. +#[derive(Serialize, Deserialize, Debug)] +pub struct EncryptedData { + /// The encryption algorithm used to encrypt the mnemonic. + /// Example: "AES-256-CBC". + pub encryption_algorithm: EncryptionAlgorithm, + + /// Detailed information about the key derivation process. This includes + /// the specific algorithm used (e.g., Argon2) and its parameters. + pub key_derivation_details: KeyDerivationDetails, + + /// The initialization vector (IV) used in the AES encryption process. + /// The IV ensures that the encryption process produces unique ciphertext + /// for the same plaintext and key when encrypted multiple times. + /// Stored as a Base64-encoded string. + pub iv: String, + + /// The encrypted mnemonic data. This is the ciphertext generated + /// using the specified encryption algorithm, key, and IV. + /// Stored as a Base64-encoded string. + pub ciphertext: String, + + /// The HMAC tag used for verifying the integrity and authenticity of the encrypted data. + /// This tag is crucial for validating that the data has not been tampered with. + /// Stored as a Base64-encoded string. + pub tag: String, +} + +/// Encrypts the provided data using AES-256-CBC encryption and HMAC for integrity check. +/// +/// This function performs several operations: +/// - It generates an Initialization Vector (IV) for the AES encryption. +/// - It creates an AES-256-CBC cipher instance and encrypts the data with the provided key and the generated IV. +/// - It creates an HMAC tag for verifying the integrity of the encrypted data. +/// - It constructs an [`EncryptedData`] instance containing all the necessary components for decryption. +/// +/// # Returns +/// `MmResult` - The result is either an [`EncryptedData`] +/// struct containing all the necessary components for decryption, or an [`EncryptionError`] in case of failure. +/// +/// # Errors +/// This function can return various errors related to IV generation, AES encryption, HMAC creation, and data encoding. +pub fn encrypt_data( + data: &[u8], + key_derivation_details: KeyDerivationDetails, + key_aes: &[u8; 32], + key_hmac: &[u8; 32], +) -> MmResult { + // Generate IV + let mut iv = [0u8; 16]; + common::os_rng(&mut iv).map_to_mm(|e| EncryptionError::UnableToGenerateRandomBytes(e.to_string()))?; + drop_mutability!(iv); + + // Create an AES-256-CBC cipher instance, encrypt the data with the key and the IV and get the ciphertext + let msg_len = data.len(); + let buffer_len = msg_len + 16 - (msg_len % 16); + let mut buffer = vec![0u8; buffer_len]; + buffer[..msg_len].copy_from_slice(data); + let ciphertext = Aes256CbcEnc::new(key_aes.into(), &iv.into()) + .encrypt_padded_mut::(&mut buffer, msg_len) + .map_to_mm(|e| EncryptionError::AESCipherError(e.to_string()))?; + + // Create HMAC tag + let mut mac = Hmac::::new_from_slice(key_hmac).map_to_mm(|e| EncryptionError::Internal(e.to_string()))?; + mac.update(ciphertext); + mac.update(&iv); + let tag = mac.finalize().into_bytes(); + + let encrypted_data = EncryptedData { + encryption_algorithm: EncryptionAlgorithm::AES256CBC, + key_derivation_details, + iv: STANDARD.encode(iv), + ciphertext: STANDARD.encode(ciphertext), + tag: STANDARD.encode(tag), + }; + + Ok(encrypted_data) +} diff --git a/mm2src/crypto/src/global_hd_ctx.rs b/mm2src/crypto/src/global_hd_ctx.rs index 79326e9889..15cb2cffbb 100644 --- a/mm2src/crypto/src/global_hd_ctx.rs +++ b/mm2src/crypto/src/global_hd_ctx.rs @@ -8,6 +8,7 @@ use keys::{KeyPair, Secret as Secp256k1Secret}; use mm2_err_handle::prelude::*; use std::ops::Deref; use std::sync::Arc; +use zeroize::{Zeroize, ZeroizeOnDrop}; const HARDENED: bool = true; const NON_HARDENED: bool = false; @@ -23,8 +24,11 @@ impl Deref for GlobalHDAccountArc { fn deref(&self) -> &Self::Target { &self.0 } } +#[derive(Zeroize, ZeroizeOnDrop)] +pub struct Bip39Seed(pub [u8; 64]); + pub struct GlobalHDAccountCtx { - bip39_seed: bip39::Seed, + bip39_seed: Bip39Seed, bip39_secp_priv_key: ExtendedPrivateKey, } @@ -32,8 +36,7 @@ impl GlobalHDAccountCtx { pub fn new(passphrase: &str) -> CryptoInitResult<(Mm2InternalKeyPair, GlobalHDAccountCtx)> { let bip39_seed = bip39_seed_from_passphrase(passphrase)?; let bip39_secp_priv_key: ExtendedPrivateKey = - ExtendedPrivateKey::new(bip39_seed.as_bytes()) - .map_to_mm(|e| PrivKeyError::InvalidPrivKey(e.to_string()))?; + ExtendedPrivateKey::new(bip39_seed.0).map_to_mm(|e| PrivKeyError::InvalidPrivKey(e.to_string()))?; let derivation_path = mm2_internal_der_path(); @@ -57,10 +60,10 @@ impl GlobalHDAccountCtx { pub fn into_arc(self) -> GlobalHDAccountArc { GlobalHDAccountArc(Arc::new(self)) } /// Returns the root BIP39 seed. - pub fn root_seed(&self) -> &bip39::Seed { &self.bip39_seed } + pub fn root_seed(&self) -> &Bip39Seed { &self.bip39_seed } /// Returns the root BIP39 seed as bytes. - pub fn root_seed_bytes(&self) -> &[u8] { self.bip39_seed.as_bytes() } + pub fn root_seed_bytes(&self) -> &[u8] { &self.bip39_seed.0 } /// Returns the root BIP39 private key. pub fn root_priv_key(&self) -> &ExtendedPrivateKey { &self.bip39_secp_priv_key } diff --git a/mm2src/crypto/src/key_derivation.rs b/mm2src/crypto/src/key_derivation.rs new file mode 100644 index 0000000000..74500c3d52 --- /dev/null +++ b/mm2src/crypto/src/key_derivation.rs @@ -0,0 +1,222 @@ +use argon2::password_hash::SaltString; +use argon2::{Argon2, PasswordHasher}; +use common::drop_mutability; +use derive_more::Display; +use hmac::{Hmac, Mac}; +use mm2_err_handle::mm_error::MmResult; +use mm2_err_handle::prelude::*; +use sha2::Sha512; +use std::convert::TryInto; + +const ARGON2_ALGORITHM: &str = "Argon2id"; +const ARGON2ID_VERSION: &str = "0x13"; +const ARGON2ID_M_COST: u32 = 65536; +const ARGON2ID_T_COST: u32 = 2; +const ARGON2ID_P_COST: u32 = 1; + +#[allow(dead_code)] +type HmacSha512 = Hmac; + +#[derive(Debug, Display, PartialEq)] +pub enum KeyDerivationError { + #[display(fmt = "Error hashing password: {}", _0)] + PasswordHashingFailed(String), + #[display(fmt = "Error initializing HMAC")] + HmacInitialization, + #[display(fmt = "Invalid key length")] + InvalidKeyLength, +} + +impl From for KeyDerivationError { + fn from(e: argon2::password_hash::Error) -> Self { KeyDerivationError::PasswordHashingFailed(e.to_string()) } +} + +/// Parameters for the Argon2 key derivation function. +/// +/// This struct defines the configuration parameters used by Argon2, one of the +/// most secure and widely used key derivation functions, especially for +/// password hashing. +#[derive(Serialize, Deserialize, Debug)] +pub struct Argon2Params { + /// The specific variant of the Argon2 algorithm used (e.g., Argon2id). + algorithm: String, + + /// The version of the Argon2 algorithm (e.g., 0x13 for the latest version). + version: String, + + /// The memory cost parameter defining the memory usage of the algorithm. + /// Expressed in kibibytes (KiB). + m_cost: u32, + + /// The time cost parameter defining the execution time and number of + /// iterations of the algorithm. + t_cost: u32, + + /// The parallelism cost parameter defining the number of parallel threads. + p_cost: u32, +} + +impl Default for Argon2Params { + fn default() -> Self { + Argon2Params { + algorithm: ARGON2_ALGORITHM.to_string(), + version: ARGON2ID_VERSION.to_string(), + m_cost: ARGON2ID_M_COST, + t_cost: ARGON2ID_T_COST, + p_cost: ARGON2ID_P_COST, + } + } +} + +/// Enum representing different key derivation details. +/// +/// This enum allows for flexible specification of various key derivation +/// algorithms and their parameters, making it easier to extend and support +/// multiple algorithms in the future. +#[derive(Serialize, Deserialize, Debug)] +pub enum KeyDerivationDetails { + /// Argon2 algorithm for key derivation. + Argon2 { + /// The parameters for the Argon2 key derivation function. + params: Argon2Params, + /// The salt used in the key derivation process for the AES key. + /// Stored as a Base64-encoded string. + salt_aes: String, + /// The salt used in the key derivation process for the HMAC key. + /// This is applicable if HMAC is used for ensuring data integrity and authenticity. + /// Stored as a Base64-encoded string. + salt_hmac: String, + }, + /// Algorithm for deriving a hierarchy of symmetric keys from a master secret according to [SLIP-0021](https://github.com/satoshilabs/slips/blob/master/slip-0021.md). + SLIP0021 { + encryption_path: String, + authentication_path: String, + }, + // Placeholder for future algorithms. +} + +/// Derives AES and HMAC keys from a given password and salts for mnemonic encryption/decryption. +/// +/// # Returns +/// A tuple containing the AES key and HMAC key as byte arrays, or a `MnemonicError` in case of failure. +#[allow(dead_code)] +pub(crate) fn derive_keys_for_mnemonic( + password: &str, + salt_aes: &SaltString, + salt_hmac: &SaltString, +) -> MmResult<([u8; 32], [u8; 32]), KeyDerivationError> { + let argon2 = Argon2::default(); + + // Derive AES Key + let aes_password_hash = argon2.hash_password(password.as_bytes(), salt_aes)?; + let key_aes_output = aes_password_hash + .serialize() + .hash() + .ok_or_else(|| KeyDerivationError::PasswordHashingFailed("Error finding AES key hashing output".to_string()))?; + let key_aes = key_aes_output + .as_bytes() + .try_into() + .map_err(|_| KeyDerivationError::PasswordHashingFailed("Invalid AES key length".to_string()))?; + + // Derive HMAC Key + let hmac_password_hash = argon2.hash_password(password.as_bytes(), salt_hmac)?; + let key_hmac_output = hmac_password_hash.serialize().hash().ok_or_else(|| { + KeyDerivationError::PasswordHashingFailed("Error finding HMAC key hashing output".to_string()) + })?; + let key_hmac = key_hmac_output + .as_bytes() + .try_into() + .map_err(|_| KeyDerivationError::PasswordHashingFailed("Invalid HMAC key length".to_string()))?; + + Ok((key_aes, key_hmac)) +} + +/// Splits a path into its components and derives a key for each component. +fn derive_key_from_path(master_node: &[u8], path: &str) -> MmResult<[u8; 32], KeyDerivationError> { + let mut current_key_material = master_node.to_vec(); + for segment in path.split('/').filter(|s| !s.is_empty()) { + let mut mac = HmacSha512::new_from_slice(¤t_key_material[..32]) + .map_err(|_| KeyDerivationError::HmacInitialization)?; + mac.update(b"\x00"); + mac.update(segment.as_bytes()); + drop_mutability!(mac); + + let hmac_result = mac.finalize().into_bytes(); + current_key_material = hmac_result.to_vec(); + } + drop_mutability!(current_key_material); + + current_key_material[32..64] + .try_into() + .map_to_mm(|_| KeyDerivationError::InvalidKeyLength) +} + +/// Derives encryption and authentication keys from the master private key using [SLIP-0021](https://github.com/satoshilabs/slips/blob/master/slip-0021.md). +/// +/// # Returns +/// A tuple containing the encryption and authentication keys as byte arrays, or a [`KeyDerivationError`] in case of failure. +#[allow(dead_code)] +pub(crate) fn derive_encryption_authentication_keys( + master_secret: &[u8; 64], + encryption_path: &str, + authentication_path: &str, +) -> MmResult<([u8; 32], [u8; 32]), KeyDerivationError> { + const MASTER_NODE_HMAC_KEY: &[u8] = b"Symmetric key seed"; + + // Generate the master node `m` according to SLIP-0021. + let mut mac = + HmacSha512::new_from_slice(MASTER_NODE_HMAC_KEY).map_to_mm(|_| KeyDerivationError::HmacInitialization)?; + mac.update(master_secret); + drop_mutability!(mac); + let master_key_material = mac.finalize().into_bytes(); + + // Derive encryption key + let encryption_key = derive_key_from_path(&master_key_material, encryption_path)?; + + // Derive authentication key + let authentication_key = derive_key_from_path(&master_key_material, authentication_path)?; + + Ok((encryption_key, authentication_key)) +} + +#[cfg(any(test, target_arch = "wasm32"))] +mod tests { + use super::*; + use crate::slip21::{AUTHENTICATION_PATH, ENCRYPTION_PATH}; + use common::cross_test; + + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + // https://github.com/satoshilabs/slips/blob/master/slip-0021.md#example + cross_test!(test_slip_0021_key_derivation, { + let master_secret = hex::decode("c76c4ac4f4e4a00d6b274d5c39c700bb4a7ddc04fbc6f78e85ca75007b5b495f74a9043eeb77bdd53aa6fc3a0e31462270316fa04b8c19114c8798706cd02ac8").unwrap(); + + let expected_encryption_key = + hex::decode("ea163130e35bbafdf5ddee97a17b39cef2be4b4f390180d65b54cf05c6a82fde").unwrap(); + let expected_authentication_key = + hex::decode("47194e938ab24cc82bfa25f6486ed54bebe79c40ae2a5a32ea6db294d81861a6").unwrap(); + + // Directly derive the encryption and authentication keys from the master secret + let (derived_encryption_key, derived_authentication_key) = derive_encryption_authentication_keys( + &master_secret.try_into().expect("Invalid master secret"), + ENCRYPTION_PATH, + AUTHENTICATION_PATH, + ) + .expect("Key derivation failed"); + + // Verify the derived keys against the expected values + assert_eq!( + derived_encryption_key, + expected_encryption_key.as_slice(), + "Derived encryption key does not match expected value" + ); + assert_eq!( + derived_authentication_key, + expected_authentication_key.as_slice(), + "Derived authentication key does not match expected value" + ); + }); +} diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index da9ff99a29..04820759f9 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -2,13 +2,18 @@ mod bip32_child; mod crypto_ctx; +mod decrypt; +mod encrypt; mod global_hd_ctx; mod hw_client; mod hw_ctx; mod hw_error; pub mod hw_rpc_task; +mod key_derivation; +pub mod mnemonic; pub mod privkey; mod shared_db_id; +mod slip21; mod standard_hd_path; mod xpub; @@ -18,6 +23,7 @@ mod xpub; pub use bip32_child::{Bip32Child, Bip32DerPathError, Bip32DerPathOps, Bip44Tail}; pub use crypto_ctx::{CryptoCtx, CryptoCtxError, CryptoInitError, CryptoInitResult, HwCtxInitError, KeyPairPolicy}; +pub use encrypt::EncryptedData; pub use global_hd_ctx::{derive_secp256k1_secret, GlobalHDAccountArc}; pub use hw_client::{HwClient, HwConnectionStatus, HwDeviceInfo, HwProcessingError, HwPubkey, HwWalletType, TrezorConnectProcessor}; @@ -26,6 +32,7 @@ pub use hw_common::primitives::{Bip32Error, ChildNumber, DerivationPath, EcdsaCu pub use hw_ctx::{HardwareWalletArc, HardwareWalletCtx}; pub use hw_error::{from_hw_error, HwError, HwResult, HwRpcError, WithHwRpcError}; pub use keys::Secret as Secp256k1Secret; +pub use mnemonic::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, MnemonicError}; pub use standard_hd_path::{Bip44Chain, StandardHDCoinAddress, StandardHDPath, StandardHDPathError, StandardHDPathToAccount, StandardHDPathToCoin, UnknownChainError}; pub use trezor; diff --git a/mm2src/crypto/src/mnemonic.rs b/mm2src/crypto/src/mnemonic.rs new file mode 100644 index 0000000000..c92e23c05b --- /dev/null +++ b/mm2src/crypto/src/mnemonic.rs @@ -0,0 +1,185 @@ +use crate::decrypt::decrypt_data; +use crate::encrypt::encrypt_data; +use crate::key_derivation::{derive_keys_for_mnemonic, Argon2Params, KeyDerivationDetails, KeyDerivationError}; +use crate::EncryptedData; +use argon2::password_hash::SaltString; +use bip39::{Language, Mnemonic}; +use derive_more::Display; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; + +const DEFAULT_WORD_COUNT: u64 = 12; + +#[derive(Debug, Display, PartialEq)] +pub enum MnemonicError { + #[display(fmt = "BIP39 mnemonic error: {}", _0)] + BIP39Error(String), + #[display(fmt = "Error deriving key: {}", _0)] + KeyDerivationError(String), + #[display(fmt = "Error decoding string: {}", _0)] + DecodeError(String), + #[display(fmt = "Error encrypting mnemonic: {}", _0)] + EncryptionError(String), + #[display(fmt = "Error decrypting mnemonic: {}", _0)] + DecryptionError(String), + Internal(String), +} + +impl From for MnemonicError { + fn from(e: bip39::Error) -> Self { MnemonicError::BIP39Error(e.to_string()) } +} + +impl From for MnemonicError { + fn from(e: argon2::password_hash::Error) -> Self { MnemonicError::KeyDerivationError(e.to_string()) } +} + +impl From for MnemonicError { + fn from(e: KeyDerivationError) -> Self { MnemonicError::KeyDerivationError(e.to_string()) } +} + +/// Generates a new mnemonic passphrase. +/// +/// This function creates a new mnemonic passphrase using a specified word count and randomness source. +/// The generated mnemonic is intended for use as a wallet mnemonic. +/// +/// # Returns +/// `MmInitResult` - The generated mnemonic passphrase or an error if generation fails. +/// +/// # Errors +/// Returns `MmInitError::Internal` if mnemonic generation fails. +pub fn generate_mnemonic(ctx: &MmArc) -> MmResult { + let mut rng = bip39::rand_core::OsRng; + let word_count = ctx.conf["word_count"].as_u64().unwrap_or(DEFAULT_WORD_COUNT) as usize; + let mnemonic = Mnemonic::generate_in_with(&mut rng, Language::English, word_count)?; + Ok(mnemonic) +} + +/// Encrypts a mnemonic phrase using a specified password. +/// +/// This function performs several operations: +/// - It generates salts for AES and HMAC key derivation. +/// - It derives the keys using the Argon2 algorithm. +/// - It encrypts the mnemonic using AES-256-CBC. +/// - It creates an HMAC tag for verifying the integrity and authenticity of the encrypted data. +/// +/// # Returns +/// `MmResult` - The result is either an `EncryptedData` +/// struct containing all the necessary components for decryption, or a `MnemonicError` in case of failure. +/// +/// # Errors +/// This function can return various errors related to key derivation, encryption, and data encoding. +pub fn encrypt_mnemonic(mnemonic: &str, password: &str) -> MmResult { + use argon2::password_hash::rand_core::OsRng; + + // Generate salt for AES key + let salt_aes = SaltString::generate(&mut OsRng); + + // Generate salt for HMAC key + let salt_hmac = SaltString::generate(&mut OsRng); + + let key_derivation_details = KeyDerivationDetails::Argon2 { + params: Argon2Params::default(), + salt_aes: salt_aes.as_str().to_string(), + salt_hmac: salt_hmac.as_str().to_string(), + }; + + // Derive AES and HMAC keys + let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &salt_aes, &salt_hmac)?; + + encrypt_data(mnemonic.as_bytes(), key_derivation_details, &key_aes, &key_hmac) + .mm_err(|e| MnemonicError::EncryptionError(e.to_string())) +} + +/// Decrypts an encrypted mnemonic phrase using a specified password. +/// +/// This function performs the reverse operations of `encrypt_mnemonic`. It: +/// - Decodes and re-creates the necessary salts, IV, and ciphertext from the `EncryptedData`. +/// - Derives the AES and HMAC keys using the Argon2 algorithm. +/// - Verifies the integrity and authenticity of the data using the HMAC tag. +/// - Decrypts the mnemonic using AES-256-CBC. +/// +/// # Returns +/// `MmResult` - The result is either a `Mnemonic` instance if decryption is successful, +/// or a `MnemonicError` in case of failure. +/// +/// # Errors +/// This function can return various errors related to decoding, key derivation, encryption, and HMAC verification. +pub fn decrypt_mnemonic(encrypted_data: &EncryptedData, password: &str) -> MmResult { + // Re-create the salts from Base64-encoded strings + let (salt_aes, salt_hmac) = match &encrypted_data.key_derivation_details { + KeyDerivationDetails::Argon2 { + salt_aes, salt_hmac, .. + } => (SaltString::from_b64(salt_aes)?, SaltString::from_b64(salt_hmac)?), + _ => { + return MmError::err(MnemonicError::KeyDerivationError( + "Key derivation details should be Argon2!".to_string(), + )) + }, + }; + + // Re-create the keys from the password and salts + let (key_aes, key_hmac) = derive_keys_for_mnemonic(password, &salt_aes, &salt_hmac)?; + + // Decrypt the ciphertext + let decrypted_data = + decrypt_data(encrypted_data, &key_aes, &key_hmac).mm_err(|e| MnemonicError::DecryptionError(e.to_string()))?; + + // Convert decrypted data back to a string + let mnemonic_str = String::from_utf8(decrypted_data).map_to_mm(|e| MnemonicError::DecodeError(e.to_string()))?; + let mnemonic = Mnemonic::parse_normalized(&mnemonic_str)?; + Ok(mnemonic) +} + +#[cfg(any(test, target_arch = "wasm32"))] +mod tests { + use super::*; + use common::cross_test; + + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + cross_test!(test_encrypt_decrypt_mnemonic, { + let mnemonic = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + let password = "password"; + + // Verify that the mnemonic is valid + let parsed_mnemonic = Mnemonic::parse_normalized(mnemonic); + assert!(parsed_mnemonic.is_ok()); + let parsed_mnemonic = parsed_mnemonic.unwrap(); + + // Encrypt the mnemonic + let encrypted_data = encrypt_mnemonic(mnemonic, password); + assert!(encrypted_data.is_ok()); + let encrypted_data = encrypted_data.unwrap(); + + // Decrypt the mnemonic + let decrypted_mnemonic = decrypt_mnemonic(&encrypted_data, password); + assert!(decrypted_mnemonic.is_ok()); + let decrypted_mnemonic = decrypted_mnemonic.unwrap(); + + // Verify if decrypted mnemonic matches the original + assert_eq!(decrypted_mnemonic, parsed_mnemonic); + }); + + cross_test!(test_mnemonic_with_last_byte_zero, { + let mnemonic = "tank abandon bind salon remove wisdom net size aspect direct source fossil\0".to_string(); + let password = "password"; + + // Encrypt the mnemonic + let encrypted_data = encrypt_mnemonic(&mnemonic, password); + assert!(encrypted_data.is_ok()); + let encrypted_data = encrypted_data.unwrap(); + + // Decrypt the mnemonic + let decrypted_mnemonic = decrypt_mnemonic(&encrypted_data, password); + assert!(decrypted_mnemonic.is_err()); + + // Verify that the error is due to parsing and not padding + assert!(decrypted_mnemonic + .unwrap_err() + .to_string() + .contains("mnemonic contains an unknown word (word 11)")); + }); +} diff --git a/mm2src/crypto/src/privkey.rs b/mm2src/crypto/src/privkey.rs index c25a09680c..620b214955 100644 --- a/mm2src/crypto/src/privkey.rs +++ b/mm2src/crypto/src/privkey.rs @@ -19,6 +19,7 @@ // marketmaker // +use crate::global_hd_ctx::Bip39Seed; use bitcrypto::{sha256, ChecksumType}; use derive_more::Display; use keys::{Error as KeysError, KeyPair, Private, Secret as Secp256k1Secret}; @@ -117,10 +118,11 @@ pub fn key_pair_from_secret(secret: &[u8]) -> PrivKeyResult { Ok(KeyPair::from_private(private)?) } -pub fn bip39_seed_from_passphrase(passphrase: &str) -> PrivKeyResult { - let mnemonic = bip39::Mnemonic::from_phrase(passphrase, bip39::Language::English) +pub fn bip39_seed_from_passphrase(passphrase: &str) -> PrivKeyResult { + let mnemonic = bip39::Mnemonic::parse_in_normalized(bip39::Language::English, passphrase) .map_to_mm(|e| PrivKeyError::ErrorParsingPassphrase(e.to_string()))?; - Ok(bip39::Seed::new(&mnemonic, "")) + let seed = mnemonic.to_seed_normalized(""); + Ok(Bip39Seed(seed)) } #[derive(Clone, Copy, Debug)] diff --git a/mm2src/crypto/src/slip21.rs b/mm2src/crypto/src/slip21.rs new file mode 100644 index 0000000000..bc2bb976d5 --- /dev/null +++ b/mm2src/crypto/src/slip21.rs @@ -0,0 +1,108 @@ +use crate::decrypt::decrypt_data; +use crate::encrypt::encrypt_data; +use crate::key_derivation::{derive_encryption_authentication_keys, KeyDerivationDetails, KeyDerivationError}; +use crate::EncryptedData; +use derive_more::Display; +use mm2_err_handle::prelude::*; + +#[allow(dead_code)] +pub(crate) const ENCRYPTION_PATH: &str = "SLIP-0021/Master encryption key/"; +#[allow(dead_code)] +pub(crate) const AUTHENTICATION_PATH: &str = "SLIP-0021/Authentication key/"; + +#[derive(Debug, Display, PartialEq)] +#[allow(dead_code)] +pub enum SLIP21Error { + #[display(fmt = "Error deriving key: {}", _0)] + KeyDerivationError(String), + #[display(fmt = "Error encrypting mnemonic: {}", _0)] + EncryptionFailed(String), + #[display(fmt = "Error decrypting mnemonic: {}", _0)] + DecryptionFailed(String), +} + +impl From for SLIP21Error { + fn from(e: KeyDerivationError) -> Self { SLIP21Error::KeyDerivationError(e.to_string()) } +} + +/// Encrypts data using SLIP-0021 derived keys. +/// +/// # Returns +/// `MmResult` - The encrypted data along with metadata for decryption, or an error. +#[allow(dead_code)] +pub fn encrypt_with_slip21( + data: &[u8], + master_secret: &[u8; 64], + derivation_path: &str, +) -> MmResult { + let encryption_path = ENCRYPTION_PATH.to_string() + derivation_path; + let authentication_path = AUTHENTICATION_PATH.to_string() + derivation_path; + + // Derive encryption and authentication keys using SLIP-0021 + let (key_aes, key_hmac) = + derive_encryption_authentication_keys(master_secret, &encryption_path, &authentication_path)?; + + let key_derivation_details = KeyDerivationDetails::SLIP0021 { + encryption_path, + authentication_path, + }; + + encrypt_data(data, key_derivation_details, &key_aes, &key_hmac) + .mm_err(|e| SLIP21Error::EncryptionFailed(e.to_string())) +} + +/// Decrypts data encrypted with SLIP-0021 derived keys. +/// +/// # Returns +/// `MmResult, DecryptionError>` - The decrypted data, or an error. +#[allow(dead_code)] +pub fn decrypt_with_slip21(encrypted_data: &EncryptedData, master_secret: &[u8; 64]) -> MmResult, SLIP21Error> { + let (encryption_path, authentication_path) = match &encrypted_data.key_derivation_details { + KeyDerivationDetails::SLIP0021 { + encryption_path, + authentication_path, + } => (encryption_path, authentication_path), + _ => { + return MmError::err(SLIP21Error::KeyDerivationError( + "Key derivation details should be SLIP0021!".to_string(), + )) + }, + }; + + // Derive encryption and authentication keys using SLIP-0021 + let (key_aes, key_hmac) = + derive_encryption_authentication_keys(master_secret, encryption_path, authentication_path)?; + + decrypt_data(encrypted_data, &key_aes, &key_hmac).mm_err(|e| SLIP21Error::DecryptionFailed(e.to_string())) +} + +#[cfg(any(test, target_arch = "wasm32"))] +mod tests { + use super::*; + use common::cross_test; + use std::convert::TryInto; + + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + cross_test!(test_encrypt_decrypt_with_slip21, { + let data = b"Example data to encrypt and decrypt using SLIP-0021"; + let master_secret = hex::decode("c76c4ac4f4e4a00d6b274d5c39c700bb4a7ddc04fbc6f78e85ca75007b5b495f74a9043eeb77bdd53aa6fc3a0e31462270316fa04b8c19114c8798706cd02ac8").unwrap().try_into().unwrap(); + let derivation_path = "test/path"; + + // Encrypt the data + let encrypted_data_result = encrypt_with_slip21(data, &master_secret, derivation_path); + assert!(encrypted_data_result.is_ok()); + let encrypted_data = encrypted_data_result.unwrap(); + + // Decrypt the data + let decrypted_data_result = decrypt_with_slip21(&encrypted_data, &master_secret); + assert!(decrypted_data_result.is_ok()); + let decrypted_data = decrypted_data_result.unwrap(); + + // Verify if decrypted data matches the original data + assert_eq!(data.to_vec(), decrypted_data); + }); +} diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml index bc4c37e8cb..b34f1911a3 100644 --- a/mm2src/gossipsub/Cargo.toml +++ b/mm2src/gossipsub/Cargo.toml @@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"] doctest = false [dependencies] -base64 = "0.11.0" +base64 = "0.21.2" bytes = "0.5.4" byteorder = "1.3.2" common = { path = "../common" } @@ -25,7 +25,7 @@ libp2p-core = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1 log = "0.4.17" prost = "0.10" rand = "0.7" -sha2 = "0.9" +sha2 = "0.10" smallvec = "1.1.0" unsigned-varint = { version = "0.4.0", features = ["futures-codec"] } wasm-timer = "0.2.4" diff --git a/mm2src/mm2_bitcoin/crypto/Cargo.toml b/mm2src/mm2_bitcoin/crypto/Cargo.toml index fba1c0851f..20a62aa693 100644 --- a/mm2src/mm2_bitcoin/crypto/Cargo.toml +++ b/mm2src/mm2_bitcoin/crypto/Cargo.toml @@ -11,7 +11,7 @@ groestl = "0.9" primitives = { path = "../primitives" } ripemd160 = "0.9.0" sha-1 = "0.9" -sha2 = "0.9" +sha2 = "0.10" sha3 = "0.9" siphasher = "0.1.1" serialization = { path = "../serialization" } diff --git a/mm2src/mm2_bitcoin/crypto/src/lib.rs b/mm2src/mm2_bitcoin/crypto/src/lib.rs index afaa9e4102..f8e6e59388 100644 --- a/mm2src/mm2_bitcoin/crypto/src/lib.rs +++ b/mm2src/mm2_bitcoin/crypto/src/lib.rs @@ -11,7 +11,7 @@ use groestl::Groestl512; use primitives::hash::{H160, H256, H32, H512}; use ripemd160::{Digest, Ripemd160}; use sha1::Sha1; -use sha2::Sha256; +use sha2::{Digest as Sha2Digest, Sha256}; use sha3::Keccak256; use siphasher::sip::SipHasher24; use std::hash::Hasher; diff --git a/mm2src/mm2_bitcoin/spv_validation/Cargo.toml b/mm2src/mm2_bitcoin/spv_validation/Cargo.toml index 1627ff24a3..53e840955b 100644 --- a/mm2src/mm2_bitcoin/spv_validation/Cargo.toml +++ b/mm2src/mm2_bitcoin/spv_validation/Cargo.toml @@ -17,7 +17,7 @@ ripemd160 = "0.9.0" rustc-hex = "2" serde = "1.0" serialization = { path = "../serialization" } -sha2 = "0.9" +sha2 = "0.10" test_helpers = { path = "../test_helpers" } [dev-dependencies] diff --git a/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs b/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs index d3ce01813d..fcdd5ce5ab 100644 --- a/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs +++ b/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs @@ -4,8 +4,8 @@ use crate::work::{next_block_bits, NextBlockBitsError}; use chain::{BlockHeader, RawHeaderError}; use derive_more::Display; use primitives::hash::H256; -use ripemd160::Digest; use serialization::parse_compact_int; +use sha2::digest::Digest; use sha2::Sha256; #[derive(Clone, Debug, Display, Eq, PartialEq)] diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index 04de2e4d87..5c184e0dfb 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -109,6 +109,11 @@ pub struct MmCtx { pub swaps_ctx: Mutex>>, /// The context belonging to the `lp_stats` mod: `StatsContext` pub stats_ctx: Mutex>>, + /// Wallet name for this mm2 instance. Optional for backwards compatibility. + pub wallet_name: Constructible>, + /// The context belonging to the `lp_wallet` mod: `WalletsContext`. + #[cfg(target_arch = "wasm32")] + pub wallets_ctx: Mutex>>, /// The RPC sender forwarding requests to writing part of underlying stream. #[cfg(target_arch = "wasm32")] pub wasm_rpc: Constructible, @@ -165,6 +170,9 @@ impl MmCtx { coins_needed_for_kick_start: Mutex::new(HashSet::new()), swaps_ctx: Mutex::new(None), stats_ctx: Mutex::new(None), + wallet_name: Constructible::default(), + #[cfg(target_arch = "wasm32")] + wallets_ctx: Mutex::new(None), #[cfg(target_arch = "wasm32")] wasm_rpc: Constructible::default(), #[cfg(not(target_arch = "wasm32"))] @@ -278,6 +286,12 @@ impl MmCtx { }) } + #[cfg(not(target_arch = "wasm32"))] + pub fn wallet_file_path(&self, wallet_name: &str) -> PathBuf { + let db_root = path_to_db_root(self.conf["dbdir"].as_str()); + db_root.join(wallet_name.to_string() + ".dat") + } + /// MM database path. /// Defaults to a relative "DB". /// @@ -393,15 +407,21 @@ impl Drop for MmCtx { } } -/// This function can be used later by an FFI function to open a GUI storage. +/// Returns the path to the MM database root. #[cfg(not(target_arch = "wasm32"))] -pub fn path_to_dbdir(db_root: Option<&str>, db_id: &H160) -> PathBuf { +fn path_to_db_root(db_root: Option<&str>) -> &Path { const DEFAULT_ROOT: &str = "DB"; - let path = match db_root { + match db_root { Some(dbdir) if !dbdir.is_empty() => Path::new(dbdir), _ => Path::new(DEFAULT_ROOT), - }; + } +} + +/// This function can be used later by an FFI function to open a GUI storage. +#[cfg(not(target_arch = "wasm32"))] +pub fn path_to_dbdir(db_root: Option<&str>, db_id: &H160) -> PathBuf { + let path = path_to_db_root(db_root); path.join(hex::encode(db_id.as_slice())) } diff --git a/mm2src/mm2_db/src/indexed_db/db_lock.rs b/mm2src/mm2_db/src/indexed_db/db_lock.rs index 2a650f0c5f..1ca10262f9 100644 --- a/mm2src/mm2_db/src/indexed_db/db_lock.rs +++ b/mm2src/mm2_db/src/indexed_db/db_lock.rs @@ -14,7 +14,7 @@ pub struct ConstructibleDb { /// It's better to use something like [`Constructible`], but it doesn't provide a method to get the inner value by the mutable reference. mutex: AsyncMutex>, db_namespace: DbNamespaceId, - wallet_rmd160: H160, + wallet_rmd160: Option, } impl ConstructibleDb { @@ -26,7 +26,7 @@ impl ConstructibleDb { ConstructibleDb { mutex: AsyncMutex::new(None), db_namespace: ctx.db_namespace, - wallet_rmd160: *ctx.rmd160(), + wallet_rmd160: Some(*ctx.rmd160()), } } @@ -37,7 +37,17 @@ impl ConstructibleDb { ConstructibleDb { mutex: AsyncMutex::new(None), db_namespace: ctx.db_namespace, - wallet_rmd160: *ctx.shared_db_id(), + wallet_rmd160: Some(*ctx.shared_db_id()), + } + } + + /// Creates a new uninitialized `Db` instance shared between all wallets/seed. + /// This can be initialized later using [`ConstructibleDb::get_or_initialize`]. + pub fn new_global_db(ctx: &MmArc) -> Self { + ConstructibleDb { + mutex: AsyncMutex::new(None), + db_namespace: ctx.db_namespace, + wallet_rmd160: None, } } diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index b89ff84dad..d6e8ab15d4 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -86,14 +86,14 @@ pub struct DbIdentifier { namespace_id: DbNamespaceId, /// The `RIPEMD160(SHA256(x))` where x is secp256k1 pubkey derived from passphrase. /// This value is used to distinguish different databases corresponding to user's different seed phrases. - wallet_rmd160: H160, + wallet_rmd160: Option, db_name: &'static str, } impl DbIdentifier { pub fn db_name(&self) -> &'static str { self.db_name } - pub fn new(namespace_id: DbNamespaceId, wallet_rmd160: H160) -> DbIdentifier { + pub fn new(namespace_id: DbNamespaceId, wallet_rmd160: Option) -> DbIdentifier { DbIdentifier { namespace_id, wallet_rmd160, @@ -104,18 +104,22 @@ impl DbIdentifier { pub fn for_test(db_name: &'static str) -> DbIdentifier { DbIdentifier { namespace_id: DbNamespaceId::for_test(), - wallet_rmd160: H160::default(), + wallet_rmd160: Some(H160::default()), db_name, } } - pub fn display_rmd160(&self) -> String { hex::encode(*self.wallet_rmd160) } + pub fn display_rmd160(&self) -> String { + self.wallet_rmd160 + .map(hex::encode) + .unwrap_or_else(|| "KOMODEFI".to_string()) + } } pub struct IndexedDbBuilder { - db_name: String, - db_version: u32, - tables: HashMap, + pub db_name: String, + pub db_version: u32, + pub tables: HashMap, } impl IndexedDbBuilder { diff --git a/mm2src/mm2_libp2p/Cargo.toml b/mm2src/mm2_libp2p/Cargo.toml index 3a0f283e36..17f406c3b4 100644 --- a/mm2src/mm2_libp2p/Cargo.toml +++ b/mm2src/mm2_libp2p/Cargo.toml @@ -24,7 +24,7 @@ regex = "1" rmp-serde = "0.14.3" serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11.5" -sha2 = "0.9" +sha2 = "0.10" void = "1.0" wasm-timer = "0.2.4" diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index ef3c6cc049..556631b0e6 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -125,6 +125,7 @@ mm2_test_helpers = { path = "../mm2_test_helpers" } mocktopus = "0.8.0" testcontainers = "0.15.0" web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.20.0", default-features = false, features = ["http"] } +ethabi = { version = "17.0.0" } [build-dependencies] chrono = "0.4" diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 50c9f13c83..726ae612f4 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -22,7 +22,7 @@ use bitcrypto::sha256; use coins::register_balance_update_handler; use common::executor::{SpawnFuture, Timer}; use common::log::{info, warn}; -use crypto::{from_hw_error, CryptoCtx, CryptoInitError, HwError, HwProcessingError, HwRpcError, WithHwRpcError}; +use crypto::{from_hw_error, CryptoCtx, HwError, HwProcessingError, HwRpcError, WithHwRpcError}; use derive_more::Display; use enum_derives::EnumFromTrait; use mm2_core::mm_ctx::{MmArc, MmCtx}; @@ -53,6 +53,7 @@ use crate::mm2::lp_ordermatch::{broadcast_maker_orders_keep_alive_loop, clean_me lp_ordermatch_loop, orders_kick_start, BalanceUpdateOrdermatchHandler, OrdermatchInitError}; use crate::mm2::lp_swap::{running_swaps_num, swap_kick_starts}; +use crate::mm2::lp_wallet::{initialize_wallet_passphrase, WalletInitError}; use crate::mm2::rpc::spawn_rpc; cfg_native! { @@ -201,10 +202,8 @@ pub enum MmInitError { SwapsKickStartError(String), #[display(fmt = "Order kick start error: {}", _0)] OrdersKickStartError(String), - #[display(fmt = "Passphrase cannot be an empty string")] - EmptyPassphrase, - #[display(fmt = "Invalid passphrase: {}", _0)] - InvalidPassphrase(String), + #[display(fmt = "Error initializing wallet: {}", _0)] + WalletInitError(String), #[display(fmt = "NETWORK event initialization failed: {}", _0)] NetworkEventInitFailed(String), #[display(fmt = "HEARTBEAT event initialization failed: {}", _0)] @@ -246,25 +245,23 @@ impl From for MmInitError { } } -impl From for MmInitError { - fn from(e: InitMessageServiceError) -> Self { +impl From for MmInitError { + fn from(e: WalletInitError) -> Self { match e { - InitMessageServiceError::ErrorDeserializingConfig { field, error } => { + WalletInitError::ErrorDeserializingConfig { field, error } => { MmInitError::ErrorDeserializingConfig { field, error } }, + other => MmInitError::WalletInitError(other.to_string()), } } } -impl From for MmInitError { - fn from(e: CryptoInitError) -> Self { +impl From for MmInitError { + fn from(e: InitMessageServiceError) -> Self { match e { - e @ CryptoInitError::InitializedAlready | e @ CryptoInitError::NotInitialized => { - MmInitError::Internal(e.to_string()) + InitMessageServiceError::ErrorDeserializingConfig { field, error } => { + MmInitError::ErrorDeserializingConfig { field, error } }, - CryptoInitError::EmptyPassphrase => MmInitError::EmptyPassphrase, - CryptoInitError::InvalidPassphrase(pass) => MmInitError::InvalidPassphrase(pass.to_string()), - CryptoInitError::Internal(internal) => MmInitError::Internal(internal), } } } @@ -338,10 +335,6 @@ pub fn fix_directories(ctx: &MmCtx) -> MmInitResult<()> { fix_shared_dbdir(ctx)?; let dbdir = ctx.dbdir(); - fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { - path: dbdir.clone(), - error: e.to_string(), - })?; if !ensure_dir_is_writable(&dbdir.join("SWAPS")) { return MmError::err(MmInitError::db_directory_is_not_writable("SWAPS")); @@ -502,25 +495,21 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { Ok(()) } -#[cfg_attr(target_arch = "wasm32", allow(unused_variables))] -/// * `ctx_cb` - callback used to share the `MmCtx` ID with the call site. pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitResult<()> { info!("Version: {} DT {}", version, datetime); - if !ctx.conf["passphrase"].is_null() { - let passphrase: String = - json::from_value(ctx.conf["passphrase"].clone()).map_to_mm(|e| MmInitError::ErrorDeserializingConfig { - field: "passphrase".to_owned(), - error: e.to_string(), - })?; - - // This defaults to false to maintain backward compatibility. - match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { - true => CryptoCtx::init_with_global_hd_account(ctx.clone(), &passphrase)?, - false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), &passphrase)?, - }; + #[cfg(not(target_arch = "wasm32"))] + { + let dbdir = ctx.dbdir(); + fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { + path: dbdir.clone(), + error: e.to_string(), + })?; } + // This either initializes the cryptographic context or sets up the context for "no login mode". + initialize_wallet_passphrase(&ctx).await?; + lp_init_continue(ctx.clone()).await?; let ctx_id = ctx.ffi_handle().map_to_mm(MmInitError::Internal)?; diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index a1393bf71e..04699c0dee 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -5802,7 +5802,7 @@ fn orderbook_address( ) -> Result> { let protocol: CoinProtocol = json::from_value(conf["protocol"].clone())?; match protocol { - CoinProtocol::ERC20 { .. } | CoinProtocol::ETH | CoinProtocol::Nft { .. } => { + CoinProtocol::ERC20 { .. } | CoinProtocol::ETH | CoinProtocol::NFT { .. } => { coins::eth::addr_from_pubkey_str(pubkey) .map(OrderbookAddress::Transparent) .map_to_mm(OrderbookAddrErr::AddrFromPubkeyError) diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index e401e96692..6143b708f1 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -8,8 +8,8 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_s SwapConfirmationsSettings, TransactionIdentifier, MAKER_SWAP_V2_TYPE, MAX_STARTED_AT_DIFF}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; -use coins::{CanRefundHtlc, CoinAssocTypes, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, - GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, RefundMakerPaymentArgs, +use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, GenTakerFundingSpendArgs, + GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundMakerPaymentArgs, RefundPaymentArgs, SearchForFundingSpendErr, SendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradePreimageValue, Transaction, TxPreimageWithSig, ValidateTakerFundingArgs}; use common::executor::abortable_queue::AbortableQueue; @@ -1004,7 +1004,7 @@ impl { +struct NegotiationData { taker_payment_locktime: u64, taker_funding_locktime: u64, maker_coin_htlc_pub_from_taker: MakerCoin::Pubkey, @@ -1014,7 +1014,7 @@ struct NegotiationData { taker_secret_hash: Vec, } -impl NegotiationData { +impl NegotiationData { fn to_stored_data(&self) -> StoredNegotiationData { StoredNegotiationData { taker_payment_locktime: self.taker_payment_locktime, @@ -1048,14 +1048,14 @@ impl NegotiationData { +struct WaitingForTakerFunding { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, maker_payment_trade_fee: SavedTradeFee, } -impl TransitionFrom> +impl TransitionFrom> for WaitingForTakerFunding { } @@ -1133,7 +1133,7 @@ impl { +struct TakerFundingReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, @@ -1141,8 +1141,8 @@ struct TakerFundingReceived TransitionFrom> - for TakerFundingReceived +impl + TransitionFrom> for TakerFundingReceived { } @@ -1245,7 +1245,7 @@ impl { +struct MakerPaymentSentFundingSpendGenerated { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, @@ -1254,7 +1254,8 @@ struct MakerPaymentSentFundingSpendGenerated TransitionFrom> +impl + TransitionFrom> for MakerPaymentSentFundingSpendGenerated { } @@ -1414,7 +1415,7 @@ pub enum MakerPaymentRefundReason { ErrorOnTakerFundingSpendSearch(String), } -struct MakerPaymentRefundRequired { +struct MakerPaymentRefundRequired { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, @@ -1422,12 +1423,12 @@ struct MakerPaymentRefundRequired +impl TransitionFrom> for MakerPaymentRefundRequired { } -impl +impl TransitionFrom> for MakerPaymentRefundRequired { } @@ -1548,7 +1549,7 @@ impl { +struct TakerPaymentReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: MakerCoin::Tx, @@ -1556,7 +1557,7 @@ struct TakerPaymentReceived, } -impl +impl TransitionFrom> for TakerPaymentReceived { @@ -1728,7 +1729,7 @@ impl { +struct TakerPaymentSpent { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: MakerCoin::Tx, @@ -1736,8 +1737,8 @@ struct TakerPaymentSpent { taker_payment_spend: TakerCoin::Tx, } -impl TransitionFrom> - for TakerPaymentSpent +impl + TransitionFrom> for TakerPaymentSpent { } @@ -1844,15 +1845,15 @@ impl TransitionFrom> for Aborted {} impl TransitionFrom> for Aborted {} -impl TransitionFrom> - for Aborted +impl + TransitionFrom> for Aborted { } -impl TransitionFrom> - for Aborted +impl + TransitionFrom> for Aborted { } -impl +impl TransitionFrom> for Aborted { } @@ -1893,12 +1894,12 @@ impl TransitionFrom> - for Completed +impl + TransitionFrom> for Completed { } -struct MakerPaymentRefunded { +struct MakerPaymentRefunded { taker_coin: PhantomData, maker_payment: MakerCoin::Tx, maker_payment_refund: MakerCoin::Tx, @@ -1942,7 +1943,7 @@ impl +impl TransitionFrom> for MakerPaymentRefunded { } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 6a29e3dee6..5f1389adbd 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -8,10 +8,11 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_s TAKER_SWAP_V2_TYPE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; -use coins::{CanRefundHtlc, CoinAssocTypes, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerFundingSpendArgs, - GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, RefundFundingSecretArgs, RefundPaymentArgs, - SendTakerFundingArgs, SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, - TradeFee, TradePreimageValue, Transaction, TxPreimageWithSig, ValidateMakerPaymentArgs}; +use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerFundingSpendArgs, + GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundFundingSecretArgs, + RefundPaymentArgs, SendTakerFundingArgs, SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, + TakerCoinSwapOpsV2, ToBytes, TradeFee, TradePreimageValue, Transaction, TxPreimageWithSig, + ValidateMakerPaymentArgs}; use common::executor::abortable_queue::AbortableQueue; use common::executor::{AbortableSystem, Timer}; use common::log::{debug, error, info, warn}; @@ -1153,7 +1154,7 @@ impl { +struct NegotiationData { maker_secret_hash: Vec, maker_payment_locktime: u64, maker_coin_htlc_pub_from_maker: MakerCoin::Pubkey, @@ -1163,7 +1164,7 @@ struct NegotiationData { taker_coin_maker_address: TakerCoin::Address, } -impl NegotiationData { +impl NegotiationData { fn to_stored_data(&self) -> StoredNegotiationData { StoredNegotiationData { maker_payment_locktime: self.maker_payment_locktime, @@ -1199,7 +1200,7 @@ impl NegotiationData { +struct Negotiated { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, @@ -1207,7 +1208,7 @@ struct Negotiated { maker_payment_spend_fee: SavedTradeFee, } -impl TransitionFrom> +impl TransitionFrom> for Negotiated { } @@ -1270,7 +1271,7 @@ impl { +struct TakerFundingSent { maker_coin_start_block: u64, taker_coin_start_block: u64, taker_funding: TakerCoin::Tx, @@ -1388,7 +1389,7 @@ impl TransitionFrom> +impl TransitionFrom> for TakerFundingSent { } @@ -1411,7 +1412,7 @@ impl { +struct MakerPaymentAndFundingSpendPreimgReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, @@ -1420,7 +1421,8 @@ struct MakerPaymentAndFundingSpendPreimgReceived TransitionFrom> +impl + TransitionFrom> for MakerPaymentAndFundingSpendPreimgReceived { } @@ -1585,7 +1587,7 @@ impl { +struct TakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, taker_payment: TakerCoin::Tx, @@ -1593,11 +1595,11 @@ struct TakerPaymentSent { negotiation_data: NegotiationData, } -impl TransitionFrom> - for TakerPaymentSent +impl + TransitionFrom> for TakerPaymentSent { } -impl +impl TransitionFrom> for TakerPaymentSent { @@ -1750,7 +1752,7 @@ pub enum TakerFundingRefundReason { MakerPaymentNotConfirmedInTime(String), } -struct TakerFundingRefundRequired { +struct TakerFundingRefundRequired { maker_coin_start_block: u64, taker_coin_start_block: u64, taker_funding: TakerCoin::Tx, @@ -1758,17 +1760,17 @@ struct TakerFundingRefundRequired TransitionFrom> - for TakerFundingRefundRequired +impl + TransitionFrom> for TakerFundingRefundRequired { } -impl +impl TransitionFrom> for TakerFundingRefundRequired { } -impl TransitionFrom> - for TakerFundingRefundRequired +impl + TransitionFrom> for TakerFundingRefundRequired { } @@ -1842,18 +1844,18 @@ pub enum TakerPaymentRefundReason { MakerDidNotSpendInTime(String), } -struct TakerPaymentRefundRequired { +struct TakerPaymentRefundRequired { taker_payment: TakerCoin::Tx, negotiation_data: NegotiationData, reason: TakerPaymentRefundReason, } -impl TransitionFrom> - for TakerPaymentRefundRequired +impl + TransitionFrom> for TakerPaymentRefundRequired { } -impl TransitionFrom> - for TakerPaymentRefundRequired +impl + TransitionFrom> for TakerPaymentRefundRequired { } @@ -1939,7 +1941,7 @@ impl { +struct MakerPaymentConfirmed { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: MakerCoin::Tx, @@ -1948,7 +1950,7 @@ struct MakerPaymentConfirmed, } -impl +impl TransitionFrom> for MakerPaymentConfirmed { @@ -2035,7 +2037,7 @@ impl { +struct TakerPaymentSpent { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: MakerCoin::Tx, @@ -2044,8 +2046,8 @@ struct TakerPaymentSpent { negotiation_data: NegotiationData, } -impl TransitionFrom> - for TakerPaymentSpent +impl + TransitionFrom> for TakerPaymentSpent { } @@ -2134,7 +2136,7 @@ impl { +struct MakerPaymentSpent { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: MakerCoin::Tx, @@ -2143,8 +2145,8 @@ struct MakerPaymentSpent { maker_payment_spend: MakerCoin::Tx, } -impl TransitionFrom> - for MakerPaymentSpent +impl + TransitionFrom> for MakerPaymentSpent { } @@ -2255,19 +2257,19 @@ impl TransitionFrom> for Aborted {} impl TransitionFrom> for Aborted {} -impl TransitionFrom> +impl TransitionFrom> for Aborted { } -impl TransitionFrom> - for Aborted +impl + TransitionFrom> for Aborted { } -impl +impl TransitionFrom> for Aborted { } -impl +impl TransitionFrom> for Aborted { } @@ -2308,12 +2310,12 @@ impl TransitionFrom> - for Completed +impl + TransitionFrom> for Completed { } -struct TakerFundingRefunded { +struct TakerFundingRefunded { maker_coin: PhantomData, funding_tx: TakerCoin::Tx, funding_refund_tx: TakerCoin::Tx, @@ -2357,12 +2359,12 @@ impl +impl TransitionFrom> for TakerFundingRefunded { } -struct TakerPaymentRefunded { +struct TakerPaymentRefunded { maker_coin: PhantomData, taker_payment: TakerCoin::Tx, taker_payment_refund: TransactionIdentifier, @@ -2403,7 +2405,7 @@ impl +impl TransitionFrom> for TakerPaymentRefunded { } diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs new file mode 100644 index 0000000000..e84ea8e98c --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -0,0 +1,505 @@ +use common::HttpStatusCode; +use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedData, + MnemonicError}; +use http::StatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use serde::de::DeserializeOwned; +use serde_json::{self as json}; + +cfg_wasm32! { + use crate::mm2::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; + use mm2_core::mm_ctx::from_ctx; + use mm2_db::indexed_db::{ConstructibleDb, DbLocked, InitDbResult}; + use mnemonics_wasm_db::{read_encrypted_passphrase_if_available, save_encrypted_passphrase}; + use std::sync::Arc; + + type WalletsDbLocked<'a> = DbLocked<'a, WalletsDb>; +} + +cfg_native! { + use mnemonics_storage::{read_encrypted_passphrase_if_available, save_encrypted_passphrase, WalletsStorageError}; +} + +#[cfg(not(target_arch = "wasm32"))] +#[path = "lp_wallet/mnemonics_storage.rs"] +mod mnemonics_storage; +#[cfg(target_arch = "wasm32")] +#[path = "lp_wallet/mnemonics_wasm_db.rs"] +mod mnemonics_wasm_db; + +type WalletInitResult = Result>; + +#[derive(Debug, Deserialize, Display, Serialize)] +pub enum WalletInitError { + #[display(fmt = "Error deserializing '{}' config field: {}", field, error)] + ErrorDeserializingConfig { + field: String, + error: String, + }, + #[display(fmt = "The '{}' field not found in the config", field)] + FieldNotFoundInConfig { + field: String, + }, + #[display(fmt = "Wallets storage error: {}", _0)] + WalletsStorageError(String), + #[display( + fmt = "Passphrase doesn't match the one from file, please create a new wallet if you want to use a new passphrase" + )] + PassphraseMismatch, + #[display(fmt = "Error generating or decrypting mnemonic: {}", _0)] + MnemonicError(String), + #[display(fmt = "Error initializing crypto context: {}", _0)] + CryptoInitError(String), + InternalError(String), +} + +impl From for WalletInitError { + fn from(e: MnemonicError) -> Self { WalletInitError::MnemonicError(e.to_string()) } +} + +impl From for WalletInitError { + fn from(e: CryptoInitError) -> Self { WalletInitError::CryptoInitError(e.to_string()) } +} + +#[derive(Debug, Deserialize, Display, Serialize)] +pub enum ReadPassphraseError { + #[display(fmt = "Wallets storage error: {}", _0)] + WalletsStorageError(String), + #[display(fmt = "Error decrypting passphrase: {}", _0)] + DecryptionError(String), +} + +impl From for WalletInitError { + fn from(e: ReadPassphraseError) -> Self { + match e { + ReadPassphraseError::WalletsStorageError(e) => WalletInitError::WalletsStorageError(e), + ReadPassphraseError::DecryptionError(e) => WalletInitError::MnemonicError(e), + } + } +} + +#[cfg(target_arch = "wasm32")] +struct WalletsContext { + wallets_db: ConstructibleDb, +} + +#[cfg(target_arch = "wasm32")] +impl WalletsContext { + /// Obtains a reference to this crate context, creating it if necessary. + fn from_ctx(ctx: &MmArc) -> Result, String> { + Ok(try_s!(from_ctx(&ctx.wallets_ctx, move || { + Ok(WalletsContext { + #[cfg(target_arch = "wasm32")] + wallets_db: ConstructibleDb::new_global_db(ctx), + }) + }))) + } + + pub async fn wallets_db(&self) -> InitDbResult> { self.wallets_db.get_or_initialize().await } +} + +// Utility function for deserialization to reduce repetition +fn deserialize_config_field(ctx: &MmArc, field: &str) -> WalletInitResult { + json::from_value::(ctx.conf[field].clone()).map_to_mm(|e| WalletInitError::ErrorDeserializingConfig { + field: field.to_owned(), + error: e.to_string(), + }) +} + +// Utility function to handle passphrase encryption and saving +async fn encrypt_and_save_passphrase( + ctx: &MmArc, + wallet_name: &str, + passphrase: &str, + wallet_password: &str, +) -> WalletInitResult<()> { + let encrypted_passphrase_data = encrypt_mnemonic(passphrase, wallet_password)?; + save_encrypted_passphrase(ctx, wallet_name, &encrypted_passphrase_data) + .await + .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string())) +} + +/// Reads and decrypts the passphrase from a file associated with the given wallet name, if available. +/// +/// This function first checks if a passphrase is available. If a passphrase is found, +/// since it is stored in an encrypted format, it decrypts it before returning. If no passphrase is found, +/// it returns `None`. +/// +/// # Returns +/// `MmInitResult` - The decrypted passphrase or an error if any operation fails. +/// +/// # Errors +/// Returns specific `MmInitError` variants for different failure scenarios. +async fn read_and_decrypt_passphrase_if_available( + ctx: &MmArc, + wallet_password: &str, +) -> MmResult, ReadPassphraseError> { + match read_encrypted_passphrase_if_available(ctx) + .await + .mm_err(|e| ReadPassphraseError::WalletsStorageError(e.to_string()))? + { + Some(encrypted_passphrase) => { + let mnemonic = decrypt_mnemonic(&encrypted_passphrase, wallet_password) + .mm_err(|e| ReadPassphraseError::DecryptionError(e.to_string()))?; + Ok(Some(mnemonic.to_string())) + }, + None => Ok(None), + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +enum Passphrase { + Encrypted(EncryptedData), + Decrypted(String), +} + +fn deserialize_wallet_config(ctx: &MmArc) -> WalletInitResult<(Option, Option)> { + let passphrase = deserialize_config_field::>(ctx, "passphrase")?; + // New approach for passphrase, `wallet_name` is needed in the config to enable multi-wallet support. + // In this case the passphrase will be generated if not provided. + // The passphrase will then be encrypted and saved whether it was generated or provided. + let wallet_name = deserialize_config_field::>(ctx, "wallet_name")?; + Ok((wallet_name, passphrase)) +} + +/// Passphrase is not provided. Generate, encrypt and save passphrase if not already saved. +async fn retrieve_or_create_passphrase( + ctx: &MmArc, + wallet_name: &str, + wallet_password: &str, +) -> WalletInitResult> { + match read_and_decrypt_passphrase_if_available(ctx, wallet_password).await? { + Some(passphrase_from_file) => { + // If an existing passphrase is found, return it + Ok(Some(passphrase_from_file)) + }, + None => { + // If no passphrase is found, generate a new one + let new_passphrase = generate_mnemonic(ctx)?.to_string(); + // Encrypt and save the new passphrase + encrypt_and_save_passphrase(ctx, wallet_name, &new_passphrase, wallet_password).await?; + Ok(Some(new_passphrase)) + }, + } +} + +/// Passphrase is provided in plaintext. Encrypt and save passphrase if not already saved. +async fn confirm_or_encrypt_and_store_passphrase( + ctx: &MmArc, + wallet_name: &str, + passphrase: &str, + wallet_password: &str, +) -> WalletInitResult> { + match read_and_decrypt_passphrase_if_available(ctx, wallet_password).await? { + Some(passphrase_from_file) if passphrase == passphrase_from_file => { + // If an existing passphrase is found and it matches the provided passphrase, return it + Ok(Some(passphrase_from_file)) + }, + None => { + // If no passphrase is found in the file, encrypt and save the provided passphrase + encrypt_and_save_passphrase(ctx, wallet_name, passphrase, wallet_password).await?; + Ok(Some(passphrase.to_string())) + }, + _ => { + // If an existing passphrase is found and it does not match the provided passphrase, return an error + Err(WalletInitError::PassphraseMismatch.into()) + }, + } +} + +/// Encrypted passphrase is provided. Decrypt and save encrypted passphrase if not already saved. +async fn decrypt_validate_or_save_passphrase( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: EncryptedData, + wallet_password: &str, +) -> WalletInitResult> { + // Decrypt the provided encrypted passphrase + let decrypted_passphrase = decrypt_mnemonic(&encrypted_passphrase_data, wallet_password)?.to_string(); + + match read_and_decrypt_passphrase_if_available(ctx, wallet_password).await? { + Some(passphrase_from_file) if decrypted_passphrase == passphrase_from_file => { + // If an existing passphrase is found and it matches the decrypted passphrase, return it + Ok(Some(decrypted_passphrase)) + }, + None => { + save_encrypted_passphrase(ctx, wallet_name, &encrypted_passphrase_data) + .await + .mm_err(|e| WalletInitError::WalletsStorageError(e.to_string()))?; + Ok(Some(decrypted_passphrase)) + }, + _ => { + // If an existing passphrase is found and it does not match the decrypted passphrase, return an error + Err(WalletInitError::PassphraseMismatch.into()) + }, + } +} + +async fn process_wallet_with_name( + ctx: &MmArc, + wallet_name: &str, + passphrase: Option, + wallet_password: &str, +) -> WalletInitResult> { + match passphrase { + None => retrieve_or_create_passphrase(ctx, wallet_name, wallet_password).await, + Some(Passphrase::Decrypted(passphrase)) => { + confirm_or_encrypt_and_store_passphrase(ctx, wallet_name, &passphrase, wallet_password).await + }, + Some(Passphrase::Encrypted(encrypted_data)) => { + decrypt_validate_or_save_passphrase(ctx, wallet_name, encrypted_data, wallet_password).await + }, + } +} + +async fn process_passphrase_logic( + ctx: &MmArc, + wallet_name: Option, + passphrase: Option, +) -> WalletInitResult> { + match (wallet_name, passphrase) { + (None, None) => Ok(None), + // Legacy approach for passphrase, no `wallet_name` is needed in the config, in this case the passphrase is not encrypted and saved. + (None, Some(Passphrase::Decrypted(passphrase))) => Ok(Some(passphrase)), + // Importing an encrypted passphrase without a wallet name is not supported since it's not possible to save the passphrase. + (None, Some(Passphrase::Encrypted(_))) => Err(WalletInitError::FieldNotFoundInConfig { + field: "wallet_name".to_owned(), + } + .into()), + + (Some(wallet_name), passphrase_option) => { + let wallet_password = deserialize_config_field::(ctx, "wallet_password")?; + process_wallet_with_name(ctx, &wallet_name, passphrase_option, &wallet_password).await + }, + } +} + +fn initialize_crypto_context(ctx: &MmArc, passphrase: &str) -> WalletInitResult<()> { + // This defaults to false to maintain backward compatibility. + match ctx.conf["enable_hd"].as_bool().unwrap_or(false) { + true => CryptoCtx::init_with_global_hd_account(ctx.clone(), passphrase)?, + false => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), passphrase)?, + }; + Ok(()) +} + +/// Initializes and manages the wallet passphrase. +/// +/// This function handles several scenarios based on the configuration: +/// - Deserializes the passphrase and wallet name from the configuration. +/// - If both wallet name and passphrase are `None`, the function sets up the context for "no login mode" +/// This mode can be entered after the function's execution, allowing access to Komodo DeFi Framework +/// functionalities that don't require a passphrase (e.g., viewing the orderbook). +/// - If a wallet name is provided without a passphrase, it first checks for the existence of a +/// passphrase file associated with the wallet. If no file is found, it generates a new passphrase, +/// encrypts it, and saves it, enabling multi-wallet support. +/// - If a passphrase is provided (with or without a wallet name), it uses the provided passphrase +/// and handles encryption and storage as needed. +/// - Initializes the cryptographic context based on the `enable_hd` configuration. +/// +/// # Returns +/// `MmInitResult<()>` - Result indicating success or failure of the initialization process. +/// +/// # Errors +/// Returns `MmInitError` if deserialization fails or if there are issues in passphrase handling. +/// +pub(crate) async fn initialize_wallet_passphrase(ctx: &MmArc) -> WalletInitResult<()> { + let (wallet_name, passphrase) = deserialize_wallet_config(ctx)?; + ctx.wallet_name + .pin(wallet_name.clone()) + .map_to_mm(WalletInitError::InternalError)?; + let passphrase = process_passphrase_logic(ctx, wallet_name, passphrase).await?; + + if let Some(passphrase) = passphrase { + initialize_crypto_context(ctx, &passphrase)?; + } + + Ok(()) +} + +/// `MnemonicFormat` is an enum representing the format of a mnemonic. +/// +/// It has two variants: +/// - `Encrypted`: This variant represents an encrypted mnemonic. It does not carry any associated data. +/// - `PlainText`: This variant represents a plaintext mnemonic. It carries the password to decrypt the mnemonic in string format. +#[derive(Debug, Deserialize)] +#[serde(tag = "format", content = "password", rename_all = "lowercase")] +pub enum MnemonicFormat { + Encrypted, + PlainText(String), +} + +/// `GetMnemonicRequest` is a struct representing a request to get a mnemonic. +/// +/// It contains a single field, `mnemonic_format`, which is an instance of the `MnemonicFormat` enum. +/// The `#[serde(flatten)]` attribute is used so that the fields of the `MnemonicFormat` enum are included +/// directly in the `GetMnemonicRequest` when it is deserialized, rather than nested under a +/// `mnemonic_format` field. +/// +/// # Examples +/// +/// For a `GetMnemonicRequest` where the `MnemonicFormat` is `Encrypted`, the JSON representation would be: +/// ```json +/// { +/// "format": "encrypted" +/// } +/// ``` +/// +/// For a `GetMnemonicRequest` where the `MnemonicFormat` is `PlainText` with a password of "password123", the JSON representation would be: +/// ```json +/// { +/// "format": "plaintext", +/// "password": "password123" +/// } +/// ``` +#[derive(Debug, Deserialize)] +pub struct GetMnemonicRequest { + #[serde(flatten)] + pub mnemonic_format: MnemonicFormat, +} + +/// `MnemonicForRpc` is an enum representing the format of a mnemonic for RPC communication. +/// +/// It has two variants: +/// - `Encrypted`: This variant represents an encrypted mnemonic. It carries the [`EncryptedData`] struct. +/// - `PlainText`: This variant represents a plaintext mnemonic. It carries the mnemonic as a `String`. +#[derive(Serialize)] +#[serde(tag = "format", rename_all = "lowercase")] +pub enum MnemonicForRpc { + Encrypted { encrypted_mnemonic_data: EncryptedData }, + PlainText { mnemonic: String }, +} + +impl From for MnemonicForRpc { + fn from(encrypted_mnemonic_data: EncryptedData) -> Self { + MnemonicForRpc::Encrypted { + encrypted_mnemonic_data, + } + } +} + +impl From for MnemonicForRpc { + fn from(mnemonic: String) -> Self { MnemonicForRpc::PlainText { mnemonic } } +} + +/// [`GetMnemonicResponse`] is a struct representing the response to a get mnemonic request. +/// +/// It contains a single field, `mnemonic`, which is an instance of the [`MnemonicForRpc`] enum. +/// The `#[serde(flatten)]` attribute is used so that the fields of the [`MnemonicForRpc`] enum are included +/// directly in the [`GetMnemonicResponse`] when it is serialized, rather than nested under a +/// `mnemonic` field. +/// +/// # Examples +/// +/// For a [`GetMnemonicResponse`] where the [`MnemonicForRpc`] is `Encrypted` with some [`EncryptedData`], the JSON representation would be: +/// ```json +/// { +/// "format": "encrypted", +/// "encrypted_mnemonic_data": { +/// // EncryptedData fields go here +/// } +/// } +/// ``` +/// +/// For a `GetMnemonicResponse` where the `MnemonicForRpc` is `PlainText` with a mnemonic of "your_mnemonic_here", the JSON representation would be: +/// ```json +/// { +/// "format": "plaintext", +/// "mnemonic": "your_mnemonic_here" +/// } +/// ``` +#[derive(Serialize)] +pub struct GetMnemonicResponse { + #[serde(flatten)] + pub mnemonic: MnemonicForRpc, +} + +#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetMnemonicError { + #[display(fmt = "Invalid request error: {}", _0)] + InvalidRequest(String), + #[display(fmt = "Wallets storage error: {}", _0)] + WalletsStorageError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), +} + +impl HttpStatusCode for GetMnemonicError { + fn status_code(&self) -> StatusCode { + match self { + GetMnemonicError::InvalidRequest(_) => StatusCode::BAD_REQUEST, + GetMnemonicError::WalletsStorageError(_) | GetMnemonicError::Internal(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for GetMnemonicError { + fn from(e: WalletsStorageError) -> Self { GetMnemonicError::WalletsStorageError(e.to_string()) } +} + +#[cfg(target_arch = "wasm32")] +impl From for GetMnemonicError { + fn from(e: WalletsDBError) -> Self { GetMnemonicError::WalletsStorageError(e.to_string()) } +} + +impl From for GetMnemonicError { + fn from(e: ReadPassphraseError) -> Self { GetMnemonicError::WalletsStorageError(e.to_string()) } +} + +/// Retrieves the wallet mnemonic in the requested format. +/// +/// # Returns +/// +/// A `Result` type containing: +/// +/// * [`Ok`]([`GetMnemonicResponse`]) - The wallet mnemonic in the requested format. +/// * [`MmError`]<[`GetMnemonicError>`]> - Returns specific [`GetMnemonicError`] variants for different failure scenarios. +/// +/// # Errors +/// +/// This function will return an error in the following situations: +/// +/// * The wallet name is not found in the context. +/// * The wallet is initialized without a name. +/// * The wallet passphrase file is not found for `MnemonicFormat::Encrypted`. +/// * The wallet mnemonic file is not found for `MnemonicFormat::PlainText`. +/// +/// # Examples +/// +/// ```rust +/// let ctx = MmArc::new(MmCtx::default()); +/// let req = GetMnemonicRequest { +/// mnemonic_format: MnemonicFormat::Encrypted, +/// }; +/// let result = get_mnemonic_rpc(ctx, req).await; +/// match result { +/// Ok(response) => println!("Mnemonic: {:?}", response.mnemonic), +/// Err(e) => println!("Error: {:?}", e), +/// } +/// ``` +pub async fn get_mnemonic_rpc(ctx: MmArc, req: GetMnemonicRequest) -> MmResult { + match req.mnemonic_format { + MnemonicFormat::Encrypted => { + let encrypted_mnemonic = read_encrypted_passphrase_if_available(&ctx) + .await? + .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet mnemonic file not found".to_string()))?; + Ok(GetMnemonicResponse { + mnemonic: encrypted_mnemonic.into(), + }) + }, + MnemonicFormat::PlainText(wallet_password) => { + let plaintext_mnemonic = read_and_decrypt_passphrase_if_available(&ctx, &wallet_password) + .await? + .ok_or_else(|| GetMnemonicError::InvalidRequest("Wallet mnemonic file not found".to_string()))?; + Ok(GetMnemonicResponse { + mnemonic: plaintext_mnemonic.into(), + }) + }, + } +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs new file mode 100644 index 0000000000..3cf40e61fb --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -0,0 +1,63 @@ +use crypto::EncryptedData; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use mm2_io::fs::ensure_file_is_writable; + +type WalletsStorageResult = Result>; + +#[derive(Debug, Deserialize, Display, Serialize)] +pub enum WalletsStorageError { + #[display(fmt = "Error writing to file: {}", _0)] + FsWriteError(String), + #[display(fmt = "Error reading from file: {}", _0)] + FsReadError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), +} + +/// Saves the passphrase to a file associated with the given wallet name. +/// +/// # Returns +/// Result indicating success or an error. +pub(super) async fn save_encrypted_passphrase( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: &EncryptedData, +) -> WalletsStorageResult<()> { + let wallet_path = ctx.wallet_file_path(wallet_name); + ensure_file_is_writable(&wallet_path).map_to_mm(WalletsStorageError::FsWriteError)?; + mm2_io::fs::write_json(encrypted_passphrase_data, &wallet_path, true) + .await + .mm_err(|e| WalletsStorageError::FsWriteError(e.to_string())) +} + +/// Reads the encrypted passphrase data from the file associated with the given wallet name, if available. +/// +/// This function is responsible for retrieving the encrypted passphrase data from a file, if it exists. +/// The data is expected to be in the format of `EncryptedData`, which includes +/// all necessary components for decryption, such as the encryption algorithm, key derivation +/// +/// # Returns +/// `io::Result` - The encrypted passphrase data or an error if the +/// reading process fails. +/// +/// # Errors +/// Returns an `io::Error` if the file cannot be read or the data cannot be deserialized into +/// `EncryptedData`. +pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> WalletsStorageResult> { + let wallet_name = ctx + .wallet_name + .ok_or(WalletsStorageError::Internal( + "`wallet_name` not initialized yet!".to_string(), + ))? + .clone() + .ok_or_else(|| WalletsStorageError::Internal("`wallet_name` cannot be None!".to_string()))?; + let wallet_path = ctx.wallet_file_path(&wallet_name); + mm2_io::fs::read_json(&wallet_path).await.mm_err(|e| { + WalletsStorageError::FsReadError(format!( + "Error reading passphrase from file {}: {}", + wallet_path.display(), + e + )) + }) +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs new file mode 100644 index 0000000000..f7bf2674e4 --- /dev/null +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -0,0 +1,146 @@ +use crate::mm2::lp_wallet::WalletsContext; +use async_trait::async_trait; +use crypto::EncryptedData; +use mm2_core::mm_ctx::MmArc; +use mm2_core::DbNamespaceId; +use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbTransactionError, DbUpgrader, IndexedDb, IndexedDbBuilder, + InitDbError, InitDbResult, OnUpgradeError, OnUpgradeResult, TableSignature}; +use mm2_err_handle::prelude::*; +use std::collections::HashMap; +use std::ops::Deref; + +const DB_VERSION: u32 = 1; + +type WalletsDBResult = Result>; + +#[derive(Debug, Deserialize, Display, Serialize)] +pub enum WalletsDBError { + #[display(fmt = "Error deserializing '{}': {}", field, error)] + DeserializationError { + field: String, + error: String, + }, + #[display(fmt = "Error serializing '{}': {}", field, error)] + SerializationError { + field: String, + error: String, + }, + Internal(String), +} + +impl From for WalletsDBError { + fn from(e: InitDbError) -> Self { WalletsDBError::Internal(e.to_string()) } +} + +impl From for WalletsDBError { + fn from(e: DbTransactionError) -> Self { WalletsDBError::Internal(e.to_string()) } +} + +#[derive(Debug, Deserialize, Serialize)] +struct MnemonicsTable { + wallet_name: String, + encrypted_mnemonic: String, +} + +pub struct WalletsDb { + inner: IndexedDb, +} + +#[async_trait] +impl DbInstance for WalletsDb { + const DB_NAME: &'static str = "wallets"; + + async fn init(db_id: DbIdentifier) -> InitDbResult { + let inner = IndexedDbBuilder::new(db_id) + .with_version(DB_VERSION) + .with_table::() + .build() + .await?; + Ok(WalletsDb { inner }) + } +} + +impl Deref for WalletsDb { + type Target = IndexedDb; + + fn deref(&self) -> &Self::Target { &self.inner } +} + +impl TableSignature for MnemonicsTable { + const TABLE_NAME: &'static str = "mnemonics"; + + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + let table = upgrader.create_table(Self::TABLE_NAME)?; + table.create_index("wallet_name", true)?; + }, + // handle new versions here if needed + unsupported_version => { + return MmError::err(OnUpgradeError::UnsupportedVersion { + unsupported_version, + old_version, + new_version, + }) + }, + } + + old_version += 1; + } + Ok(()) + } +} + +pub(super) async fn save_encrypted_passphrase( + ctx: &MmArc, + wallet_name: &str, + encrypted_passphrase_data: &EncryptedData, +) -> WalletsDBResult<()> { + let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; + + let db = wallets_ctx.wallets_db().await?; + let transaction = db.transaction().await?; + let table = transaction.table::().await?; + + let mnemonics_table_item = MnemonicsTable { + wallet_name: wallet_name.to_string(), + encrypted_mnemonic: serde_json::to_string(encrypted_passphrase_data).map_err(|e| { + WalletsDBError::SerializationError { + field: "encrypted_mnemonic".to_string(), + error: e.to_string(), + } + })?, + }; + table.add_item(&mnemonics_table_item).await?; + + Ok(()) +} + +pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> WalletsDBResult> { + let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; + + let db = wallets_ctx.wallets_db().await?; + let transaction = db.transaction().await?; + let table = transaction.table::().await?; + + let wallet_name = ctx + .wallet_name + .ok_or(WalletsDBError::Internal( + "`wallet_name` not initialized yet!".to_string(), + ))? + .clone() + .ok_or_else(|| WalletsDBError::Internal("`wallet_name` can't be None!".to_string()))?; + table + .get_item_by_unique_index("wallet_name", wallet_name) + .await? + .map(|(_item_id, wallet_table_item)| { + serde_json::from_str(&wallet_table_item.encrypted_mnemonic).map_to_mm(|e| { + WalletsDBError::DeserializationError { + field: "encrypted_mnemonic".to_string(), + error: e.to_string(), + } + }) + }) + .transpose() +} diff --git a/mm2src/mm2_main/src/mm2.rs b/mm2src/mm2_main/src/mm2.rs index 49c713ae43..ecaec4fd9e 100644 --- a/mm2src/mm2_main/src/mm2.rs +++ b/mm2src/mm2_main/src/mm2.rs @@ -65,6 +65,7 @@ pub mod database; #[path = "lp_ordermatch.rs"] pub mod lp_ordermatch; #[path = "lp_stats.rs"] pub mod lp_stats; #[path = "lp_swap.rs"] pub mod lp_swap; +#[path = "lp_wallet.rs"] pub mod lp_wallet; #[path = "rpc.rs"] pub mod rpc; pub const PASSWORD_MAXIMUM_CONSECUTIVE_CHARACTERS: usize = 3; diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 02c6911c53..430f85f154 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -5,6 +5,7 @@ use crate::mm2::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_ use crate::mm2::lp_ordermatch::{best_orders_rpc_v2, orderbook_rpc_v2, start_simple_market_maker_bot, stop_simple_market_maker_bot}; use crate::mm2::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; +use crate::mm2::lp_wallet::get_mnemonic_rpc; use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, stop_version_stat_collection, update_version_stat_collection}, @@ -174,6 +175,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_current_mtp_rpc).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, + "get_mnemonic" => handle_mmrpc(ctx, request, get_mnemonic_rpc).await, "get_my_address" => handle_mmrpc(ctx, request, get_my_address).await, "get_new_address" => handle_mmrpc(ctx, request, get_new_address).await, "get_nft_list" => handle_mmrpc(ctx, request, get_nft_list).await, diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index 1640f1b263..5585492858 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -7,7 +7,7 @@ pub use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, ETH_DEV_SWAP_CONTRACT, ETH_DEV_TOKEN_CONTRACT, MAKER_ERROR_EVENTS, MAKER_SUCCESS_EVENTS, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; -use crate::docker_tests::eth_docker_tests::fill_eth; +use super::eth_docker_tests::{fill_eth, geth_account}; use bitcrypto::{dhash160, ChecksumType}; use chain::TransactionOutput; use coins::eth::{addr_from_raw_pubkey, eth_coin_from_conf_and_request, EthCoin}; @@ -24,6 +24,7 @@ use coins::utxo::{coin_daemon_data_dir, sat_from_big_decimal, zcash_params_path, use coins::{CoinProtocol, ConfirmPaymentInput, MarketCoinOps, PrivKeyBuildPolicy, Transaction}; use crypto::privkey::key_pair_from_seed; use crypto::Secp256k1Secret; +use ethabi::Token; use ethereum_types::{H160 as H160Eth, U256}; use futures01::Future; use http::StatusCode; @@ -48,7 +49,7 @@ use testcontainers::clients::Cli; use testcontainers::core::WaitFor; use testcontainers::{Container, GenericImage, RunnableImage}; use web3::transports::Http; -use web3::types::TransactionRequest; +use web3::types::{BlockId, BlockNumber, TransactionRequest}; use web3::Web3; lazy_static! { @@ -80,6 +81,12 @@ pub static mut GETH_ERC20_CONTRACT: H160Eth = H160Eth::zero(); pub static mut GETH_SWAP_CONTRACT: H160Eth = H160Eth::zero(); /// Swap contract (with watchers support) address on Geth dev node pub static mut GETH_WATCHERS_SWAP_CONTRACT: H160Eth = H160Eth::zero(); +/// ERC721 token address on Geth dev node +pub static mut GETH_ERC721_CONTRACT: H160Eth = H160Eth::zero(); +/// ERC1155 token address on Geth dev node +pub static mut GETH_ERC1155_CONTRACT: H160Eth = H160Eth::zero(); +/// Nft Swap contract address on Geth dev node +pub static mut GETH_NFT_SWAP_CONTRACT: H160Eth = H160Eth::zero(); pub static GETH_RPC_URL: &str = "http://127.0.0.1:8545"; pub const UTXO_ASSET_DOCKER_IMAGE: &str = "docker.io/artempikulin/testblockchain"; @@ -89,6 +96,11 @@ pub const GETH_DOCKER_IMAGE_WITH_TAG: &str = "docker.io/ethereum/client-go:stabl pub const QTUM_ADDRESS_LABEL: &str = "MM2_ADDRESS_LABEL"; +/// ERC721_TEST_TOKEN has additional mint function +pub const ERC721_TEST_ABI: &str = include_str!("../../../mm2_test_helpers/dummy_files/erc721_test_abi.json"); +/// ERC1155_TEST_TOKEN has additional mint function +pub const ERC1155_TEST_ABI: &str = include_str!("../../../mm2_test_helpers/dummy_files/erc1155_test_abi.json"); + /// Ticker of MYCOIN dockerized blockchain. pub const MYCOIN: &str = "MYCOIN"; /// Ticker of MYCOIN1 dockerized blockchain. @@ -97,6 +109,9 @@ pub const MYCOIN1: &str = "MYCOIN1"; pub const ERC20_TOKEN_BYTES: &str = "6080604052600860ff16600a0a633b9aca000260005534801561002157600080fd5b50600054600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610c69806100776000396000f3006080604052600436106100a4576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100a9578063095ea7b31461013957806318160ddd1461019e57806323b872dd146101c9578063313ce5671461024e5780635a3b7e421461027f57806370a082311461030f57806395d89b4114610366578063a9059cbb146103f6578063dd62ed3e1461045b575b600080fd5b3480156100b557600080fd5b506100be6104d2565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fe5780820151818401526020810190506100e3565b50505050905090810190601f16801561012b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014557600080fd5b50610184600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061050b565b604051808215151515815260200191505060405180910390f35b3480156101aa57600080fd5b506101b36106bb565b6040518082815260200191505060405180910390f35b3480156101d557600080fd5b50610234600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106c1565b604051808215151515815260200191505060405180910390f35b34801561025a57600080fd5b506102636109a1565b604051808260ff1660ff16815260200191505060405180910390f35b34801561028b57600080fd5b506102946109a6565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102d45780820151818401526020810190506102b9565b50505050905090810190601f1680156103015780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561031b57600080fd5b50610350600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506109df565b6040518082815260200191505060405180910390f35b34801561037257600080fd5b5061037b6109f7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103bb5780820151818401526020810190506103a0565b50505050905090810190601f1680156103e85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561040257600080fd5b50610441600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a30565b604051808215151515815260200191505060405180910390f35b34801561046757600080fd5b506104bc600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610be1565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f515243205445535400000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff161415151561053457600080fd5b60008314806105bf57506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054145b15156105ca57600080fd5b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a3600191505092915050565b60005481565b60008360008173ffffffffffffffffffffffffffffffffffffffff16141515156106ea57600080fd5b8360008173ffffffffffffffffffffffffffffffffffffffff161415151561071157600080fd5b610797600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610860600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506108ec600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c1f565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef866040518082815260200191505060405180910390a36001925050509392505050565b600881565b6040805190810160405280600981526020017f546f6b656e20302e31000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b6040805190810160405280600381526020017f515443000000000000000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff1614151515610a5957600080fd5b610aa2600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c06565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b2e600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c1f565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3600191505092915050565b6002602052816000526040600020602052806000526040600020600091509150505481565b6000818310151515610c1457fe5b818303905092915050565b6000808284019050838110151515610c3357fe5b80915050929150505600a165627a7a723058207f2e5248b61b80365ea08a0f6d11ac0b47374c4dfd538de76bc2f19591bbbba40029"; pub const SWAP_CONTRACT_BYTES: &str = "608060405234801561001057600080fd5b50611437806100206000396000f3fe60806040526004361061004a5760003560e01c806302ed292b1461004f5780630716326d146100de578063152cf3af1461017b57806346fc0294146101f65780639b415b2a14610294575b600080fd5b34801561005b57600080fd5b506100dc600480360360a081101561007257600080fd5b81019080803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610339565b005b3480156100ea57600080fd5b506101176004803603602081101561010157600080fd5b8101908080359060200190929190505050610867565b60405180846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526020018367ffffffffffffffff1667ffffffffffffffff16815260200182600381111561016557fe5b60ff168152602001935050505060405180910390f35b6101f46004803603608081101561019157600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff1690602001909291905050506108bf565b005b34801561020257600080fd5b50610292600480360360a081101561021957600080fd5b81019080803590602001909291908035906020019092919080356bffffffffffffffffffffffff19169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610bd9565b005b610337600480360360c08110156102aa57600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff169060200190929190505050610fe2565b005b6001600381111561034657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff16600381111561037457fe5b1461037e57600080fd5b6000600333836003600288604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106103db57805182526020820191506020810190506020830392506103b8565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561041d573d6000803e3d6000fd5b5050506040513d602081101561043257600080fd5b8101908080519060200190929190505050604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106104955780518252602082019150602081019050602083039250610472565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156104d7573d6000803e3d6000fd5b5050506040515160601b8689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b602083106105fc57805182526020820191506020810190506020830392506105d9565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561063e573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461069657600080fd5b6002600080888152602001908152602001600020600001601c6101000a81548160ff021916908360038111156106c857fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561074e573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610748573d6000803e3d6000fd5b50610820565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156107da57600080fd5b505af11580156107ee573d6000803e3d6000fd5b505050506040513d602081101561080457600080fd5b810190808051906020019092919050505061081e57600080fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8685604051808381526020018281526020019250505060405180910390a1505050505050565b60006020528060005260406000206000915090508060000160009054906101000a900460601b908060000160149054906101000a900467ffffffffffffffff169080600001601c9054906101000a900460ff16905083565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156108fc5750600034115b801561094057506000600381111561091057fe5b600080868152602001908152602001600020600001601c9054906101000a900460ff16600381111561093e57fe5b145b61094957600080fd5b60006003843385600034604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610a6c5780518252602082019150602081019050602083039250610a49565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610aae573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff16815260200160016003811115610af757fe5b81525060008087815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff02191690836003811115610b9357fe5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57856040518082815260200191505060405180910390a15050505050565b60016003811115610be657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff166003811115610c1457fe5b14610c1e57600080fd5b600060038233868689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610d405780518252602082019150602081019050602083039250610d1d565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610d82573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610e10575060008087815260200190815260200160002060000160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610e1957600080fd5b6003600080888152602001908152602001600020600001601c6101000a81548160ff02191690836003811115610e4b57fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ed1573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610ecb573d6000803e3d6000fd5b50610fa3565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015610f5d57600080fd5b505af1158015610f71573d6000803e3d6000fd5b505050506040513d6020811015610f8757600080fd5b8101908080519060200190929190505050610fa157600080fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba866040518082815260200191505060405180910390a1505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561101f5750600085115b801561106357506000600381111561103357fe5b600080888152602001908152602001600020600001601c9054906101000a900460ff16600381111561106157fe5b145b61106c57600080fd5b60006003843385888a604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b6020831061118e578051825260208201915060208101905060208303925061116b565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156111d0573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561121957fe5b81525060008089815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff021916908360038111156112b557fe5b021790555090505060008590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b15801561137d57600080fd5b505af1158015611391573d6000803e3d6000fd5b505050506040513d60208110156113a757600080fd5b81019080805190602001909291905050506113c157600080fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040518082815260200191505060405180910390a1505050505050505056fea265627a7a723158208c83db436905afce0b7be1012be64818c49323c12d451fe2ab6bce76ff6421c964736f6c63430005110032"; pub const WATCHERS_SWAP_CONTRACT_BYTES: &str = "608060405234801561000f575f80fd5b50612aa48061001d5f395ff3fe608060405260043610610085575f3560e01c806346fc02941161005857806346fc0294146101275780636a3227861461014f5780639b415b2a1461016b578063b5985c4d14610193578063cd1dde34146101bb57610085565b806302ed292b146100895780630716326d146100b15780630971fd54146100ef578063152cf3af1461010b575b5f80fd5b348015610094575f80fd5b506100af60048036038101906100aa9190611e1d565b6101e3565b005b3480156100bc575f80fd5b506100d760048036038101906100d29190611e94565b610518565b6040516100e693929190611f8e565b60405180910390f35b6101096004803603810190610104919061206f565b610568565b005b6101256004803603810190610120919061210c565b610787565b005b348015610132575f80fd5b5061014d60048036038101906101489190612170565b61099d565b005b610169600480360381019061016491906121e7565b610c4d565b005b348015610176575f80fd5b50610191600480360381019061018c91906122ab565b610f61565b005b34801561019e575f80fd5b506101b960048036038101906101b49190612334565b611203565b005b3480156101c6575f80fd5b506101e160048036038101906101dc91906123f8565b611887565b005b600160038111156101f7576101f6611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff16600381111561022957610228611f1b565b5b14610232575f80fd5b5f60033383600360028860405160200161024c91906124dc565b6040516020818303038152906040526040516102689190612562565b602060405180830381855afa158015610283573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906102a6919061258c565b6040516020016102b691906124dc565b6040516020818303038152906040526040516102d29190612562565b602060405180830381855afa1580156102ed573d5f803e3d5ffd5b5050506040515160601b868960405160200161030d95949392919061263c565b6040516020818303038152906040526040516103299190612562565b602060405180830381855afa158015610344573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610397575f80fd5b60025f808881526020019081526020015f205f01601c6101000a81548160ff021916908360038111156103cd576103cc611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361044e573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610448573d5f803e3d5ffd5b506104d7565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b815260040161048d9291906126b8565b6020604051808303815f875af11580156104a9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104cd91906126f3565b6104d5575f80fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e868560405161050892919061272d565b60405180910390a1505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900467ffffffffffffffff1690805f01601c9054906101000a900460ff16905083565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16141580156105a357505f34115b80156105f157505f60038111156105bd576105bc611f1b565b5b5f808981526020019081526020015f205f01601c9054906101000a900460ff1660038111156105ef576105ee611f1b565b5b145b6105f9575f80fd5b5f60038733885f3489898960405160200161061b9897969594939291906127e7565b6040516020818303038152906040526040516106379190612562565b602060405180830381855afa158015610652573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff168152602001600160038111156106a2576106a1611f1b565b5b8152505f808a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561073e5761073d611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516107759190612878565b60405180910390a15050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156107c257505f34115b801561081057505f60038111156107dc576107db611f1b565b5b5f808681526020019081526020015f205f01601c9054906101000a900460ff16600381111561080e5761080d611f1b565b5b145b610818575f80fd5b5f60038433855f3460405160200161083495949392919061263c565b6040516020818303038152906040526040516108509190612562565b602060405180830381855afa15801561086b573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff168152602001600160038111156108bb576108ba611f1b565b5b8152505f808781526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561095757610956611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578560405161098e9190612878565b60405180910390a15050505050565b600160038111156109b1576109b0611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff1660038111156109e3576109e2611f1b565b5b146109ec575f80fd5b5f60038233868689604051602001610a0895949392919061263c565b604051602081830303815290604052604051610a249190612562565b602060405180830381855afa158015610a3f573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610ac657505f808781526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610ace575f80fd5b60035f808881526020019081526020015f205f01601c6101000a81548160ff02191690836003811115610b0457610b03611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610b85573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610b7f573d5f803e3d5ffd5b50610c0e565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401610bc49291906126b8565b6020604051808303815f875af1158015610be0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c0491906126f3565b610c0c575f80fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba86604051610c3d9190612878565b60405180910390a1505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614158015610c8857505f88115b8015610cd657505f6003811115610ca257610ca1611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff166003811115610cd457610cd3611f1b565b5b145b610cde575f80fd5b5f6003811115610cf157610cf0611f1b565b5b836003811115610d0457610d03611f1b565b5b14158015610d365750600380811115610d2057610d1f611f1b565b5b836003811115610d3357610d32611f1b565b5b14155b15610d4757803414610d46575f80fd5b5b5f60038733888b8d898989604051602001610d699897969594939291906127e7565b604051602081830303815290604052604051610d859190612562565b602060405180830381855afa158015610da0573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff16815260200160016003811115610df057610def611f1b565b5b8152505f808c81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff02191690836003811115610e8c57610e8b611f1b565b5b02179055509050505f8890508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308d6040518463ffffffff1660e01b8152600401610ed593929190612891565b6020604051808303815f875af1158015610ef1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1591906126f3565b610f1d575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578b604051610f4c9190612878565b60405180910390a15050505050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614158015610f9c57505f85115b8015610fea57505f6003811115610fb657610fb5611f1b565b5b5f808881526020019081526020015f205f01601c9054906101000a900460ff166003811115610fe857610fe7611f1b565b5b145b610ff2575f80fd5b5f6003843385888a60405160200161100e95949392919061263c565b60405160208183030381529060405260405161102a9190612562565b602060405180830381855afa158015611045573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561109557611094611f1b565b5b8152505f808981526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561113157611130611f1b565b5b02179055509050505f8590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b815260040161117a93929190612891565b6020604051808303815f875af1158015611196573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111ba91906126f3565b6111c2575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516111f19190612878565b60405180910390a15050505050505050565b6001600381111561121757611216611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff16600381111561124957611248611f1b565b5b14611289576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161128090612920565b60405180910390fd5b5f60038587600360028c6040516020016112a391906124dc565b6040516020818303038152906040526040516112bf9190612562565b602060405180830381855afa1580156112da573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906112fd919061258c565b60405160200161130d91906124dc565b6040516020818303038152906040526040516113299190612562565b602060405180830381855afa158015611344573d5f803e3d5ffd5b5050506040515160601b8a8d89898960405160200161136a9897969594939291906127e7565b6040516020818303038152906040526040516113869190612562565b602060405180830381855afa1580156113a1573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461142b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161142290612988565b60405180910390fd5b60025f808c81526020019081526020015f205f01601c6101000a81548160ff0219169083600381111561146157611460611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff160361159e575f8060038111156114ad576114ac611f1b565b5b8560038111156114c0576114bf611f1b565b5b1480156114cb575083155b6114e057828a6114db91906129d3565b6114e2565b895b90508573ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611527573d5f803e3d5ffd5b5060038081111561153b5761153a611f1b565b5b85600381111561154e5761154d611f1b565b5b03611598573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611596573d5f803e3d5ffd5b505b50611786565b5f6003808111156115b2576115b1611f1b565b5b8560038111156115c5576115c4611f1b565b5b146115d057896115dd565b828a6115dc91906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb88846040518363ffffffff1660e01b815260040161161e9291906126b8565b6020604051808303815f875af115801561163a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061165e91906126f3565b61169d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161169490612a50565b60405180910390fd5b6003808111156116b0576116af611f1b565b5b8660038111156116c3576116c2611f1b565b5b03611783578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b81526004016117039291906126b8565b6020604051808303815f875af115801561171f573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061174391906126f3565b611782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161177990612a50565b60405180910390fd5b5b50505b6002600381111561179a57611799611f1b565b5b8460038111156117ad576117ac611f1b565b5b036117f7578573ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f193505050501580156117f5573d5f803e3d5ffd5b505b8215611842573373ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f19350505050158015611840573d5f803e3d5ffd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8a8960405161187392919061272d565b60405180910390a150505050505050505050565b6001600381111561189b5761189a611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff1660038111156118cd576118cc611f1b565b5b146118d6575f80fd5b5f600385878a8a8d8989896040516020016118f89897969594939291906127e7565b6040516020818303038152906040526040516119149190612562565b602060405180830381855afa15801561192f573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161480156119b657505f808b81526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b6119be575f80fd5b60035f808c81526020019081526020015f205f01601c6101000a81548160ff021916908360038111156119f4576119f3611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1603611b27575f806003811115611a4057611a3f611f1b565b5b856003811115611a5357611a52611f1b565b5b14611a6957828a611a6491906129d3565b611a6b565b895b90508673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611ab0573d5f803e3d5ffd5b505f6003811115611ac457611ac3611f1b565b5b856003811115611ad757611ad6611f1b565b5b14611b21573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611b1f573d5f803e3d5ffd5b505b50611d16565b5f600380811115611b3b57611b3a611f1b565b5b856003811115611b4e57611b4d611f1b565b5b14611b595789611b66565b828a611b6591906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb89846040518363ffffffff1660e01b8152600401611ba79291906126b8565b6020604051808303815f875af1158015611bc3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611be791906126f3565b611bef575f80fd5b600380811115611c0257611c01611f1b565b5b866003811115611c1557611c14611f1b565b5b03611ca2578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b8152600401611c559291906126b8565b6020604051808303815f875af1158015611c71573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c9591906126f3565b611c9d575f80fd5b611d13565b5f6003811115611cb557611cb4611f1b565b5b866003811115611cc857611cc7611f1b565b5b14611d12573373ffffffffffffffffffffffffffffffffffffffff166108fc8590811502906040515f60405180830381858888f19350505050158015611d10573d5f803e3d5ffd5b505b5b50505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba8a604051611d459190612878565b60405180910390a150505050505050505050565b5f80fd5b5f819050919050565b611d6f81611d5d565b8114611d79575f80fd5b50565b5f81359050611d8a81611d66565b92915050565b5f819050919050565b611da281611d90565b8114611dac575f80fd5b50565b5f81359050611dbd81611d99565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611dec82611dc3565b9050919050565b611dfc81611de2565b8114611e06575f80fd5b50565b5f81359050611e1781611df3565b92915050565b5f805f805f60a08688031215611e3657611e35611d59565b5b5f611e4388828901611d7c565b9550506020611e5488828901611daf565b9450506040611e6588828901611d7c565b9350506060611e7688828901611e09565b9250506080611e8788828901611e09565b9150509295509295909350565b5f60208284031215611ea957611ea8611d59565b5b5f611eb684828501611d7c565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b611ef381611ebf565b82525050565b5f67ffffffffffffffff82169050919050565b611f1581611ef9565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60048110611f5957611f58611f1b565b5b50565b5f819050611f6982611f48565b919050565b5f611f7882611f5c565b9050919050565b611f8881611f6e565b82525050565b5f606082019050611fa15f830186611eea565b611fae6020830185611f0c565b611fbb6040830184611f7f565b949350505050565b611fcc81611ebf565b8114611fd6575f80fd5b50565b5f81359050611fe781611fc3565b92915050565b611ff681611ef9565b8114612000575f80fd5b50565b5f8135905061201181611fed565b92915050565b60048110612023575f80fd5b50565b5f8135905061203481612017565b92915050565b5f8115159050919050565b61204e8161203a565b8114612058575f80fd5b50565b5f8135905061206981612045565b92915050565b5f805f805f805f60e0888a03121561208a57612089611d59565b5b5f6120978a828b01611d7c565b97505060206120a88a828b01611e09565b96505060406120b98a828b01611fd9565b95505060606120ca8a828b01612003565b94505060806120db8a828b01612026565b93505060a06120ec8a828b0161205b565b92505060c06120fd8a828b01611daf565b91505092959891949750929550565b5f805f806080858703121561212457612123611d59565b5b5f61213187828801611d7c565b945050602061214287828801611e09565b935050604061215387828801611fd9565b925050606061216487828801612003565b91505092959194509250565b5f805f805f60a0868803121561218957612188611d59565b5b5f61219688828901611d7c565b95505060206121a788828901611daf565b94505060406121b888828901611fd9565b93505060606121c988828901611e09565b92505060806121da88828901611e09565b9150509295509295909350565b5f805f805f805f805f6101208a8c03121561220557612204611d59565b5b5f6122128c828d01611d7c565b99505060206122238c828d01611daf565b98505060406122348c828d01611e09565b97505060606122458c828d01611e09565b96505060806122568c828d01611fd9565b95505060a06122678c828d01612003565b94505060c06122788c828d01612026565b93505060e06122898c828d0161205b565b92505061010061229b8c828d01611daf565b9150509295985092959850929598565b5f805f805f8060c087890312156122c5576122c4611d59565b5b5f6122d289828a01611d7c565b96505060206122e389828a01611daf565b95505060406122f489828a01611e09565b945050606061230589828a01611e09565b935050608061231689828a01611fd9565b92505060a061232789828a01612003565b9150509295509295509295565b5f805f805f805f805f6101208a8c03121561235257612351611d59565b5b5f61235f8c828d01611d7c565b99505060206123708c828d01611daf565b98505060406123818c828d01611d7c565b97505060606123928c828d01611e09565b96505060806123a38c828d01611e09565b95505060a06123b48c828d01611e09565b94505060c06123c58c828d01612026565b93505060e06123d68c828d0161205b565b9250506101006123e88c828d01611daf565b9150509295985092959850929598565b5f805f805f805f805f6101208a8c03121561241657612415611d59565b5b5f6124238c828d01611d7c565b99505060206124348c828d01611daf565b98505060406124458c828d01611fd9565b97505060606124568c828d01611e09565b96505060806124678c828d01611e09565b95505060a06124788c828d01611e09565b94505060c06124898c828d01612026565b93505060e061249a8c828d0161205b565b9250506101006124ac8c828d01611daf565b9150509295985092959850929598565b5f819050919050565b6124d66124d182611d5d565b6124bc565b82525050565b5f6124e782846124c5565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b5f5b8381101561252757808201518184015260208101905061250c565b5f8484015250505050565b5f61253c826124f6565b6125468185612500565b935061255681856020860161250a565b80840191505092915050565b5f61256d8284612532565b915081905092915050565b5f8151905061258681611d66565b92915050565b5f602082840312156125a1576125a0611d59565b5b5f6125ae84828501612578565b91505092915050565b5f8160601b9050919050565b5f6125cd826125b7565b9050919050565b5f6125de826125c3565b9050919050565b6125f66125f182611de2565b6125d4565b82525050565b5f819050919050565b61261661261182611ebf565b6125fc565b82525050565b5f819050919050565b61263661263182611d90565b61261c565b82525050565b5f61264782886125e5565b60148201915061265782876125e5565b6014820191506126678286612605565b60148201915061267782856125e5565b6014820191506126878284612625565b6020820191508190509695505050505050565b6126a381611de2565b82525050565b6126b281611d90565b82525050565b5f6040820190506126cb5f83018561269a565b6126d860208301846126a9565b9392505050565b5f815190506126ed81612045565b92915050565b5f6020828403121561270857612707611d59565b5b5f612715848285016126df565b91505092915050565b61272781611d5d565b82525050565b5f6040820190506127405f83018561271e565b61274d602083018461271e565b9392505050565b6004811061276557612764611f1b565b5b50565b5f81905061277582612754565b919050565b5f61278482612768565b9050919050565b5f8160f81b9050919050565b5f6127a18261278b565b9050919050565b6127b96127b48261277a565b612797565b82525050565b5f6127c982612797565b9050919050565b6127e16127dc8261203a565b6127bf565b82525050565b5f6127f2828b6125e5565b601482019150612802828a6125e5565b6014820191506128128289612605565b60148201915061282282886125e5565b6014820191506128328287612625565b60208201915061284282866127a8565b60018201915061285282856127d0565b6001820191506128628284612625565b6020820191508190509998505050505050505050565b5f60208201905061288b5f83018461271e565b92915050565b5f6060820190506128a45f83018661269a565b6128b1602083018561269a565b6128be60408301846126a9565b949350505050565b5f82825260208201905092915050565b7f5061796d656e7420776173206e6f742073656e740000000000000000000000005f82015250565b5f61290a6014836128c6565b9150612915826128d6565b602082019050919050565b5f6020820190508181035f830152612937816128fe565b9050919050565b7f496e76616c6964207061796d656e7420686173680000000000000000000000005f82015250565b5f6129726014836128c6565b915061297d8261293e565b602082019050919050565b5f6020820190508181035f83015261299f81612966565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6129dd82611d90565b91506129e883611d90565b9250828203905081811115612a00576129ff6129a6565b5b92915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f612a3a6015836128c6565b9150612a4582612a06565b602082019050919050565b5f6020820190508181035f830152612a6781612a2e565b905091905056fea26469706673582212203106867e1b147b377237cde0aba42d82faf0282b83d7b6d62cca039d0b7f840564736f6c63430008160033"; +pub const ERC721_TEST_TOKEN_BYTES: &str = "608060405234801562000010575f80fd5b50604051620022ac380380620022ac8339818101604052810190620000369190620001ea565b8181815f9081620000489190620004a4565b5080600190816200005a9190620004a4565b505050505062000588565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f80604083850312156200020357620002026200006e565b5b5f83015167ffffffffffffffff81111562000223576200022262000072565b5b6200023185828601620001b8565b925050602083015167ffffffffffffffff81111562000255576200025462000072565b5b6200026385828601620001b8565b9150509250929050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620002bc57607f821691505b602082108103620002d257620002d162000277565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003367fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002f9565b620003428683620002f9565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6200038c6200038662000380846200035a565b62000363565b6200035a565b9050919050565b5f819050919050565b620003a7836200036c565b620003bf620003b68262000393565b84845462000305565b825550505050565b5f90565b620003d5620003c7565b620003e28184846200039c565b505050565b5b818110156200040957620003fd5f82620003cb565b600181019050620003e8565b5050565b601f82111562000458576200042281620002d8565b6200042d84620002ea565b810160208510156200043d578190505b620004556200044c85620002ea565b830182620003e7565b50505b505050565b5f82821c905092915050565b5f6200047a5f19846008026200045d565b1980831691505092915050565b5f62000494838362000469565b9150826002028217905092915050565b620004af826200026d565b67ffffffffffffffff811115620004cb57620004ca6200008e565b5b620004d78254620002a4565b620004e48282856200040d565b5f60209050601f8311600181146200051a575f841562000505578287015190505b62000511858262000487565b86555062000580565b601f1984166200052a86620002d8565b5f5b8281101562000553578489015182556001820191506020850194506020810190506200052c565b868310156200057357848901516200056f601f89168262000469565b8355505b6001600288020188555050505b505050505050565b611d1680620005965f395ff3fe608060405234801561000f575f80fd5b50600436106100e8575f3560e01c80636352211e1161008a578063a22cb46511610064578063a22cb46514610258578063b88d4fde14610274578063c87b56dd14610290578063e985e9c5146102c0576100e8565b80636352211e146101da57806370a082311461020a57806395d89b411461023a576100e8565b8063095ea7b3116100c6578063095ea7b31461016a57806323b872dd1461018657806340c10f19146101a257806342842e0e146101be576100e8565b806301ffc9a7146100ec57806306fdde031461011c578063081812fc1461013a575b5f80fd5b610106600480360381019061010191906115a7565b6102f0565b60405161011391906115ec565b60405180910390f35b6101246103d1565b604051610131919061168f565b60405180910390f35b610154600480360381019061014f91906116e2565b610460565b604051610161919061174c565b60405180910390f35b610184600480360381019061017f919061178f565b61047b565b005b6101a0600480360381019061019b91906117cd565b610491565b005b6101bc60048036038101906101b7919061178f565b610590565b005b6101d860048036038101906101d391906117cd565b61059e565b005b6101f460048036038101906101ef91906116e2565b6105bd565b604051610201919061174c565b60405180910390f35b610224600480360381019061021f919061181d565b6105ce565b6040516102319190611857565b60405180910390f35b610242610684565b60405161024f919061168f565b60405180910390f35b610272600480360381019061026d919061189a565b610714565b005b61028e60048036038101906102899190611a04565b61072a565b005b6102aa60048036038101906102a591906116e2565b610747565b6040516102b7919061168f565b60405180910390f35b6102da60048036038101906102d59190611a84565b6107ad565b6040516102e791906115ec565b60405180910390f35b5f7f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103ba57507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103ca57506103c98261083b565b5b9050919050565b60605f80546103df90611aef565b80601f016020809104026020016040519081016040528092919081815260200182805461040b90611aef565b80156104565780601f1061042d57610100808354040283529160200191610456565b820191905f5260205f20905b81548152906001019060200180831161043957829003601f168201915b5050505050905090565b5f61046a826108a4565b506104748261092a565b9050919050565b61048d8282610488610963565b61096a565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610501575f6040517f64a0ae920000000000000000000000000000000000000000000000000000000081526004016104f8919061174c565b60405180910390fd5b5f610514838361050f610963565b61097c565b90508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461058a578382826040517f64283d7b00000000000000000000000000000000000000000000000000000000815260040161058193929190611b1f565b60405180910390fd5b50505050565b61059a8282610b87565b5050565b6105b883838360405180602001604052805f81525061072a565b505050565b5f6105c7826108a4565b9050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361063f575f6040517f89c62b64000000000000000000000000000000000000000000000000000000008152600401610636919061174c565b60405180910390fd5b60035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606001805461069390611aef565b80601f01602080910402602001604051908101604052809291908181526020018280546106bf90611aef565b801561070a5780601f106106e15761010080835404028352916020019161070a565b820191905f5260205f20905b8154815290600101906020018083116106ed57829003601f168201915b5050505050905090565b61072661071f610963565b8383610c7a565b5050565b610735848484610491565b61074184848484610de3565b50505050565b6060610752826108a4565b505f61075c610f95565b90505f81511161077a5760405180602001604052805f8152506107a5565b8061078484610fab565b604051602001610795929190611b8e565b6040516020818303038152906040525b915050919050565b5f60055f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f806108af83611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092157826040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016109189190611857565b60405180910390fd5b80915050919050565b5f60045f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f33905090565b61097783838360016110ae565b505050565b5f8061098784611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16146109c8576109c781848661126d565b5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610a5357610a075f855f806110ae565b600160035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825403925050819055505b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614610ad257600160035f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8460025f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4809150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610bf7575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610bee919061174c565b60405180910390fd5b5f610c0383835f61097c565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610c75575f6040517f73c6ac6e000000000000000000000000000000000000000000000000000000008152600401610c6c919061174c565b60405180910390fd5b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610cea57816040517f5b08ba18000000000000000000000000000000000000000000000000000000008152600401610ce1919061174c565b60405180910390fd5b8060055f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610dd691906115ec565b60405180910390a3505050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b1115610f8f578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02610e26610963565b8685856040518563ffffffff1660e01b8152600401610e489493929190611c03565b6020604051808303815f875af1925050508015610e8357506040513d601f19601f82011682018060405250810190610e809190611c61565b60015b610f04573d805f8114610eb1576040519150601f19603f3d011682016040523d82523d5f602084013e610eb6565b606091505b505f815103610efc57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610ef3919061174c565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614610f8d57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610f84919061174c565b60405180910390fd5b505b50505050565b606060405180602001604052805f815250905090565b60605f6001610fb984611330565b0190505f8167ffffffffffffffff811115610fd757610fd66118e0565b5b6040519080825280601f01601f1916602001820160405280156110095781602001600182028036833780820191505090505b5090505f82602001820190505b60011561106a578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161105f5761105e611c8c565b5b0494505f8503611016575b819350505050919050565b5f60025f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b80806110e657505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b15611218575f6110f5846108a4565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561115f57508273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015611172575061117081846107ad565b155b156111b457826040517fa9fbf51f0000000000000000000000000000000000000000000000000000000081526004016111ab919061174c565b60405180910390fd5b811561121657838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b8360045f8581526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b611278838383611481565b61132b575f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036112ec57806040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016112e39190611857565b60405180910390fd5b81816040517f177e802f000000000000000000000000000000000000000000000000000000008152600401611322929190611cb9565b60405180910390fd5b505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000831061138c577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161138257611381611c8c565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106113c9576d04ee2d6d415b85acef810000000083816113bf576113be611c8c565b5b0492506020810190505b662386f26fc1000083106113f857662386f26fc1000083816113ee576113ed611c8c565b5b0492506010810190505b6305f5e1008310611421576305f5e100838161141757611416611c8c565b5b0492506008810190505b612710831061144657612710838161143c5761143b611c8c565b5b0492506004810190505b60648310611469576064838161145f5761145e611c8c565b5b0492506002810190505b600a8310611478576001810190505b80915050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561153857508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806114f957506114f884846107ad565b5b8061153757508273ffffffffffffffffffffffffffffffffffffffff1661151f8361092a565b73ffffffffffffffffffffffffffffffffffffffff16145b5b90509392505050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61158681611552565b8114611590575f80fd5b50565b5f813590506115a18161157d565b92915050565b5f602082840312156115bc576115bb61154a565b5b5f6115c984828501611593565b91505092915050565b5f8115159050919050565b6115e6816115d2565b82525050565b5f6020820190506115ff5f8301846115dd565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561163c578082015181840152602081019050611621565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61166182611605565b61166b818561160f565b935061167b81856020860161161f565b61168481611647565b840191505092915050565b5f6020820190508181035f8301526116a78184611657565b905092915050565b5f819050919050565b6116c1816116af565b81146116cb575f80fd5b50565b5f813590506116dc816116b8565b92915050565b5f602082840312156116f7576116f661154a565b5b5f611704848285016116ce565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6117368261170d565b9050919050565b6117468161172c565b82525050565b5f60208201905061175f5f83018461173d565b92915050565b61176e8161172c565b8114611778575f80fd5b50565b5f8135905061178981611765565b92915050565b5f80604083850312156117a5576117a461154a565b5b5f6117b28582860161177b565b92505060206117c3858286016116ce565b9150509250929050565b5f805f606084860312156117e4576117e361154a565b5b5f6117f18682870161177b565b93505060206118028682870161177b565b9250506040611813868287016116ce565b9150509250925092565b5f602082840312156118325761183161154a565b5b5f61183f8482850161177b565b91505092915050565b611851816116af565b82525050565b5f60208201905061186a5f830184611848565b92915050565b611879816115d2565b8114611883575f80fd5b50565b5f8135905061189481611870565b92915050565b5f80604083850312156118b0576118af61154a565b5b5f6118bd8582860161177b565b92505060206118ce85828601611886565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61191682611647565b810181811067ffffffffffffffff82111715611935576119346118e0565b5b80604052505050565b5f611947611541565b9050611953828261190d565b919050565b5f67ffffffffffffffff821115611972576119716118e0565b5b61197b82611647565b9050602081019050919050565b828183375f83830152505050565b5f6119a86119a384611958565b61193e565b9050828152602081018484840111156119c4576119c36118dc565b5b6119cf848285611988565b509392505050565b5f82601f8301126119eb576119ea6118d8565b5b81356119fb848260208601611996565b91505092915050565b5f805f8060808587031215611a1c57611a1b61154a565b5b5f611a298782880161177b565b9450506020611a3a8782880161177b565b9350506040611a4b878288016116ce565b925050606085013567ffffffffffffffff811115611a6c57611a6b61154e565b5b611a78878288016119d7565b91505092959194509250565b5f8060408385031215611a9a57611a9961154a565b5b5f611aa78582860161177b565b9250506020611ab88582860161177b565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611b0657607f821691505b602082108103611b1957611b18611ac2565b5b50919050565b5f606082019050611b325f83018661173d565b611b3f6020830185611848565b611b4c604083018461173d565b949350505050565b5f81905092915050565b5f611b6882611605565b611b728185611b54565b9350611b8281856020860161161f565b80840191505092915050565b5f611b998285611b5e565b9150611ba58284611b5e565b91508190509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611bd582611bb1565b611bdf8185611bbb565b9350611bef81856020860161161f565b611bf881611647565b840191505092915050565b5f608082019050611c165f83018761173d565b611c23602083018661173d565b611c306040830185611848565b8181036060830152611c428184611bcb565b905095945050505050565b5f81519050611c5b8161157d565b92915050565b5f60208284031215611c7657611c7561154a565b5b5f611c8384828501611c4d565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f604082019050611ccc5f83018561173d565b611cd96020830184611848565b939250505056fea26469706673582212207439b47c2a9a1624955997732075917bbf1da26949d000c778f561eb5687576164736f6c63430008180033"; +pub const ERC1155_TEST_TOKEN_BYTES: &str = "608060405234801562000010575f80fd5b50604051620024eb380380620024eb8339818101604052810190620000369190620001ea565b8062000048816200005060201b60201c565b505062000554565b806002908162000061919062000470565b5050565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f602082840312156200020257620002016200006e565b5b5f82015167ffffffffffffffff81111562000222576200022162000072565b5b6200023084828501620001b8565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200028857607f821691505b6020821081036200029e576200029d62000243565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002c5565b6200030e8683620002c5565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000358620003526200034c8462000326565b6200032f565b62000326565b9050919050565b5f819050919050565b620003738362000338565b6200038b62000382826200035f565b848454620002d1565b825550505050565b5f90565b620003a162000393565b620003ae81848462000368565b505050565b5b81811015620003d557620003c95f8262000397565b600181019050620003b4565b5050565b601f8211156200042457620003ee81620002a4565b620003f984620002b6565b8101602085101562000409578190505b620004216200041885620002b6565b830182620003b3565b50505b505050565b5f82821c905092915050565b5f620004465f198460080262000429565b1980831691505092915050565b5f62000460838362000435565b9150826002028217905092915050565b6200047b8262000239565b67ffffffffffffffff8111156200049757620004966200008e565b5b620004a3825462000270565b620004b0828285620003d9565b5f60209050601f831160018114620004e6575f8415620004d1578287015190505b620004dd858262000453565b8655506200054c565b601f198416620004f686620002a4565b5f5b828110156200051f57848901518255600182019150602085019450602081019050620004f8565b868310156200053f57848901516200053b601f89168262000435565b8355505b6001600288020188555050505b505050505050565b611f8980620005625f395ff3fe608060405234801561000f575f80fd5b5060043610610090575f3560e01c80634e1273f4116100645780634e1273f414610140578063731133e914610170578063a22cb4651461018c578063e985e9c5146101a8578063f242432a146101d857610090565b8062fdd58e1461009457806301ffc9a7146100c45780630e89341c146100f45780632eb2c2d614610124575b5f80fd5b6100ae60048036038101906100a991906113bd565b6101f4565b6040516100bb919061140a565b60405180910390f35b6100de60048036038101906100d99190611478565b610249565b6040516100eb91906114bd565b60405180910390f35b61010e600480360381019061010991906114d6565b61032a565b60405161011b919061158b565b60405180910390f35b61013e6004803603810190610139919061179b565b6103bc565b005b61015a60048036038101906101559190611926565b610463565b6040516101679190611a53565b60405180910390f35b61018a60048036038101906101859190611a73565b61056a565b005b6101a660048036038101906101a19190611b1d565b61057c565b005b6101c260048036038101906101bd9190611b5b565b610592565b6040516101cf91906114bd565b60405180910390f35b6101f260048036038101906101ed9190611b99565b610620565b005b5f805f8381526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f7fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061031357507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103235750610322826106c7565b5b9050919050565b60606002805461033990611c59565b80601f016020809104026020016040519081016040528092919081815260200182805461036590611c59565b80156103b05780601f10610387576101008083540402835291602001916103b0565b820191905f5260205f20905b81548152906001019060200180831161039357829003601f168201915b50505050509050919050565b5f6103c5610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561040a57506104088682610592565b155b1561044e5780866040517fe237d922000000000000000000000000000000000000000000000000000000008152600401610445929190611c98565b60405180910390fd5b61045b8686868686610737565b505050505050565b606081518351146104af57815183516040517f5b0599910000000000000000000000000000000000000000000000000000000081526004016104a6929190611cbf565b60405180910390fd5b5f835167ffffffffffffffff8111156104cb576104ca6115af565b5b6040519080825280602002602001820160405280156104f95781602001602082028036833780820191505090505b5090505f5b845181101561055f5761053561051d828761082b90919063ffffffff16565b610530838761083e90919063ffffffff16565b6101f4565b82828151811061054857610547611ce6565b5b6020026020010181815250508060010190506104fe565b508091505092915050565b61057684848484610851565b50505050565b61058e610587610730565b83836108e6565b5050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f610629610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561066e575061066c8682610592565b155b156106b25780866040517fe237d9220000000000000000000000000000000000000000000000000000000081526004016106a9929190611c98565b60405180910390fd5b6106bf8686868686610a4f565b505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f33905090565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036107a7575f6040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161079e9190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610817575f6040517f01a8351400000000000000000000000000000000000000000000000000000000815260040161080e9190611d13565b60405180910390fd5b6108248585858585610b55565b5050505050565b5f60208202602084010151905092915050565b5f60208202602084010151905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036108c1575f6040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016108b89190611d13565b60405180910390fd5b5f806108cd8585610c01565b915091506108de5f87848487610b55565b505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610956575f6040517fced3e10000000000000000000000000000000000000000000000000000000000815260040161094d9190611d13565b60405180910390fd5b8060015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610a4291906114bd565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610abf575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610ab69190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610b2f575f6040517f01a83514000000000000000000000000000000000000000000000000000000008152600401610b269190611d13565b60405180910390fd5b5f80610b3b8585610c01565b91509150610b4c8787848487610b55565b50505050505050565b610b6185858585610c31565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614610bfa575f610b9d610730565b90506001845103610be9575f610bbc5f8661083e90919063ffffffff16565b90505f610bd25f8661083e90919063ffffffff16565b9050610be2838989858589610fc1565b5050610bf8565b610bf7818787878787611170565b5b505b5050505050565b60608060405191506001825283602083015260408201905060018152826020820152604081016040529250929050565b8051825114610c7b57815181516040517f5b059991000000000000000000000000000000000000000000000000000000008152600401610c72929190611cbf565b60405180910390fd5b5f610c84610730565b90505f5b8351811015610e80575f610ca5828661083e90919063ffffffff16565b90505f610cbb838661083e90919063ffffffff16565b90505f73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614610dde575f805f8481526020019081526020015f205f8a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610d8a57888183856040517f03dee4c5000000000000000000000000000000000000000000000000000000008152600401610d819493929190611d2c565b60405180910390fd5b8181035f808581526020019081526020015f205f8b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610e7357805f808481526020019081526020015f205f8973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f828254610e6b9190611d9c565b925050819055505b5050806001019050610c88565b506001835103610f3b575f610e9e5f8561083e90919063ffffffff16565b90505f610eb45f8561083e90919063ffffffff16565b90508573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628585604051610f2c929190611cbf565b60405180910390a45050610fba565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051610fb1929190611dcf565b60405180910390a45b5050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611168578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b8152600401611021959493929190611e56565b6020604051808303815f875af192505050801561105c57506040513d601f19601f820116820180604052508101906110599190611ec2565b60015b6110dd573d805f811461108a576040519150601f19603f3d011682016040523d82523d5f602084013e61108f565b606091505b505f8151036110d557846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016110cc9190611d13565b60405180910390fd5b805181602001fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461116657846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161115d9190611d13565b60405180910390fd5b505b505050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611317578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016111d0959493929190611eed565b6020604051808303815f875af192505050801561120b57506040513d601f19601f820116820180604052508101906112089190611ec2565b60015b61128c573d805f8114611239576040519150601f19603f3d011682016040523d82523d5f602084013e61123e565b606091505b505f81510361128457846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161127b9190611d13565b60405180910390fd5b805181602001fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461131557846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161130c9190611d13565b60405180910390fd5b505b505050505050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61135982611330565b9050919050565b6113698161134f565b8114611373575f80fd5b50565b5f8135905061138481611360565b92915050565b5f819050919050565b61139c8161138a565b81146113a6575f80fd5b50565b5f813590506113b781611393565b92915050565b5f80604083850312156113d3576113d2611328565b5b5f6113e085828601611376565b92505060206113f1858286016113a9565b9150509250929050565b6114048161138a565b82525050565b5f60208201905061141d5f8301846113fb565b92915050565b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61145781611423565b8114611461575f80fd5b50565b5f813590506114728161144e565b92915050565b5f6020828403121561148d5761148c611328565b5b5f61149a84828501611464565b91505092915050565b5f8115159050919050565b6114b7816114a3565b82525050565b5f6020820190506114d05f8301846114ae565b92915050565b5f602082840312156114eb576114ea611328565b5b5f6114f8848285016113a9565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561153857808201518184015260208101905061151d565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61155d82611501565b611567818561150b565b935061157781856020860161151b565b61158081611543565b840191505092915050565b5f6020820190508181035f8301526115a38184611553565b905092915050565b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6115e582611543565b810181811067ffffffffffffffff82111715611604576116036115af565b5b80604052505050565b5f61161661131f565b905061162282826115dc565b919050565b5f67ffffffffffffffff821115611641576116406115af565b5b602082029050602081019050919050565b5f80fd5b5f61166861166384611627565b61160d565b9050808382526020820190506020840283018581111561168b5761168a611652565b5b835b818110156116b457806116a088826113a9565b84526020840193505060208101905061168d565b5050509392505050565b5f82601f8301126116d2576116d16115ab565b5b81356116e2848260208601611656565b91505092915050565b5f80fd5b5f67ffffffffffffffff821115611709576117086115af565b5b61171282611543565b9050602081019050919050565b828183375f83830152505050565b5f61173f61173a846116ef565b61160d565b90508281526020810184848401111561175b5761175a6116eb565b5b61176684828561171f565b509392505050565b5f82601f830112611782576117816115ab565b5b813561179284826020860161172d565b91505092915050565b5f805f805f60a086880312156117b4576117b3611328565b5b5f6117c188828901611376565b95505060206117d288828901611376565b945050604086013567ffffffffffffffff8111156117f3576117f261132c565b5b6117ff888289016116be565b935050606086013567ffffffffffffffff8111156118205761181f61132c565b5b61182c888289016116be565b925050608086013567ffffffffffffffff81111561184d5761184c61132c565b5b6118598882890161176e565b9150509295509295909350565b5f67ffffffffffffffff8211156118805761187f6115af565b5b602082029050602081019050919050565b5f6118a361189e84611866565b61160d565b905080838252602082019050602084028301858111156118c6576118c5611652565b5b835b818110156118ef57806118db8882611376565b8452602084019350506020810190506118c8565b5050509392505050565b5f82601f83011261190d5761190c6115ab565b5b813561191d848260208601611891565b91505092915050565b5f806040838503121561193c5761193b611328565b5b5f83013567ffffffffffffffff8111156119595761195861132c565b5b611965858286016118f9565b925050602083013567ffffffffffffffff8111156119865761198561132c565b5b611992858286016116be565b9150509250929050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6119ce8161138a565b82525050565b5f6119df83836119c5565b60208301905092915050565b5f602082019050919050565b5f611a018261199c565b611a0b81856119a6565b9350611a16836119b6565b805f5b83811015611a46578151611a2d88826119d4565b9750611a38836119eb565b925050600181019050611a19565b5085935050505092915050565b5f6020820190508181035f830152611a6b81846119f7565b905092915050565b5f805f8060808587031215611a8b57611a8a611328565b5b5f611a9887828801611376565b9450506020611aa9878288016113a9565b9350506040611aba878288016113a9565b925050606085013567ffffffffffffffff811115611adb57611ada61132c565b5b611ae78782880161176e565b91505092959194509250565b611afc816114a3565b8114611b06575f80fd5b50565b5f81359050611b1781611af3565b92915050565b5f8060408385031215611b3357611b32611328565b5b5f611b4085828601611376565b9250506020611b5185828601611b09565b9150509250929050565b5f8060408385031215611b7157611b70611328565b5b5f611b7e85828601611376565b9250506020611b8f85828601611376565b9150509250929050565b5f805f805f60a08688031215611bb257611bb1611328565b5b5f611bbf88828901611376565b9550506020611bd088828901611376565b9450506040611be1888289016113a9565b9350506060611bf2888289016113a9565b925050608086013567ffffffffffffffff811115611c1357611c1261132c565b5b611c1f8882890161176e565b9150509295509295909350565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611c7057607f821691505b602082108103611c8357611c82611c2c565b5b50919050565b611c928161134f565b82525050565b5f604082019050611cab5f830185611c89565b611cb86020830184611c89565b9392505050565b5f604082019050611cd25f8301856113fb565b611cdf60208301846113fb565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f602082019050611d265f830184611c89565b92915050565b5f608082019050611d3f5f830187611c89565b611d4c60208301866113fb565b611d5960408301856113fb565b611d6660608301846113fb565b95945050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611da68261138a565b9150611db18361138a565b9250828201905080821115611dc957611dc8611d6f565b5b92915050565b5f6040820190508181035f830152611de781856119f7565b90508181036020830152611dfb81846119f7565b90509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611e2882611e04565b611e328185611e0e565b9350611e4281856020860161151b565b611e4b81611543565b840191505092915050565b5f60a082019050611e695f830188611c89565b611e766020830187611c89565b611e8360408301866113fb565b611e9060608301856113fb565b8181036080830152611ea28184611e1e565b90509695505050505050565b5f81519050611ebc8161144e565b92915050565b5f60208284031215611ed757611ed6611328565b5b5f611ee484828501611eae565b91505092915050565b5f60a082019050611f005f830188611c89565b611f0d6020830187611c89565b8181036040830152611f1f81866119f7565b90508181036060830152611f3381856119f7565b90508181036080830152611f478184611e1e565b9050969550505050505056fea26469706673582212203835581c6344b12728c44fa4d9e912cd60e64012c1b772bb703d1c36825c16fd64736f6c63430008180033"; +pub const NFT_SWAP_CONTRACT_BYTES: &str = "60a060405234801562000010575f80fd5b50604051620055a2380380620055a2833981810160405281019062000036919062000147565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603620000a7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200009e90620001fb565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050506200021b565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6200011182620000e6565b9050919050565b620001238162000105565b81146200012e575f80fd5b50565b5f81519050620001418162000118565b92915050565b5f602082840312156200015f576200015e620000e2565b5b5f6200016e8482850162000131565b91505092915050565b5f82825260208201905092915050565b7f66656541646472657373206d757374206e6f74206265207a65726f20616464725f8201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b5f620001e360238362000177565b9150620001f08262000187565b604082019050919050565b5f6020820190508181035f8301526200021481620001d5565b9050919050565b608051615360620002425f395f8181612aef01528181612b8a0152612f4801526153605ff3fe608060405260043610610113575f3560e01c80639b4603f21161009f578063cc90c19911610063578063cc90c1991461038e578063d6a71eb4146103b6578063e06cf966146103de578063efccb9eb14610408578063f23a6e611461044657610113565b80639b4603f2146102be578063b27e46fb146102da578063bc197c8114610302578063c8d9009b1461033e578063c92cd12d1461036657610113565b8063150b7a02116100e6578063150b7a02146101cb5780633e6af5f21461020757806346b95ac71461022f57806365e266171461026e5780636e6bf6d21461029657610113565b806301ffc9a71461011757806305ec158d146101535780630f235fce1461017b578063146e5b24146101a3575b5f80fd5b348015610122575f80fd5b5061013d6004803603810190610138919061386f565b610482565b60405161014a91906138b4565b60405180910390f35b34801561015e575f80fd5b506101796004803603810190610174919061398d565b610563565b005b348015610186575f80fd5b506101a1600480360381019061019c9190613a2a565b610823565b005b3480156101ae575f80fd5b506101c960048036038101906101c49190613ab3565b610add565b005b3480156101d6575f80fd5b506101f160048036038101906101ec9190613bb1565b610cc3565b6040516101fe9190613c44565b60405180910390f35b348015610212575f80fd5b5061022d60048036038101906102289190613ab3565b611112565b005b34801561023a575f80fd5b5061025560048036038101906102509190613c5d565b611423565b6040516102659493929190613d53565b60405180910390f35b348015610279575f80fd5b50610294600480360381019061028f9190613ab3565b611485565b005b3480156102a1575f80fd5b506102bc60048036038101906102b79190613a2a565b6118e9565b005b6102d860048036038101906102d39190613dc0565b611ba4565b005b3480156102e5575f80fd5b5061030060048036038101906102fb919061398d565b611eda565b005b34801561030d575f80fd5b5061032860048036038101906103239190613eb2565b612199565b6040516103359190613c44565b60405180910390f35b348015610349575f80fd5b50610364600480360381019061035f9190613a2a565b6121d5565b005b348015610371575f80fd5b5061038c6004803603810190610387919061398d565b6124fe565b005b348015610399575f80fd5b506103b460048036038101906103af9190613ab3565b61282c565b005b3480156103c1575f80fd5b506103dc60048036038101906103d79190613f89565b612bdc565b005b3480156103e9575f80fd5b506103f2612f46565b6040516103ff919061405c565b60405180910390f35b348015610413575f80fd5b5061042e60048036038101906104299190613c5d565b612f6a565b60405161043d939291906140bb565b60405180910390f35b348015610451575f80fd5b5061046c600480360381019061046791906140f0565b612fb6565b6040516104799190613c44565b60405180910390f35b5f7f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061054c57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061055c575061055b8261344a565b5b9050919050565b6001600381111561057757610576613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff1660038111156105a9576105a8613ce0565b5b146105e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105e090614206565b60405180910390fd5b5f600387336002896040516020016106019190614244565b60405160208183030381529060405260405161061d91906142ca565b602060405180830381855afa158015610638573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061065b91906142f4565b888888886040516020016106759796959493929190614384565b60405160208183030381529060405260405161069191906142ca565b602060405180830381855afa1580156106ac573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610736576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161072d9061444e565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561076c5761076b613ce0565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd73886040516107a0919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016107eb94939291906144d6565b5f604051808303815f87803b158015610802575f80fd5b505af1158015610814573d5f803e3d5ffd5b50505050505050505050505050565b6001600381111561083757610836613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561086957610868613ce0565b5b146108a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108a090614206565b60405180910390fd5b5f60038633878787876040516020016108c79695949392919061452c565b6040516020818303038152906040526040516108e391906142ca565b602060405180830381855afa1580156108fe573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610988576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161097f9061444e565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156109f3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109ea9061460b565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610a2957610a28613ce0565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad1921907287604051610a5d919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401610aa693929190614629565b5f604051808303815f87803b158015610abd575f80fd5b505af1158015610acf573d5f803e3d5ffd5b505050505050505050505050565b60016004811115610af157610af0613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff166004811115610b2457610b23613ce0565b5b14610b64576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b5b90614206565b60405180910390fd5b5f600387878733888888604051602001610b84979695949392919061465e565b604051602081830303815290604052604051610ba091906142ca565b602060405180830381855afa158015610bbb573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610c46576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c3d9061444e565b60405180910390fd5b600260015f8a81526020019081526020015f205f01601c6101000a81548160ff02191690836004811115610c7d57610c7c613ce0565b5b02179055507f9c45e43e2ef051f70491ffd5221bf02ab37e1324128714ef9610df5f24fc9fb588604051610cb1919061447b565b60405180910390a15050505050505050565b5f808383810190610cd49190614807565b90505f6003811115610ce957610ce8613ce0565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff166003811115610d1e57610d1d613ce0565b5b14610d5e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d55906148a2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff1603610dd0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dc79061490a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603610e42576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e3990614972565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610eb4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eab90614a00565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610f22576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f1990614a68565b60405180910390fd5b610f2f81602001516134b3565b15610f6f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f6690614ad0565b60405180910390fd5b5f60038260200151888460600151856080015186604001518b604051602001610f9d9695949392919061452c565b604051602081830303815290604052604051610fb991906142ca565b602060405180830381855afa158015610fd4573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff1681526020016001600381111561102457611023613ce0565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff021916908360038111156110bb576110ba613ce0565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f01516040516110f5919061447b565b60405180910390a163150b7a0260e01b9250505095945050505050565b6001600481111561112657611125613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561115957611158613ce0565b5b14611199576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161119090614206565b60405180910390fd5b5f6003878787336002896040516020016111b39190614244565b6040516020818303038152906040526040516111cf91906142ca565b602060405180830381855afa1580156111ea573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061120d91906142f4565b8888604051602001611225979695949392919061465e565b60405160208183030381529060405260405161124191906142ca565b602060405180830381855afa15801561125c573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146112e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112de9061444e565b60405180910390fd5b600460015f8a81526020019081526020015f205f01601c6101000a81548160ff0219169083600481111561131e5761131d613ce0565b5b02179055507f45169a52eef651b20a81474b50b8a5d83225225fcd097ef3cf7952d9ab304f278885604051611354929190614aee565b60405180910390a15f86886113699190614b42565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036113e7573373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156113e1573d5f803e3d5ffd5b50611418565b5f83905061141633838373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b505050505050505050565b6001602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900463ffffffff1690805f01601c9054906101000a900460ff16905084565b6001600481111561149957611498613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff1660048111156114cc576114cb613ce0565b5b148061151c5750600260048111156114e7576114e6613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561151a57611519613ce0565b5b145b61155b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161155290614be5565b60405180910390fd5b5f60038787873388888860405160200161157b979695949392919061465e565b60405160208183030381529060405260405161159791906142ca565b602060405180830381855afa1580156115b2573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461163d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116349061444e565b60405180910390fd5b6002600481111561165157611650613ce0565b5b60015f8a81526020019081526020015f205f01601c9054906101000a900460ff16600481111561168457611683613ce0565b5b036116f65760015f8981526020019081526020015f205f0160189054906101000a900463ffffffff1663ffffffff164210156116f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116ec9061460b565b60405180910390fd5b5b6001600481111561170a57611709613ce0565b5b60015f8a81526020019081526020015f205f01601c9054906101000a900460ff16600481111561173d5761173c613ce0565b5b036117af5760015f8981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156117ae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016117a590614c73565b60405180910390fd5b5b600460015f8a81526020019081526020015f205f01601c6101000a81548160ff021916908360048111156117e6576117e5613ce0565b5b02179055507fbdd7a4be6d82798a500b59077706b12d3f45acf5504828919f92501307b2b9538860405161181a919061447b565b60405180910390a15f868861182f9190614b42565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036118ad573373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156118a7573d5f803e3d5ffd5b506118de565b5f8390506118dc33838373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b505050505050505050565b600160038111156118fd576118fc613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561192f5761192e613ce0565b5b1461196f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161196690614206565b60405180910390fd5b5f600386336002886040516020016119879190614244565b6040516020818303038152906040526040516119a391906142ca565b602060405180830381855afa1580156119be573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906119e191906142f4565b8787876040516020016119f99695949392919061452c565b604051602081830303815290604052604051611a1591906142ca565b602060405180830381855afa158015611a30573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614611aba576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ab19061444e565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115611af057611aef613ce0565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd7387604051611b24919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401611b6d93929190614629565b5f604051808303815f87803b158015611b84575f80fd5b505af1158015611b96573d5f803e3d5ffd5b505050505050505050505050565b5f6004811115611bb757611bb6613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff166004811115611bea57611be9613ce0565b5b14611c2a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2190614d01565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603611c98576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8f90614d8f565b60405180910390fd5b5f3411611cda576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611cd190614e1d565b60405180910390fd5b853411611d1c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d1390614eab565b60405180910390fd5b5f60038734611d2b9190614ec9565b88883389895f604051602001611d47979695949392919061465e565b604051602081830303815290604052604051611d6391906142ca565b602060405180830381855afa158015611d7e573d5f803e3d5ffd5b5050506040515160601b90506040518060800160405280826bffffffffffffffffffffffff191681526020018463ffffffff1681526020018363ffffffff16815260200160016004811115611dd657611dd5613ce0565b5b81525060015f8a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548163ffffffff021916908363ffffffff1602179055506060820151815f01601c6101000a81548160ff02191690836004811115611e9157611e90613ce0565b5b02179055509050507ffc6cdccd1d98ded12074a9ebc7f6ab74fed1814ff57f4fb5202464d8938bd93588604051611ec8919061447b565b60405180910390a15050505050505050565b60016003811115611eee57611eed613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115611f2057611f1f613ce0565b5b14611f60576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f5790614206565b60405180910390fd5b5f600387338888888888604051602001611f809796959493929190614384565b604051602081830303815290604052604051611f9c91906142ca565b602060405180830381855afa158015611fb7573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612041576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120389061444e565b60405180910390fd5b5f808981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156120ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120a39061460b565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff021916908360038111156120e2576120e1613ce0565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad1921907288604051612116919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b815260040161216194939291906144d6565b5f604051808303815f87803b158015612178575f80fd5b505af115801561218a573d5f803e3d5ffd5b50505050505050505050505050565b5f6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016121cc90614f46565b60405180910390fd5b600160038111156121e9576121e8613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561221b5761221a613ce0565b5b1461225b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161225290614206565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146122c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122c090614fae565b60405180910390fd5b5f60033387876002886040516020016122e29190614244565b6040516020818303038152906040526040516122fe91906142ca565b602060405180830381855afa158015612319573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061233c91906142f4565b87876040516020016123539695949392919061452c565b60405160208183030381529060405260405161236f91906142ca565b602060405180830381855afa15801561238a573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612414576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161240b9061444e565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561244a57612449613ce0565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161247e919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b81526004016124c793929190614629565b5f604051808303815f87803b1580156124de575f80fd5b505af11580156124f0573d5f803e3d5ffd5b505050505050505050505050565b6001600381111561251257612511613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff16600381111561254457612543613ce0565b5b14612584576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161257b90614206565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146125f2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016125e990614fae565b60405180910390fd5b5f600333888860028960405160200161260b9190614244565b60405160208183030381529060405260405161262791906142ca565b602060405180830381855afa158015612642573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061266591906142f4565b88888860405160200161267e9796959493929190614384565b60405160208183030381529060405260405161269a91906142ca565b602060405180830381855afa1580156126b5573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461273f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016127369061444e565b60405180910390fd5b60025f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561277557612774613ce0565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf0886040516127a9919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016127f494939291906144d6565b5f604051808303815f87803b15801561280b575f80fd5b505af115801561281d573d5f803e3d5ffd5b50505050505050505050505050565b600260048111156128405761283f613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561287357612872613ce0565b5b146128b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016128aa9061503c565b60405180910390fd5b5f600387873388886002896040516020016128ce9190614244565b6040516020818303038152906040526040516128ea91906142ca565b602060405180830381855afa158015612905573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061292891906142f4565b8860405160200161293f979695949392919061465e565b60405160208183030381529060405260405161295b91906142ca565b602060405180830381855afa158015612976573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612a01576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016129f89061444e565b60405180910390fd5b600360015f8a81526020019081526020015f205f01601c6101000a81548160ff02191690836004811115612a3857612a37613ce0565b5b02179055507f0d0da0df275f85bed3a5fe7ae79f3559341a3f9ccd8e010133438135bda00a878884604051612a6e929190614aee565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603612b56573373ffffffffffffffffffffffffffffffffffffffff166108fc8890811502906040515f60405180830381858888f19350505050158015612aec573d5f803e3d5ffd5b507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f19350505050158015612b50573d5f803e3d5ffd5b50612bd2565b5f829050612b8533898373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b612bd07f0000000000000000000000000000000000000000000000000000000000000000888373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b5050505050505050565b5f6004811115612bef57612bee613ce0565b5b60015f8b81526020019081526020015f205f01601c9054906101000a900460ff166004811115612c2257612c21613ce0565b5b14612c62576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612c59906150ca565b60405180910390fd5b5f8811612ca4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612c9b90615132565b60405180910390fd5b5f8711612ce6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612cdd9061519a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603612d54576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612d4b90614d8f565b60405180910390fd5b5f60038989883389898d604051602001612d74979695949392919061465e565b604051602081830303815290604052604051612d9091906142ca565b602060405180830381855afa158015612dab573d5f803e3d5ffd5b5050506040515160601b90506040518060800160405280826bffffffffffffffffffffffff191681526020018463ffffffff1681526020018363ffffffff16815260200160016004811115612e0357612e02613ce0565b5b81525060015f8c81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548163ffffffff021916908363ffffffff1602179055506060820151815f01601c6101000a81548160ff02191690836004811115612ebe57612ebd613ce0565b5b02179055509050507ffc6cdccd1d98ded12074a9ebc7f6ab74fed1814ff57f4fb5202464d8938bd9358a604051612ef5919061447b565b60405180910390a15f879050612f3933308b8d612f129190614b42565b8473ffffffffffffffffffffffffffffffffffffffff16613543909392919063ffffffff16565b5050505050505050505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b5f808383810190612fc79190614807565b90505f6003811115612fdc57612fdb613ce0565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff16600381111561301157613010613ce0565b5b14613051576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161304890615228565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff16036130c3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016130ba9061490a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603613135576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161312c90614972565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146131a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161319e90614a00565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614613215576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161320c90614a68565b60405180910390fd5b5f8511613257576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161324e90615290565b60405180910390fd5b61326481602001516134b3565b156132a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161329b90614ad0565b60405180910390fd5b5f60038260200151898460600151856080015186604001518c8c6040516020016132d49796959493929190614384565b6040516020818303038152906040526040516132f091906142ca565b602060405180830381855afa15801561330b573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff1681526020016001600381111561335b5761335a613ce0565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff021916908360038111156133f2576133f1613ce0565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f015160405161342c919061447b565b60405180910390a163f23a6e6160e01b925050509695505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f80823b90505f8111915050919050565b61353e838473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85856040516024016134f79291906152ae565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506135c5565b505050565b6135bf848573ffffffffffffffffffffffffffffffffffffffff166323b872dd86868660405160240161357893929190614629565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506135c5565b50505050565b5f6135ef828473ffffffffffffffffffffffffffffffffffffffff1661365a90919063ffffffff16565b90505f81511415801561361357508080602001905181019061361191906152ff565b155b1561365557826040517f5274afe700000000000000000000000000000000000000000000000000000000815260040161364c919061405c565b60405180910390fd5b505050565b606061366783835f61366f565b905092915050565b6060814710156136b657306040517fcd7860590000000000000000000000000000000000000000000000000000000081526004016136ad919061405c565b60405180910390fd5b5f808573ffffffffffffffffffffffffffffffffffffffff1684866040516136de91906142ca565b5f6040518083038185875af1925050503d805f8114613718576040519150601f19603f3d011682016040523d82523d5f602084013e61371d565b606091505b509150915061372d868383613738565b925050509392505050565b60608261374d57613748826137c5565b6137bd565b5f825114801561377357505f8473ffffffffffffffffffffffffffffffffffffffff163b145b156137b557836040517f9996b3150000000000000000000000000000000000000000000000000000000081526004016137ac919061405c565b60405180910390fd5b8190506137be565b5b9392505050565b5f815111156137d75780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61384e8161381a565b8114613858575f80fd5b50565b5f8135905061386981613845565b92915050565b5f6020828403121561388457613883613812565b5b5f6138918482850161385b565b91505092915050565b5f8115159050919050565b6138ae8161389a565b82525050565b5f6020820190506138c75f8301846138a5565b92915050565b5f819050919050565b6138df816138cd565b81146138e9575f80fd5b50565b5f813590506138fa816138d6565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61392982613900565b9050919050565b6139398161391f565b8114613943575f80fd5b50565b5f8135905061395481613930565b92915050565b5f819050919050565b61396c8161395a565b8114613976575f80fd5b50565b5f8135905061398781613963565b92915050565b5f805f805f805f60e0888a0312156139a8576139a7613812565b5b5f6139b58a828b016138ec565b97505060206139c68a828b01613946565b96505060406139d78a828b016138ec565b95505060606139e88a828b016138ec565b94505060806139f98a828b01613946565b93505060a0613a0a8a828b01613979565b92505060c0613a1b8a828b01613979565b91505092959891949750929550565b5f805f805f8060c08789031215613a4457613a43613812565b5b5f613a5189828a016138ec565b9650506020613a6289828a01613946565b9550506040613a7389828a016138ec565b9450506060613a8489828a016138ec565b9350506080613a9589828a01613946565b92505060a0613aa689828a01613979565b9150509295509295509295565b5f805f805f805f60e0888a031215613ace57613acd613812565b5b5f613adb8a828b016138ec565b9750506020613aec8a828b01613979565b9650506040613afd8a828b01613979565b9550506060613b0e8a828b01613946565b9450506080613b1f8a828b016138ec565b93505060a0613b308a828b016138ec565b92505060c0613b418a828b01613946565b91505092959891949750929550565b5f80fd5b5f80fd5b5f80fd5b5f8083601f840112613b7157613b70613b50565b5b8235905067ffffffffffffffff811115613b8e57613b8d613b54565b5b602083019150836001820283011115613baa57613ba9613b58565b5b9250929050565b5f805f805f60808688031215613bca57613bc9613812565b5b5f613bd788828901613946565b9550506020613be888828901613946565b9450506040613bf988828901613979565b935050606086013567ffffffffffffffff811115613c1a57613c19613816565b5b613c2688828901613b5c565b92509250509295509295909350565b613c3e8161381a565b82525050565b5f602082019050613c575f830184613c35565b92915050565b5f60208284031215613c7257613c71613812565b5b5f613c7f848285016138ec565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b613cbc81613c88565b82525050565b5f63ffffffff82169050919050565b613cda81613cc2565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60058110613d1e57613d1d613ce0565b5b50565b5f819050613d2e82613d0d565b919050565b5f613d3d82613d21565b9050919050565b613d4d81613d33565b82525050565b5f608082019050613d665f830187613cb3565b613d736020830186613cd1565b613d806040830185613cd1565b613d8d6060830184613d44565b95945050505050565b613d9f81613cc2565b8114613da9575f80fd5b50565b5f81359050613dba81613d96565b92915050565b5f805f805f805f60e0888a031215613ddb57613dda613812565b5b5f613de88a828b016138ec565b9750506020613df98a828b01613979565b9650506040613e0a8a828b01613946565b9550506060613e1b8a828b016138ec565b9450506080613e2c8a828b016138ec565b93505060a0613e3d8a828b01613dac565b92505060c0613e4e8a828b01613dac565b91505092959891949750929550565b5f8083601f840112613e7257613e71613b50565b5b8235905067ffffffffffffffff811115613e8f57613e8e613b54565b5b602083019150836020820283011115613eab57613eaa613b58565b5b9250929050565b5f805f805f805f8060a0898b031215613ece57613ecd613812565b5b5f613edb8b828c01613946565b9850506020613eec8b828c01613946565b975050604089013567ffffffffffffffff811115613f0d57613f0c613816565b5b613f198b828c01613e5d565b9650965050606089013567ffffffffffffffff811115613f3c57613f3b613816565b5b613f488b828c01613e5d565b9450945050608089013567ffffffffffffffff811115613f6b57613f6a613816565b5b613f778b828c01613b5c565b92509250509295985092959890939650565b5f805f805f805f805f6101208a8c031215613fa757613fa6613812565b5b5f613fb48c828d016138ec565b9950506020613fc58c828d01613979565b9850506040613fd68c828d01613979565b9750506060613fe78c828d01613946565b9650506080613ff88c828d01613946565b95505060a06140098c828d016138ec565b94505060c061401a8c828d016138ec565b93505060e061402b8c828d01613dac565b92505061010061403d8c828d01613dac565b9150509295985092959850929598565b6140568161391f565b82525050565b5f60208201905061406f5f83018461404d565b92915050565b6004811061408657614085613ce0565b5b50565b5f81905061409682614075565b919050565b5f6140a582614089565b9050919050565b6140b58161409b565b82525050565b5f6060820190506140ce5f830186613cb3565b6140db6020830185613cd1565b6140e860408301846140ac565b949350505050565b5f805f805f8060a0878903121561410a57614109613812565b5b5f61411789828a01613946565b965050602061412889828a01613946565b955050604061413989828a01613979565b945050606061414a89828a01613979565b935050608087013567ffffffffffffffff81111561416b5761416a613816565b5b61417789828a01613b5c565b92509250509295509295509295565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f6141f0602a83614186565b91506141fb82614196565b604082019050919050565b5f6020820190508181035f83015261421d816141e4565b9050919050565b5f819050919050565b61423e614239826138cd565b614224565b82525050565b5f61424f828461422d565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b5f5b8381101561428f578082015181840152602081019050614274565b5f8484015250505050565b5f6142a48261425e565b6142ae8185614268565b93506142be818560208601614272565b80840191505092915050565b5f6142d5828461429a565b915081905092915050565b5f815190506142ee816138d6565b92915050565b5f6020828403121561430957614308613812565b5b5f614316848285016142e0565b91505092915050565b5f8160601b9050919050565b5f6143358261431f565b9050919050565b5f6143468261432b565b9050919050565b61435e6143598261391f565b61433c565b82525050565b5f819050919050565b61437e6143798261395a565b614364565b82525050565b5f61438f828a61434d565b60148201915061439f828961434d565b6014820191506143af828861422d565b6020820191506143bf828761422d565b6020820191506143cf828661434d565b6014820191506143df828561436d565b6020820191506143ef828461436d565b60208201915081905098975050505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f614438601383614186565b915061444382614404565b602082019050919050565b5f6020820190508181035f8301526144658161442c565b9050919050565b614475816138cd565b82525050565b5f60208201905061448e5f83018461446c565b92915050565b61449d8161395a565b82525050565b5f82825260208201905092915050565b50565b5f6144c15f836144a3565b91506144cc826144b3565b5f82019050919050565b5f60a0820190506144e95f83018761404d565b6144f6602083018661404d565b6145036040830185614494565b6145106060830184614494565b8181036080830152614521816144b6565b905095945050505050565b5f614537828961434d565b601482019150614547828861434d565b601482019150614557828761422d565b602082019150614567828661422d565b602082019150614577828561434d565b601482019150614587828461436d565b602082019150819050979650505050505050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f6145f5603883614186565b91506146008261459b565b604082019050919050565b5f6020820190508181035f830152614622816145e9565b9050919050565b5f60608201905061463c5f83018661404d565b614649602083018561404d565b6146566040830184614494565b949350505050565b5f614669828a61436d565b602082019150614679828961436d565b602082019150614689828861434d565b601482019150614699828761434d565b6014820191506146a9828661422d565b6020820191506146b9828561422d565b6020820191506146c9828461434d565b60148201915081905098975050505050505050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b614728826146e2565b810181811067ffffffffffffffff82111715614747576147466146f2565b5b80604052505050565b5f614759613809565b9050614765828261471f565b919050565b5f60c0828403121561477f5761477e6146de565b5b61478960c0614750565b90505f614798848285016138ec565b5f8301525060206147ab84828501613946565b60208301525060406147bf84828501613946565b60408301525060606147d3848285016138ec565b60608301525060806147e7848285016138ec565b60808301525060a06147fb84828501613dac565b60a08301525092915050565b5f60c0828403121561481c5761481b613812565b5b5f6148298482850161476a565b91505092915050565b7f4d616b657220455243373231207061796d656e74206d75737420626520556e695f8201527f6e697469616c697a656400000000000000000000000000000000000000000000602082015250565b5f61488c602a83614186565b915061489782614832565b604082019050919050565b5f6020820190508181035f8301526148b981614880565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f6148f4601e83614186565b91506148ff826148c0565b602082019050919050565b5f6020820190508181035f830152614921816148e8565b9050919050565b7f546f6b656e206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f61495c601e83614186565b915061496782614928565b602082019050919050565b5f6020820190508181035f83015261498981614950565b9050919050565b7f546f6b656e206164647265737320646f6573206e6f74206d617463682073656e5f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f6149ea602383614186565b91506149f582614990565b604082019050919050565b5f6020820190508181035f830152614a17816149de565b9050919050565b7f4f70657261746f72206d757374206265207468652073656e64657200000000005f82015250565b5f614a52601b83614186565b9150614a5d82614a1e565b602082019050919050565b5f6020820190508181035f830152614a7f81614a46565b9050919050565b7f54616b65722063616e6e6f74206265206120636f6e74726163740000000000005f82015250565b5f614aba601a83614186565b9150614ac582614a86565b602082019050919050565b5f6020820190508181035f830152614ae781614aae565b9050919050565b5f604082019050614b015f83018561446c565b614b0e602083018461446c565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f614b4c8261395a565b9150614b578361395a565b9250828201905080821115614b6f57614b6e614b15565b5b92915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e74206f722054616b6572417070726f7665640000000000602082015250565b5f614bcf603b83614186565b9150614bda82614b75565b604082019050919050565b5f6020820190508181035f830152614bfc81614bc3565b9050919050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e74207072652d617070726f7665206c6f636b2074696d65000000602082015250565b5f614c5d603d83614186565b9150614c6882614c03565b604082019050919050565b5f6020820190508181035f830152614c8a81614c51565b9050919050565b7f54616b6572207061796d656e7420697320616c726561647920696e697469616c5f8201527f697a656400000000000000000000000000000000000000000000000000000000602082015250565b5f614ceb602483614186565b9150614cf682614c91565b604082019050919050565b5f6020820190508181035f830152614d1881614cdf565b9050919050565b7f5265636569766572206d757374206e6f74206265207a65726f206164647265735f8201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b5f614d79602183614186565b9150614d8482614d1f565b604082019050919050565b5f6020820190508181035f830152614da681614d6d565b9050919050565b7f4554482076616c7565206d7573742062652067726561746572207468616e207a5f8201527f65726f0000000000000000000000000000000000000000000000000000000000602082015250565b5f614e07602383614186565b9150614e1282614dad565b604082019050919050565b5f6020820190508181035f830152614e3481614dfb565b9050919050565b7f4554482076616c7565206d7573742062652067726561746572207468616e20645f8201527f6578206665650000000000000000000000000000000000000000000000000000602082015250565b5f614e95602683614186565b9150614ea082614e3b565b604082019050919050565b5f6020820190508181035f830152614ec281614e89565b9050919050565b5f614ed38261395a565b9150614ede8361395a565b9250828203905081811115614ef657614ef5614b15565b5b92915050565b7f4261746368207472616e7366657273206e6f7420737570706f727465640000005f82015250565b5f614f30601d83614186565b9150614f3b82614efc565b602082019050919050565b5f6020820190508181035f830152614f5d81614f24565b9050919050565b7f43616c6c6572206d75737420626520616e20454f4100000000000000000000005f82015250565b5f614f98601583614186565b9150614fa382614f64565b602082019050919050565b5f6020820190508181035f830152614fc581614f8c565b9050919050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520545f8201527f616b6572417070726f7665640000000000000000000000000000000000000000602082015250565b5f615026602c83614186565b915061503182614fcc565b604082019050919050565b5f6020820190508181035f8301526150538161501a565b9050919050565b7f4552433230207632207061796d656e7420697320616c726561647920696e69745f8201527f69616c697a656400000000000000000000000000000000000000000000000000602082015250565b5f6150b4602783614186565b91506150bf8261505a565b604082019050919050565b5f6020820190508181035f8301526150e1816150a8565b9050919050565b7f416d6f756e74206d757374206e6f74206265207a65726f0000000000000000005f82015250565b5f61511c601783614186565b9150615127826150e8565b602082019050919050565b5f6020820190508181035f83015261514981615110565b9050919050565b7f44657820666565206d757374206e6f74206265207a65726f00000000000000005f82015250565b5f615184601883614186565b915061518f82615150565b602082019050919050565b5f6020820190508181035f8301526151b181615178565b9050919050565b7f4d616b65722045524331313535207061796d656e74206d75737420626520556e5f8201527f696e697469616c697a6564000000000000000000000000000000000000000000602082015250565b5f615212602b83614186565b915061521d826151b8565b604082019050919050565b5f6020820190508181035f83015261523f81615206565b9050919050565b7f56616c7565206d7573742062652067726561746572207468616e2030000000005f82015250565b5f61527a601c83614186565b915061528582615246565b602082019050919050565b5f6020820190508181035f8301526152a78161526e565b9050919050565b5f6040820190506152c15f83018561404d565b6152ce6020830184614494565b9392505050565b6152de8161389a565b81146152e8575f80fd5b50565b5f815190506152f9816152d5565b92915050565b5f6020828403121561531457615313613812565b5b5f615321848285016152eb565b9150509291505056fea26469706673582212200d86b0f6898fb823c55626c3b02a7098bc8622606b092e0f458df6c86ce2967864736f6c63430008180033"; pub trait CoinDockerOps { fn rpc_client(&self) -> &UtxoRpcClientEnum; @@ -1028,8 +1043,19 @@ pub fn withdraw_max_and_send_v1(mm: &MarketMakerIt, coin: &str, to: &str) -> Tra tx_details } +async fn get_current_gas_limit(web3: &Web3) { + match web3.eth().block(BlockId::Number(BlockNumber::Latest)).await { + Ok(Some(block)) => { + log!("Current gas limit: {}", block.gas_limit); + }, + Ok(None) => log!("Latest block information is not available."), + Err(e) => log!("Failed to fetch the latest block: {}", e), + } +} + pub fn init_geth_node() { unsafe { + block_on(get_current_gas_limit(&GETH_WEB3)); let accounts = block_on(GETH_WEB3.eth().accounts()).unwrap(); GETH_ACCOUNT = accounts[0]; log!("GETH ACCOUNT {:?}", GETH_ACCOUNT); @@ -1146,6 +1172,124 @@ pub fn init_geth_node() { thread::sleep(Duration::from_millis(100)); } + let name = Token::String("MyNFT".into()); + let symbol = Token::String("MNFT".into()); + let params = ethabi::encode(&[name, symbol]); + let erc721_data = format!("{}{}", ERC721_TEST_TOKEN_BYTES, hex::encode(params)); + + let tx_request_deploy_erc721 = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(erc721_data).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_erc721_tx_hash = block_on(GETH_WEB3.eth().send_transaction(tx_request_deploy_erc721)).unwrap(); + log!("Sent ERC721 deploy transaction {:?}", deploy_erc721_tx_hash); + + loop { + let deploy_erc721_tx_receipt = match block_on(GETH_WEB3.eth().transaction_receipt(deploy_erc721_tx_hash)) { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_erc721_tx_receipt { + GETH_ERC721_CONTRACT = receipt.contract_address.unwrap(); + log!("GETH_ERC721_CONTRACT {:?}", GETH_ERC721_CONTRACT); + break; + } + thread::sleep(Duration::from_millis(100)); + } + + let uri = Token::String("MyNFTUri".into()); + let params = ethabi::encode(&[uri]); + let erc1155_data = format!("{}{}", ERC1155_TEST_TOKEN_BYTES, hex::encode(params)); + + let tx_request_deploy_erc1155 = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(erc1155_data).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_erc1155_tx_hash = block_on(GETH_WEB3.eth().send_transaction(tx_request_deploy_erc1155)).unwrap(); + log!("Sent ERC1155 deploy transaction {:?}", deploy_erc721_tx_hash); + + loop { + let deploy_erc1155_tx_receipt = match block_on(GETH_WEB3.eth().transaction_receipt(deploy_erc1155_tx_hash)) + { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_erc1155_tx_receipt { + GETH_ERC1155_CONTRACT = receipt.contract_address.unwrap(); + log!("GETH_ERC1155_CONTRACT {:?}", GETH_ERC1155_CONTRACT); + break; + } + thread::sleep(Duration::from_millis(100)); + } + + let dex_fee_address = Token::Address(geth_account()); + let params = ethabi::encode(&[dex_fee_address]); + let nft_swap_data = format!("{}{}", NFT_SWAP_CONTRACT_BYTES, hex::encode(params)); + + let tx_request_deploy_nft_swap_contract = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(nft_swap_data).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_nft_swap_tx_hash = + block_on(GETH_WEB3.eth().send_transaction(tx_request_deploy_nft_swap_contract)).unwrap(); + log!("Sent deploy nft swap contract transaction {:?}", deploy_swap_tx_hash); + + loop { + let deploy_nft_swap_tx_receipt = + match block_on(GETH_WEB3.eth().transaction_receipt(deploy_nft_swap_tx_hash)) { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_nft_swap_tx_receipt { + GETH_NFT_SWAP_CONTRACT = receipt.contract_address.unwrap(); + log!("GETH_NFT_SWAP_CONTRACT {:?}", GETH_SWAP_CONTRACT); + break; + } + thread::sleep(Duration::from_millis(100)); + } + let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); let alice_keypair = key_pair_from_seed(&alice_passphrase).unwrap(); let alice_eth_addr = addr_from_raw_pubkey(alice_keypair.public()).unwrap(); diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index 91789e8433..a51db2cd81 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -1,15 +1,16 @@ -use crate::docker_tests::docker_tests_common::{random_secp256k1_secret, GETH_ACCOUNT, GETH_ERC20_CONTRACT, - GETH_NONCE_LOCK, GETH_SWAP_CONTRACT, GETH_WATCHERS_SWAP_CONTRACT, - GETH_WEB3, MM_CTX}; -use bitcrypto::dhash160; -use coins::eth::{checksum_address, eth_coin_from_conf_and_request, EthCoin, ERC20_ABI}; -use coins::{CoinProtocol, ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, PrivKeyBuildPolicy, - RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, - SwapTxTypeWithSecretHash}; +use super::docker_tests_common::{random_secp256k1_secret, ERC1155_TEST_ABI, ERC721_TEST_ABI, GETH_ACCOUNT, + GETH_ERC1155_CONTRACT, GETH_ERC20_CONTRACT, GETH_ERC721_CONTRACT, + GETH_NFT_SWAP_CONTRACT, GETH_NONCE_LOCK, GETH_SWAP_CONTRACT, + GETH_WATCHERS_SWAP_CONTRACT, GETH_WEB3, MM_CTX}; +use bitcrypto::{dhash160, sha256}; +use coins::eth::{checksum_address, eth_addr_to_hex, eth_coin_from_conf_and_request, EthCoin, ERC20_ABI}; +use coins::nft::nft_structs::{Chain, ContractType, NftInfo}; +use coins::{CoinProtocol, ConfirmPaymentInput, FoundSwapTxSpend, MakerNftSwapOpsV2, MarketCoinOps, MmCoin, NftSwapInfo, ParseCoinAssocTypes, PrivKeyBuildPolicy, RefundPaymentArgs, SearchForSwapTxSpendInput, SendNftMakerPaymentArgs, SendPaymentArgs, SpendNftMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, ToBytes, Transaction, ValidateNftMakerPaymentArgs}; use common::{block_on, now_sec}; use ethereum_types::U256; use futures01::Future; -use mm2_test_helpers::for_tests::{erc20_dev_conf, eth_dev_conf}; +use mm2_number::{BigDecimal, BigUint}; +use mm2_test_helpers::for_tests::{erc20_dev_conf, eth_dev_conf, nft_dev_conf}; use std::thread; use std::time::Duration; use web3::contract::{Contract, Options}; @@ -19,13 +20,18 @@ use web3::types::{Address, TransactionRequest, H256}; /// # Safety /// /// GETH_ACCOUNT is set once during initialization before tests start -fn geth_account() -> Address { unsafe { GETH_ACCOUNT } } +pub fn geth_account() -> Address { unsafe { GETH_ACCOUNT } } /// # Safety /// /// GETH_SWAP_CONTRACT is set once during initialization before tests start pub fn swap_contract() -> Address { unsafe { GETH_SWAP_CONTRACT } } +/// # Safety +/// +/// GETH_NFT_SWAP_CONTRACT is set once during initialization before tests start +pub fn nft_swap_contract() -> Address { unsafe { GETH_NFT_SWAP_CONTRACT } } + /// # Safety /// /// GETH_WATCHERS_SWAP_CONTRACT is set once during initialization before tests start @@ -39,6 +45,16 @@ pub fn erc20_contract() -> Address { unsafe { GETH_ERC20_CONTRACT } } /// Return ERC20 dev token contract address in checksum format pub fn erc20_contract_checksum() -> String { checksum_address(&format!("{:02x}", erc20_contract())) } +/// # Safety +/// +/// GETH_ERC721_CONTRACT is set once during initialization before tests start +pub fn erc721_contract() -> Address { unsafe { GETH_ERC721_CONTRACT } } + +/// # Safety +/// +/// GETH_ERC1155_CONTRACT is set once during initialization before tests start +pub fn erc1155_contract() -> Address { unsafe { GETH_ERC1155_CONTRACT } } + fn wait_for_confirmation(tx_hash: H256) { thread::sleep(Duration::from_millis(2000)); loop { @@ -88,14 +104,130 @@ fn fill_erc20(to_addr: Address, amount: U256) { wait_for_confirmation(tx_hash); } +pub(crate) fn mint_erc721(to_addr: Address, token_id: U256) { + let _guard = GETH_NONCE_LOCK.lock().unwrap(); + let erc721_contract = Contract::from_json(GETH_WEB3.eth(), erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); + + let options = Options { + gas: Some(U256::from(150_000)), + ..Options::default() + }; + + let tx_hash = block_on(erc721_contract.call( + "mint", + (Token::Address(to_addr), Token::Uint(token_id)), + geth_account(), + options, + )) + .unwrap(); + wait_for_confirmation(tx_hash); + + let owner: Address = + block_on(erc721_contract.query("ownerOf", Token::Uint(token_id), None, Options::default(), None)).unwrap(); + + assert_eq!( + owner, to_addr, + "The ownership of the tokenID {:?} does not match the expected address {:?}.", + token_id, to_addr + ); +} + +fn erc712_owner(token_id: U256) -> Address { + let _guard = GETH_NONCE_LOCK.lock().unwrap(); + let erc721_contract = Contract::from_json(GETH_WEB3.eth(), erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); + block_on(erc721_contract.query("ownerOf", Token::Uint(token_id), None, Options::default(), None)).unwrap() +} + +pub(crate) fn mint_erc1155(to_addr: Address, token_id: U256, amount: U256) { + let _guard = GETH_NONCE_LOCK.lock().unwrap(); + let erc1155_contract = + Contract::from_json(GETH_WEB3.eth(), erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); + + let tx_hash = block_on(erc1155_contract.call( + "mint", + ( + Token::Address(to_addr), + Token::Uint(token_id), + Token::Uint(amount), + Token::Bytes("".into()), + ), + geth_account(), + Options::default(), + )) + .unwrap(); + wait_for_confirmation(tx_hash); + + // Check the balance of the token for the to_addr + let balance: U256 = block_on(erc1155_contract.query( + "balanceOf", + (Token::Address(to_addr), Token::Uint(token_id)), + None, + Options::default(), + None, + )) + .unwrap(); + + assert_eq!( + balance, amount, + "The balance of tokenId {:?} for address {:?} does not match the expected amount {:?}.", + token_id, to_addr, amount + ); +} + +fn erc1155_balance(wallet_addr: Address, token_id: U256) -> U256 { + let _guard = GETH_NONCE_LOCK.lock().unwrap(); + let erc1155_contract = + Contract::from_json(GETH_WEB3.eth(), erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); + block_on(erc1155_contract.query( + "balanceOf", + (Token::Address(wallet_addr), Token::Uint(token_id)), + None, + Options::default(), + None, + )) + .unwrap() +} + +pub(crate) async fn fill_erc1155_info(eth_coin: &EthCoin, tokens_id: u32, amount: u32) { + let nft_infos_lock = eth_coin.nfts_infos.clone(); + let mut nft_infos = nft_infos_lock.lock().await; + + let erc1155_nft_info = NftInfo { + token_address: erc1155_contract(), + token_id: BigUint::from(tokens_id), + chain: Chain::Eth, + contract_type: ContractType::Erc1155, + amount: BigDecimal::from(amount), + }; + let erc1155_address_str = eth_addr_to_hex(&erc1155_contract()); + let erc1155_key = format!("{},{}", erc1155_address_str, tokens_id); + nft_infos.insert(erc1155_key, erc1155_nft_info); +} + +pub(crate) async fn fill_erc721_info(eth_coin: &EthCoin, tokens_id: u32) { + let nft_infos_lock = eth_coin.nfts_infos.clone(); + let mut nft_infos = nft_infos_lock.lock().await; + + let erc721_nft_info = NftInfo { + token_address: erc721_contract(), + token_id: BigUint::from(tokens_id), + chain: Chain::Eth, + contract_type: ContractType::Erc721, + amount: BigDecimal::from(1), + }; + let erc721_address_str = eth_addr_to_hex(&erc721_contract()); + let erc721_key = format!("{},{}", erc721_address_str, tokens_id); + nft_infos.insert(erc721_key, erc721_nft_info); +} + /// Creates ETH protocol coin supplied with 100 ETH -pub fn eth_coin_with_random_privkey(swap_contract: Address) -> EthCoin { +pub fn eth_coin_with_random_privkey(swap_contract_address: Address) -> EthCoin { let eth_conf = eth_dev_conf(); let req = json!({ "method": "enable", "coin": "ETH", "urls": ["http://127.0.0.1:8545"], - "swap_contract_address": swap_contract, + "swap_contract_address": swap_contract_address, }); let secret = random_secp256k1_secret(); @@ -116,13 +248,13 @@ pub fn eth_coin_with_random_privkey(swap_contract: Address) -> EthCoin { } /// Creates ERC20 protocol coin supplied with 1 ETH and 100 token -pub fn erc20_coin_with_random_privkey(swap_contract: Address) -> EthCoin { +pub fn erc20_coin_with_random_privkey(swap_contract_address: Address) -> EthCoin { let erc20_conf = erc20_dev_conf(&erc20_contract_checksum()); let req = json!({ "method": "enable", "coin": "ERC20DEV", "urls": ["http://127.0.0.1:8545"], - "swap_contract_address": swap_contract, + "swap_contract_address": swap_contract_address, }); let erc20_coin = block_on(eth_coin_from_conf_and_request( @@ -146,6 +278,53 @@ pub fn erc20_coin_with_random_privkey(swap_contract: Address) -> EthCoin { erc20_coin } +pub enum TestNftType { + Erc1155 { token_id: u32, amount: u32 }, + Erc721 { token_id: u32 }, +} + +/// Generates a global NFT coin instance with a random private key and an initial 100 ETH balance. +/// Optionally mints a specified NFT (either ERC721 or ERC1155) to the global NFT address, +/// with details recorded in the `nfts_infos` field based on the provided `nft_type`. +pub fn global_nft_with_random_privkey(swap_contract_address: Address, nft_type: Option) -> EthCoin { + let nft_conf = nft_dev_conf(); + let req = json!({ + "method": "enable", + "coin": "NFT_ETH", + "urls": ["http://127.0.0.1:8545"], + "swap_contract_address": swap_contract_address, + }); + + let global_nft = block_on(eth_coin_from_conf_and_request( + &MM_CTX, + "NFT_ETH", + &nft_conf, + &req, + CoinProtocol::NFT { + platform: "ETH".to_string(), + }, + PrivKeyBuildPolicy::IguanaPrivKey(random_secp256k1_secret()), + )) + .unwrap(); + + fill_eth(global_nft.my_address, U256::from(10).pow(U256::from(20))); + + if let Some(nft_type) = nft_type { + match nft_type { + TestNftType::Erc1155 { token_id, amount } => { + mint_erc1155(global_nft.my_address, U256::from(token_id), U256::from(amount)); + block_on(fill_erc1155_info(&global_nft, token_id, amount)); + }, + TestNftType::Erc721 { token_id } => { + mint_erc721(global_nft.my_address, U256::from(token_id)); + block_on(fill_erc721_info(&global_nft, token_id)); + }, + } + } + + global_nft +} + fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { let eth_coin = eth_coin_with_random_privkey(swap_contract()); eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy); @@ -490,3 +669,170 @@ fn send_and_spend_erc20_maker_payment_internal_gas_policy() { fn send_and_spend_erc20_maker_payment_priority_fee() { send_and_spend_erc20_maker_payment_impl(SwapTxFeePolicy::Medium); } + +#[test] +fn send_and_spend_erc721_maker_payment() { + // TODO: Evaluate implementation strategy — either employing separate contracts for maker and taker + // functionalities for both coins and NFTs, or utilizing the Diamond Standard (EIP-2535) for a unified contract approach. + // Decision will inform whether to maintain multiple "swap_contract_address" fields in `EthCoin` for distinct contract types + // or a singular field for a Diamond Standard-compatible contract address. + + let erc721_nft = TestNftType::Erc721 { token_id: 2 }; + + let maker_global_nft = global_nft_with_random_privkey(nft_swap_contract(), Some(erc721_nft)); + let taker_global_nft = global_nft_with_random_privkey(nft_swap_contract(), None); + + let time_lock = now_sec() + 1000; + let maker_pubkey = maker_global_nft.derive_htlc_pubkey(&[]); + let taker_pubkey = taker_global_nft.derive_htlc_pubkey(&[]); + + let maker_secret = &[1; 32]; + let maker_secret_hash = sha256(maker_secret).to_vec(); + + let nft_swap_info = NftSwapInfo { + token_address: &erc721_contract(), + token_id: &BigUint::from(2u32).to_bytes(), + contract_type: &ContractType::Erc721, + swap_contract_address: &nft_swap_contract(), + }; + + let send_payment_args: SendNftMakerPaymentArgs = SendNftMakerPaymentArgs { + time_lock, + taker_secret_hash: &[0; 32], + maker_secret_hash: &maker_secret_hash, + amount: 1.into(), + taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), + swap_unique_data: &[], + nft_swap_info: &nft_swap_info, + }; + let maker_payment = block_on(maker_global_nft.send_nft_maker_payment_v2(send_payment_args)).unwrap(); + println!("Maker sent ERC721 NFT Payment tx hash {:02x}", maker_payment.tx_hash()); + + let confirm_input = ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: now_sec() + 70, + check_every: 1, + }; + maker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + + let validate_args = ValidateNftMakerPaymentArgs { + maker_payment_tx: &maker_payment, + time_lock, + taker_secret_hash: &[0; 32], + maker_secret_hash: &maker_secret_hash, + amount: 1.into(), + taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), + maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), + swap_unique_data: &[], + nft_swap_info: &nft_swap_info, + }; + block_on(maker_global_nft.validate_nft_maker_payment_v2(validate_args)).unwrap(); + + let spend_payment_args = SpendNftMakerPaymentArgs { + maker_payment_tx: &maker_payment, + time_lock, + taker_secret_hash: &[0; 32], + maker_secret_hash: &maker_secret_hash, + maker_secret, + maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), + swap_unique_data: &[], + contract_type: &ContractType::Erc721, + swap_contract_address: &nft_swap_contract(), + }; + let spend_tx = block_on(taker_global_nft.spend_nft_maker_payment_v2(spend_payment_args)).unwrap(); + + let confirm_input = ConfirmPaymentInput { + payment_tx: spend_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: now_sec() + 70, + check_every: 1, + }; + taker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + + let new_owner = erc712_owner(U256::from(2)); + assert_eq!(new_owner, taker_global_nft.my_address); +} + +#[test] +fn send_and_spend_erc1155_maker_payment() { + let erc1155_nft = TestNftType::Erc1155 { token_id: 4, amount: 3 }; + + let maker_global_nft = global_nft_with_random_privkey(nft_swap_contract(), Some(erc1155_nft)); + let taker_global_nft = global_nft_with_random_privkey(nft_swap_contract(), None); + + let time_lock = now_sec() + 1000; + let maker_pubkey = maker_global_nft.derive_htlc_pubkey(&[]); + let taker_pubkey = taker_global_nft.derive_htlc_pubkey(&[]); + + let maker_secret = &[1; 32]; + let maker_secret_hash = sha256(maker_secret).to_vec(); + + let nft_swap_info = NftSwapInfo { + token_address: &erc1155_contract(), + token_id: &BigUint::from(4u32).to_bytes(), + contract_type: &ContractType::Erc1155, + swap_contract_address: &nft_swap_contract(), + }; + + let send_payment_args: SendNftMakerPaymentArgs = SendNftMakerPaymentArgs { + time_lock, + taker_secret_hash: &[0; 32], + maker_secret_hash: &maker_secret_hash, + amount: 3.into(), + taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), + swap_unique_data: &[], + nft_swap_info: &nft_swap_info, + }; + let maker_payment = block_on(maker_global_nft.send_nft_maker_payment_v2(send_payment_args)).unwrap(); + println!("Maker sent ERC1155 NFT Payment tx hash {:02x}", maker_payment.tx_hash()); + + let confirm_input = ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: now_sec() + 60, + check_every: 1, + }; + maker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + + let validate_args = ValidateNftMakerPaymentArgs { + maker_payment_tx: &maker_payment, + time_lock, + taker_secret_hash: &[0; 32], + maker_secret_hash: &maker_secret_hash, + amount: 3.into(), + taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), + maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), + swap_unique_data: &[], + nft_swap_info: &nft_swap_info, + }; + block_on(maker_global_nft.validate_nft_maker_payment_v2(validate_args)).unwrap(); + + let spend_payment_args = SpendNftMakerPaymentArgs { + maker_payment_tx: &maker_payment, + time_lock, + taker_secret_hash: &[0; 32], + maker_secret_hash: &maker_secret_hash, + maker_secret, + maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), + swap_unique_data: &[], + contract_type: &ContractType::Erc1155, + swap_contract_address: &nft_swap_contract(), + }; + let spend_tx = block_on(taker_global_nft.spend_nft_maker_payment_v2(spend_payment_args)).unwrap(); + + let confirm_input = ConfirmPaymentInput { + payment_tx: spend_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: now_sec() + 60, + check_every: 1, + }; + taker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + + let balance = erc1155_balance(taker_global_nft.my_address, U256::from(4)); + assert_eq!(balance, U256::from(3)); +} diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index bd2069604f..b916ffb98b 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -1,11 +1,10 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; use coins::utxo::UtxoCommonOps; -use coins::{CoinAssocTypes, ConfirmPaymentInput, DexFee, FundingTxSpend, GenTakerFundingSpendArgs, - GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MarketCoinOps, RefundFundingSecretArgs, - RefundMakerPaymentArgs, RefundPaymentArgs, SendMakerPaymentArgs, SendTakerFundingArgs, - SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, Transaction, ValidateMakerPaymentArgs, - ValidateTakerFundingArgs}; +use coins::{ConfirmPaymentInput, DexFee, FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + MakerCoinSwapOpsV2, MarketCoinOps, ParseCoinAssocTypes, RefundFundingSecretArgs, RefundMakerPaymentArgs, + RefundPaymentArgs, SendMakerPaymentArgs, SendTakerFundingArgs, SwapTxTypeWithSecretHash, + TakerCoinSwapOpsV2, Transaction, ValidateMakerPaymentArgs, ValidateTakerFundingArgs}; use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use futures01::Future; use mm2_number::MmNumber; diff --git a/mm2src/mm2_metrics/Cargo.toml b/mm2src/mm2_metrics/Cargo.toml index 75735228b5..e7a7c60887 100644 --- a/mm2src/mm2_metrics/Cargo.toml +++ b/mm2src/mm2_metrics/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" doctest = false [dependencies] -base64 = "0.10.0" +base64 = "0.21.2" common = { path = "../common" } derive_more = "0.99" futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } diff --git a/mm2src/mm2_metrics/src/mm_metrics.rs b/mm2src/mm2_metrics/src/mm_metrics.rs index c64feb09d7..758280f420 100644 --- a/mm2src/mm2_metrics/src/mm_metrics.rs +++ b/mm2src/mm2_metrics/src/mm_metrics.rs @@ -257,6 +257,8 @@ pub mod prometheus { use crate::{MetricsArc, MetricsWeak}; use super::*; + use base64::engine::general_purpose::URL_SAFE; + use base64::Engine; use futures::future::{Future, FutureExt}; use hyper::http::{self, header, Request, Response, StatusCode}; use hyper::service::{make_service_fn, service_fn}; @@ -365,7 +367,7 @@ pub mod prometheus { .map_err(|err| MmMetricsError::PrometheusServerError(err.to_string())))? })?; - let expected = format!("Basic {}", base64::encode_config(&expected.userpass, base64::URL_SAFE)); + let expected = format!("Basic {}", URL_SAFE.encode(expected.userpass)); if header_value != expected { return Err(MmError::new(MmMetricsError::PrometheusInvalidCredentials( diff --git a/mm2src/mm2_p2p/Cargo.toml b/mm2src/mm2_p2p/Cargo.toml index d1b85635f7..ab1923bd6d 100644 --- a/mm2src/mm2_p2p/Cargo.toml +++ b/mm2src/mm2_p2p/Cargo.toml @@ -22,7 +22,7 @@ rmp-serde = "0.14.3" secp256k1 = { version = "0.20", features = ["rand"] } serde = { version = "1.0", default-features = false } serde_bytes = "0.11.5" -sha2 = "0.9" +sha2 = "0.10" smallvec = "1.6.1" syn = "2.0.18" void = "1.0" diff --git a/mm2src/mm2_p2p/src/lib.rs b/mm2src/mm2_p2p/src/lib.rs index 8e9c359111..03d6a301db 100644 --- a/mm2src/mm2_p2p/src/lib.rs +++ b/mm2src/mm2_p2p/src/lib.rs @@ -10,6 +10,7 @@ use lazy_static::lazy_static; use secp256k1::{Message as SecpMessage, PublicKey as Secp256k1Pubkey, Secp256k1, SecretKey, SignOnly, Signature, VerifyOnly}; use serde::{de, Deserialize, Serialize, Serializer}; +use sha2::digest::Update; use sha2::{Digest, Sha256}; pub use crate::swarm_runtime::SwarmRuntime; diff --git a/mm2src/mm2_test_helpers/dummy_files/erc1155_test_abi.json b/mm2src/mm2_test_helpers/dummy_files/erc1155_test_abi.json new file mode 100644 index 0000000000..8b2e2c2f80 --- /dev/null +++ b/mm2src/mm2_test_helpers/dummy_files/erc1155_test_abi.json @@ -0,0 +1,455 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "uri", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC1155InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC1155InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "idsLength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "valuesLength", + "type": "uint256" + } + ], + "name": "ERC1155InvalidArrayLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "ERC1155InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC1155InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC1155InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC1155MissingApprovalForAll", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/dummy_files/erc721_test_abi.json b/mm2src/mm2_test_helpers/dummy_files/erc721_test_abi.json new file mode 100644 index 0000000000..697eccce5c --- /dev/null +++ b/mm2src/mm2_test_helpers/dummy_files/erc721_test_abi.json @@ -0,0 +1,469 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC721IncorrectOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC721InsufficientApproval", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC721InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "ERC721InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC721InvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC721InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC721InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC721NonexistentToken", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 1728d92464..439a33813f 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -813,6 +813,23 @@ pub fn erc20_dev_conf(contract_address: &str) -> Json { }) } +/// global NFT configuration used for dockerized Geth dev node +pub fn nft_dev_conf() -> Json { + json!({ + "coin": "NFT_ETH", + "name": "nftdev", + "chain_id": 1337, + "mm2": 1, + "derivation_path": "m/44'/60'", + "protocol": { + "type": "NFT", + "protocol_data": { + "platform": "ETH" + } + } + }) +} + pub fn eth_sepolia_conf() -> Json { json!({ "coin": "ETH",