diff --git a/.github/actions/private-tangle/setup/action.yml b/.github/actions/private-tangle/setup/action.yml index 2504053375..de56383ba7 100644 --- a/.github/actions/private-tangle/setup/action.yml +++ b/.github/actions/private-tangle/setup/action.yml @@ -38,6 +38,7 @@ runs: - name: Wait for tangle to start shell: bash run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/v2.2.4/wait-for | sh -s -- -t 120 http://localhost:8050/health -- echo "Tangle is up" - - name: Wait for faucet to start - shell: bash - run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/v2.2.4/wait-for | sh -s -- -t 120 http://localhost:8088/health -- echo "Faucet is up" + # TODO https://github.com/iotaledger/iota-sdk/issues/2154 + # - name: Wait for faucet to start + # shell: bash + # run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/v2.2.4/wait-for | sh -s -- -t 120 http://localhost:8088/health -- echo "Faucet is up" diff --git a/.github/workflows/bindings-wasm-publish.yml b/.github/workflows/bindings-wasm-publish.yml index 560ecad898..f497331257 100644 --- a/.github/workflows/bindings-wasm-publish.yml +++ b/.github/workflows/bindings-wasm-publish.yml @@ -25,7 +25,7 @@ jobs: - name: Install wasm-bindgen-cli uses: jetli/wasm-bindgen-action@v0.2.0 with: - version: "0.2.90" + version: "0.2.92" - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/bindings-wasm.yml b/.github/workflows/bindings-wasm.yml index 6b32cd07d4..77b6051fcd 100644 --- a/.github/workflows/bindings-wasm.yml +++ b/.github/workflows/bindings-wasm.yml @@ -65,7 +65,7 @@ jobs: - name: Install wasm-bindgen-cli uses: jetli/wasm-bindgen-action@v0.2.0 with: - version: "0.2.90" + version: "0.2.92" - name: Set Up Node.js ${{ matrix.node }} and Yarn Cache uses: actions/setup-node@v3 diff --git a/Cargo.lock b/Cargo.lock index 01d2d20049..e2f4a0bb1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -81,9 +81,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "anstream" -version = "0.6.11" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "anymap" @@ -159,7 +159,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -237,9 +237,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bech32" -version = "0.10.0-beta" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bincode" @@ -268,7 +268,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -341,9 +341,9 @@ checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "byte-slice-cast" @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" dependencies = [ "jobserver", "libc", @@ -425,9 +425,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "num-traits", ] @@ -456,9 +456,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive", @@ -466,9 +466,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", @@ -485,7 +485,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -503,13 +503,14 @@ dependencies = [ "colored 2.1.0", "dialoguer", "dotenvy", + "eyre", "fern-logger", "iota-sdk", "log", "prefix-hex", "rustyline", "serde_json", - "strum 0.25.0", + "strum", "thiserror", "tokio", "winapi", @@ -519,9 +520,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "5.1.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec832972fefb8cf9313b45a0d1945e29c9c251f1d4c6eafc5fe2124c02d2e81" +checksum = "12f9a0700e0127ba15d1d52dd742097f821cd9c65939303a44d970465040a297" dependencies = [ "error-code", ] @@ -664,12 +665,12 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" +checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c" dependencies = [ "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -719,14 +720,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "darling" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5d6b04b3fd0ba9926f945895de7d806260a2d7431ba82e7edaecb043c4c6b8" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -734,27 +735,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e48a959bcd5c761246f5d090ebc2fbf7b9cd527a492b07a67510c108f1e7e3" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "darling_macro" -version = "0.20.5" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1545d67a2149e1d93b7e5c7752dce5a7426eb5d1357ddcfd89336b94444f77" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -815,7 +816,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1005,9 +1006,19 @@ dependencies = [ [[package]] name = "error-code" -version = "3.0.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] [[package]] name = "fd-lock" @@ -1170,7 +1181,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -1241,9 +1252,9 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", "polyval", @@ -1350,9 +1361,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1362,9 +1373,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hidapi" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a722fb137d008dbf264f54612457f8eb6a299efbcb0138178964a0809035d74" +checksum = "9e58251020fe88fe0dae5ebcc1be92b4995214af84725b375d08354d0311c23c" dependencies = [ "cc", "cfg-if", @@ -1402,9 +1413,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1517,11 +1528,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown", @@ -1646,7 +1663,7 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "strum 0.26.1", + "strum", "thiserror", "time", "tokio", @@ -1672,7 +1689,7 @@ dependencies = [ "primitive-types", "serde", "serde_json", - "strum 0.25.0", + "strum", "thiserror", "tokio", "url", @@ -1750,9 +1767,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe8f25ce1159c7740ff0b9b2f5cdf4a8428742ba7c112b9f20f22cd5219c7dab" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", @@ -1788,9 +1805,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1829,12 +1846,12 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "2caa5afb8bf9f3a2652760ce7d4f62d21c4d5a423e68466fca30df82f2330164" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.4", ] [[package]] @@ -1904,9 +1921,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "serde", ] @@ -1968,9 +1985,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -1979,9 +1996,9 @@ dependencies = [ [[package]] name = "napi" -version = "2.15.1" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43792514b0c95c5beec42996da0c1b39265b02b75c97baa82d163d3ef55cbfa7" +checksum = "54a63d0570e4c3e0daf7a8d380563610e159f538e20448d6c911337246f40e84" dependencies = [ "bitflags 2.4.2", "ctor", @@ -1993,34 +2010,35 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b4532cf86bfef556348ac65e561e3123879f0e7566cca6d43a6ff5326f13df" +checksum = "2f9130fccc5f763cf2069b34a089a18f0d0883c66aceb81f2fad541a3d823c43" [[package]] name = "napi-derive" -version = "2.15.1" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d56bb899c164ab1be5e542ae7db8b26750c864bf2eef07295f17754e6358777" +checksum = "05bb7c37e3c1dda9312fdbe4a9fc7507fca72288ba154ec093e2d49114e727ce" dependencies = [ "cfg-if", "convert_case", "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "napi-derive-backend" -version = "1.0.60" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cf2d74ac66fd1cccb646be75fdd1c1dce8acfe20a68f61566a31da0d3eb9786" +checksum = "f785a8b8d7b83e925f5aa6d2ae3c159d17fe137ac368dc185bef410e7acdaeb4" dependencies = [ "convert_case", + "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -2101,9 +2119,9 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] @@ -2125,9 +2143,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl-probe" @@ -2137,9 +2155,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "packable" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ebbd9715a319d515dbc253604dd00b0e2c8618e4e5e4d3e0b9b4e46b90ef98e" +checksum = "01fc964b1de9aff3b0a0e5c68048d342ca247da967b96b96489617f1bd51cc3d" dependencies = [ "autocfg", "hashbrown", @@ -2150,15 +2168,14 @@ dependencies = [ [[package]] name = "packable-derive" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "858971e010057f7bcae183e545085b83d41280ca8abe0333613a7135fbb54430" +checksum = "0698d973173b50fb1949f7e2e9516544dc1149610262c30b3e9d8ddace1a462e" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", + "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.52", ] [[package]] @@ -2181,7 +2198,7 @@ version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate 2.0.2", + "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", @@ -2281,9 +2298,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" @@ -2304,9 +2321,9 @@ dependencies = [ [[package]] name = "polyval" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", @@ -2314,6 +2331,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -2354,7 +2377,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -2369,16 +2392,6 @@ dependencies = [ "uint", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - [[package]] name = "proc-macro-crate" version = "2.0.2" @@ -2386,7 +2399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" dependencies = [ "toml_datetime", - "toml_edit 0.20.2", + "toml_edit", ] [[package]] @@ -2424,15 +2437,16 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0" +checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" dependencies = [ "cfg-if", "indoc", "libc", "memoffset 0.9.0", "parking_lot", + "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -2441,9 +2455,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be" +checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" dependencies = [ "once_cell", "target-lexicon", @@ -2451,9 +2465,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1" +checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" dependencies = [ "libc", "pyo3-build-config", @@ -2461,26 +2475,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3" +checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "pyo3-macros-backend" -version = "0.20.2" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f" +checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" dependencies = [ "heck", "proc-macro2", + "pyo3-build-config", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -2578,9 +2593,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2646,16 +2661,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2821,14 +2837,14 @@ checksum = "e5af959c8bf6af1aff6d2b463a57f71aae53d1332da58419e30ad8dc7011d951" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "salsa20" @@ -2924,35 +2940,35 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -2967,7 +2983,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3053,12 +3069,12 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3156,35 +3172,13 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.3", -] - [[package]] name = "strum" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" dependencies = [ - "strum_macros 0.26.1", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.48", + "strum_macros", ] [[package]] @@ -3197,7 +3191,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3219,9 +3213,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" dependencies = [ "proc-macro2", "quote", @@ -3263,28 +3257,28 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.13" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3378,7 +3372,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3411,17 +3405,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", -] - [[package]] name = "toml_edit" version = "0.20.2" @@ -3458,7 +3441,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -3528,9 +3511,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -3607,9 +3590,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -3632,9 +3615,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "serde", @@ -3644,24 +3627,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -3671,9 +3654,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3681,22 +3664,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-logger" @@ -3711,9 +3694,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -3802,7 +3785,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -3822,17 +3805,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -3843,9 +3826,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -3861,9 +3844,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -3879,9 +3862,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -3897,9 +3880,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -3915,9 +3898,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -3927,9 +3910,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -3945,15 +3928,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" -version = "0.5.39" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -4031,7 +4014,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] [[package]] @@ -4052,5 +4035,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.52", ] diff --git a/bindings/core/Cargo.toml b/bindings/core/Cargo.toml index 0ef374146c..4b7b81d115 100644 --- a/bindings/core/Cargo.toml +++ b/bindings/core/Cargo.toml @@ -11,6 +11,7 @@ publish = false [dependencies] iota-sdk = { path = "../../sdk", default-features = false, features = [ "wallet", + "protocol_parameters_samples", "tls", ] } @@ -23,12 +24,14 @@ iota-crypto = { version = "0.23.1", default-features = false, features = [ "bip44", ] } log = { version = "0.4.20", default-features = false } -packable = { version = "0.10.1", default-features = false } +packable = { version = "0.11.0", default-features = false, features = [ + "primitive-types", +] } prefix-hex = { version = "0.7.1", default-features = false } primitive-types = { version = "0.12.2", default-features = false } serde = { version = "1.0.196", default-features = false } serde_json = { version = "1.0.113", default-features = false } -strum = { version = "0.25.0", default-features = false, features = ["derive"] } +strum = { version = "0.26.1", default-features = false, features = ["derive"] } thiserror = { version = "1.0.56", default-features = false } tokio = { version = "1.35.1", default-features = false } url = { version = "2.4.1", default-features = false, features = ["serde"] } diff --git a/bindings/core/src/error.rs b/bindings/core/src/error.rs index ad52cd9fb8..128b6d4454 100644 --- a/bindings/core/src/error.rs +++ b/bindings/core/src/error.rs @@ -1,12 +1,13 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use iota_sdk::types::block::{ + mana::ManaError, output::OutputError, payload::PayloadError, semantic::TransactionFailureReason, + signature::SignatureError, BlockError, +}; use packable::error::UnexpectedEOF; use serde::{Serialize, Serializer}; -/// Result type of the bindings core crate. -pub type Result = std::result::Result; - /// Error type for the bindings core crate. #[derive(Debug, thiserror::Error, strum::AsRefStr)] #[strum(serialize_all = "camelCase")] @@ -14,13 +15,28 @@ pub type Result = std::result::Result; pub enum Error { /// Block errors. #[error("{0}")] - Block(#[from] iota_sdk::types::block::Error), + Block(#[from] BlockError), + /// Output errors. + #[error("{0}")] + Output(#[from] OutputError), + /// Payload errors. + #[error("{0}")] + Payload(#[from] PayloadError), + /// Signature errors. + #[error("{0}")] + Signature(#[from] SignatureError), + /// Mana errors. + #[error("{0}")] + Mana(#[from] ManaError), + /// Semantic errors. + #[error("{0}")] + TransactionSemantic(#[from] TransactionFailureReason), /// Client errors. #[error("{0}")] - Client(#[from] iota_sdk::client::Error), + Client(#[from] iota_sdk::client::ClientError), /// Wallet errors. #[error("{0}")] - Wallet(#[from] iota_sdk::wallet::Error), + Wallet(#[from] iota_sdk::wallet::WalletError), /// Prefix hex errors. #[error("{0}")] PrefixHex(#[from] prefix_hex::Error), @@ -29,20 +45,20 @@ pub enum Error { SerdeJson(#[from] serde_json::error::Error), /// Unpack errors. #[error("{0}")] - Unpack(#[from] packable::error::UnpackError), + Unpack(#[from] packable::error::UnpackError), } #[cfg(feature = "stronghold")] impl From for Error { fn from(error: iota_sdk::client::stronghold::Error) -> Self { - Self::Client(iota_sdk::client::Error::Stronghold(error)) + Self::Client(iota_sdk::client::ClientError::Stronghold(error)) } } #[cfg(feature = "mqtt")] impl From for Error { fn from(error: iota_sdk::client::node_api::mqtt::Error) -> Self { - Self::Client(iota_sdk::client::Error::Mqtt(error)) + Self::Client(iota_sdk::client::ClientError::Mqtt(error)) } } diff --git a/bindings/core/src/lib.rs b/bindings/core/src/lib.rs index d7dc366cfa..2c36cb9403 100644 --- a/bindings/core/src/lib.rs +++ b/bindings/core/src/lib.rs @@ -19,7 +19,7 @@ use iota_sdk::{ client::secret::{SecretManager, SecretManagerDto}, types::block::address::Bech32Address, utils::serde::bip44::option_bip44, - wallet::{ClientOptions, Wallet}, + wallet::{ClientOptions, Wallet, WalletError}, }; use serde::Deserialize; @@ -28,7 +28,7 @@ pub use self::method_handler::listen_mqtt; #[cfg(not(target_family = "wasm"))] pub use self::method_handler::CallMethod; pub use self::{ - error::{Error, Result}, + error::Error, method::{ClientMethod, SecretManagerMethod, UtilsMethod, WalletMethod}, method_handler::{call_client_method, call_secret_manager_method, call_utils_method, call_wallet_method}, response::Response, @@ -45,9 +45,9 @@ pub fn init_logger(config: String) -> std::result::Result<(), fern_logger::Error #[serde(rename_all = "camelCase")] pub struct WalletOptions { pub address: Option, - pub alias: Option, #[serde(with = "option_bip44", default)] pub bip_path: Option, + pub alias: Option, pub client_options: Option, #[derivative(Debug(format_with = "OmittedDebug::omitted_fmt"))] pub secret_manager: Option, @@ -60,13 +60,13 @@ impl WalletOptions { self } - pub fn with_alias(mut self, alias: impl Into>) -> Self { - self.alias = alias.into(); + pub fn with_bip_path(mut self, bip_path: impl Into>) -> Self { + self.bip_path = bip_path.into(); self } - pub fn with_bip_path(mut self, bip_path: impl Into>) -> Self { - self.bip_path = bip_path.into(); + pub fn with_alias(mut self, alias: impl Into>) -> Self { + self.alias = alias.into(); self } @@ -86,12 +86,12 @@ impl WalletOptions { self } - pub async fn build(self) -> iota_sdk::wallet::Result { + pub async fn build(self) -> Result { log::debug!("wallet options: {self:?}"); let mut builder = Wallet::builder() .with_address(self.address) - .with_alias(self.alias) .with_bip_path(self.bip_path) + .with_alias(self.alias) .with_client_options(self.client_options); #[cfg(feature = "storage")] diff --git a/bindings/core/src/method/client.rs b/bindings/core/src/method/client.rs index 63c07f61d4..d10431bf32 100644 --- a/bindings/core/src/method/client.rs +++ b/bindings/core/src/method/client.rs @@ -14,7 +14,7 @@ use iota_sdk::{ node_manager::node::NodeAuth, }, types::block::{ - address::{Bech32Address, Hrp}, + address::{Address, Bech32Address, Hrp}, output::{ feature::Feature, unlock_condition::UnlockCondition, AccountId, AnchorId, DelegationId, FoundryId, NftId, Output, OutputId, TokenScheme, @@ -104,8 +104,6 @@ pub enum ClientMethod { }, /// Get a node candidate from the healthy node pool. GetNode, - /// Gets the network related information such as network_id. - GetNetworkInfo, /// Gets the network id of the node we're connecting to. GetNetworkId, /// Returns the bech32_hrp @@ -135,20 +133,24 @@ pub enum ClientMethod { ////////////////////////////////////////////////////////////////////// // Node core API ////////////////////////////////////////////////////////////////////// - /// Get health + /// Returns the health of the node. GetHealth { /// Url url: String, }, - /// Get node info - GetNodeInfo { + /// Returns the available API route groups of the node. + GetRoutes, + /// Returns general information about a node given its URL and - if required - the authentication data. + GetInfo { /// Url url: String, /// Node authentication auth: Option, }, - /// Returns the node information together with the url of the used node - GetInfo, + /// Returns general information about the node together with its URL. + GetNodeInfo, + /// Returns network metrics. + GetNetworkMetrics, /// Check the readiness of the node to issue a new block, the reference mana cost based on the rate setter and /// current network congestion, and the block issuance credits of the requested account. #[serde(rename_all = "camelCase")] @@ -160,7 +162,7 @@ pub enum ClientMethod { }, /// Returns all the available Mana rewards of an account or delegation output in the returned range of epochs. #[serde(rename_all = "camelCase")] - GetRewards { + GetOutputManaRewards { /// Output ID of an account or delegation output. output_id: OutputId, /// A client can specify a slot index explicitly, which should be equal to the slot it uses as the commitment @@ -203,7 +205,7 @@ pub enum ClientMethod { /// Post block (raw) #[serde(rename_all = "camelCase")] PostBlockRaw { - /// Block + /// Block as raw bytes block_bytes: Vec, }, /// Get block @@ -224,25 +226,31 @@ pub enum ClientMethod { /// Block ID block_id: BlockId, }, - /// Get block raw + /// Get block as raw bytes. #[serde(rename_all = "camelCase")] GetBlockRaw { /// Block ID block_id: BlockId, }, - /// Get output + /// Get output with its output ID proof. #[serde(rename_all = "camelCase")] GetOutput { /// Output ID output_id: OutputId, }, - /// Get output metadata + /// Get output as raw bytes. + #[serde(rename_all = "camelCase")] + GetOutputRaw { + /// Output ID + output_id: OutputId, + }, + /// Get output metadata. #[serde(rename_all = "camelCase")] GetOutputMetadata { /// Output ID output_id: OutputId, }, - /// Get output with its metadata + /// Get output with its metadata including the output ID proof. #[serde(rename_all = "camelCase")] GetOutputWithMetadata { /// Output ID @@ -254,6 +262,12 @@ pub enum ClientMethod { /// Transaction ID transaction_id: TransactionId, }, + /// Returns the raw bytes of the included block of a transaction. + #[serde(rename_all = "camelCase")] + GetIncludedBlockRaw { + /// Transaction ID + transaction_id: TransactionId, + }, /// Returns the included block metadata of the transaction. #[serde(rename_all = "camelCase")] GetIncludedBlockMetadata { @@ -272,6 +286,12 @@ pub enum ClientMethod { /// Commitment ID of the commitment to look up. commitment_id: SlotCommitmentId, }, + /// Look up a commitment by a given commitment ID and return its raw bytes. + #[serde(rename_all = "camelCase")] + GetCommitmentRaw { + /// Commitment ID of the commitment to look up. + commitment_id: SlotCommitmentId, + }, /// Get all UTXO changes of a given slot by Commitment ID. #[serde(rename_all = "camelCase")] GetUtxoChanges { @@ -285,17 +305,22 @@ pub enum ClientMethod { commitment_id: SlotCommitmentId, }, /// Look up a commitment by a given commitment index. - GetCommitmentByIndex { + GetCommitmentBySlot { + /// Index of the commitment to look up. + slot: SlotIndex, + }, + /// Look up a commitment by a given commitment index and return its raw bytes. + GetCommitmentBySlotRaw { /// Index of the commitment to look up. slot: SlotIndex, }, /// Get all UTXO changes of a given slot by commitment index. - GetUtxoChangesByIndex { + GetUtxoChangesBySlot { /// Index of the commitment to look up. slot: SlotIndex, }, /// Get all full UTXO changes of a given slot by commitment index. - GetUtxoChangesFullByIndex { + GetUtxoChangesFullBySlot { /// Index of the commitment to look up. slot: SlotIndex, }, @@ -379,16 +404,16 @@ pub enum ClientMethod { ////////////////////////////////////////////////////////////////////// // High level API ////////////////////////////////////////////////////////////////////// - /// Fetch OutputWithMetadataResponse from provided OutputIds (requests are sent in parallel) + /// Fetch outputs with associated output ID proofs from provided OutputIds (requests are sent in parallel) #[serde(rename_all = "camelCase")] GetOutputs { /// Output IDs output_ids: Vec, }, - /// Try to get OutputWithMetadataResponse from provided OutputIds (requests are sent in parallel and errors are - /// ignored, can be useful for spent outputs) + /// Try to get outputs with associated output ID proofs from provided OutputIds (requests are sent in parallel and + /// errors are ignored, can be useful for spent outputs) #[serde(rename_all = "camelCase")] - GetOutputsIgnoreErrors { + GetOutputsIgnoreNotFound { /// Output IDs output_ids: Vec, }, @@ -409,38 +434,9 @@ pub enum ClientMethod { ////////////////////////////////////////////////////////////////////// // Utils ////////////////////////////////////////////////////////////////////// - /// Transforms a hex encoded address to a bech32 encoded address - #[serde(rename_all = "camelCase")] - HexToBech32 { - /// Hex encoded bech32 address - hex: String, - /// Human readable part - bech32_hrp: Option, - }, - /// Transforms an account id to a bech32 encoded address - #[serde(rename_all = "camelCase")] - AccountIdToBech32 { - /// Account ID - account_id: AccountId, - /// Human readable part - bech32_hrp: Option, - }, - /// Transforms an nft id to a bech32 encoded address - #[serde(rename_all = "camelCase")] - NftIdToBech32 { - /// Nft ID - nft_id: NftId, - /// Human readable part - bech32_hrp: Option, - }, - /// Transforms a hex encoded public key to a bech32 encoded address + /// Converts an address to its bech32 representation #[serde(rename_all = "camelCase")] - HexPublicKeyToBech32Address { - /// Hex encoded public key - hex: String, - /// Human readable part - bech32_hrp: Option, - }, + AddressToBech32 { address: Address, bech32_hrp: Option }, /// Calculate the minimum required amount for an output. /// Expected response: /// [`Amount`](crate::Response::Amount) diff --git a/bindings/core/src/method/secret_manager.rs b/bindings/core/src/method/secret_manager.rs index 2a13acbca0..4cb40cf985 100644 --- a/bindings/core/src/method/secret_manager.rs +++ b/bindings/core/src/method/secret_manager.rs @@ -19,7 +19,7 @@ use crate::OmittedDebug; #[serde(tag = "name", content = "data", rename_all = "camelCase")] #[non_exhaustive] pub enum SecretManagerMethod { - /// Generate Ed25519 addresses. + /// Generate multiple Ed25519 addresses at once. GenerateEd25519Addresses { /// Addresses generation options options: GetAddressesOptions, diff --git a/bindings/core/src/method/utils.rs b/bindings/core/src/method/utils.rs index 246a2febd5..9eacde8567 100644 --- a/bindings/core/src/method/utils.rs +++ b/bindings/core/src/method/utils.rs @@ -7,8 +7,8 @@ use derivative::Derivative; use iota_sdk::{ client::secret::types::InputSigningData, types::block::{ - address::{Bech32Address, Hrp}, - output::{AccountId, NftId, Output, OutputId, StorageScoreParameters}, + address::{Address, Bech32Address, Hrp}, + output::{AccountId, Output, OutputId, StorageScoreParameters}, payload::signed_transaction::{ dto::{SignedTransactionPayloadDto, TransactionDto}, TransactionId, @@ -19,7 +19,7 @@ use iota_sdk::{ unlock::Unlock, BlockDto, }, - utils::serde::{mana_rewards, string}, + utils::serde::{option_mana_rewards, prefix_hex_bytes, string}, }; use serde::{Deserialize, Serialize}; @@ -31,24 +31,20 @@ use crate::OmittedDebug; #[serde(tag = "name", content = "data", rename_all = "camelCase")] #[non_exhaustive] pub enum UtilsMethod { - /// Transforms bech32 to hex - Bech32ToHex { bech32: Bech32Address }, - /// Transforms a hex encoded address to a bech32 encoded address + /// Converts an address to its bech32 representation #[serde(rename_all = "camelCase")] - HexToBech32 { hex: String, bech32_hrp: Hrp }, - /// Transforms an account id to a bech32 encoded address - #[serde(rename_all = "camelCase")] - AccountIdToBech32 { account_id: AccountId, bech32_hrp: Hrp }, - /// Transforms an nft id to a bech32 encoded address - #[serde(rename_all = "camelCase")] - NftIdToBech32 { nft_id: NftId, bech32_hrp: Hrp }, - /// Transforms a hex encoded public key to a bech32 encoded address - #[serde(rename_all = "camelCase")] - HexPublicKeyToBech32Address { hex: String, bech32_hrp: Hrp }, + AddressToBech32 { + address: Address, + bech32_hrp: Hrp, + }, /// Returns a valid Address parsed from a String. - ParseBech32Address { address: Bech32Address }, + ParseBech32Address { + address: Bech32Address, + }, /// Checks if a String is a valid bech32 encoded address. - IsAddressValid { address: String }, + IsAddressValid { + address: String, + }, /// Generates a new mnemonic. GenerateMnemonic, /// Returns a hex encoded seed for a mnemonic. @@ -63,10 +59,15 @@ pub enum UtilsMethod { protocol_parameters: ProtocolParameters, }, /// Returns the transaction ID (Blake2b256 hash of the provided transaction payload) - TransactionId { payload: SignedTransactionPayloadDto }, - /// Computes the account ID + TransactionId { + payload: SignedTransactionPayloadDto, + }, + /// Computes the Blake2b256 hash of the provided hex encoded bytes. #[serde(rename_all = "camelCase")] - ComputeAccountId { output_id: OutputId }, + Blake2b256Hash { + #[serde(with = "prefix_hex_bytes")] + bytes: Vec, + }, /// Computes the Foundry ID #[serde(rename_all = "camelCase")] ComputeFoundryId { @@ -74,11 +75,11 @@ pub enum UtilsMethod { serial_number: u32, token_scheme_type: u8, }, - /// Computes the NFT ID - #[serde(rename_all = "camelCase")] - ComputeNftId { output_id: OutputId }, /// Computes the output ID from transaction id and output index - ComputeOutputId { id: TransactionId, index: u16 }, + ComputeOutputId { + id: TransactionId, + index: u16, + }, /// Computes a tokenId from the accountId, serial number and token scheme type. #[serde(rename_all = "camelCase")] ComputeTokenId { @@ -88,9 +89,13 @@ pub enum UtilsMethod { }, /// Computes the hash of the given protocol parameters. #[serde(rename_all = "camelCase")] - ProtocolParametersHash { protocol_parameters: ProtocolParameters }, + ProtocolParametersHash { + protocol_parameters: ProtocolParameters, + }, /// Computes the signing hash of a transaction. - TransactionSigningHash { transaction: TransactionDto }, + TransactionSigningHash { + transaction: TransactionDto, + }, /// Computes the minimum required amount of an output. #[serde(rename_all = "camelCase")] ComputeMinimumOutputAmount { @@ -117,22 +122,27 @@ pub enum UtilsMethod { }, /// Creates a UTXOInput from outputId. #[serde(rename_all = "camelCase")] - OutputIdToUtxoInput { output_id: OutputId }, + OutputIdToUtxoInput { + output_id: OutputId, + }, /// Computes the slot commitment id from a slot commitment. #[serde(rename_all = "camelCase")] - ComputeSlotCommitmentId { slot_commitment: SlotCommitment }, + ComputeSlotCommitmentId { + slot_commitment: SlotCommitment, + }, /// Returns the hex representation of the serialized output bytes. - #[serde(rename_all = "camelCase")] - OutputHexBytes { output: Output }, + OutputHexBytes { + output: Output, + }, /// Verifies the semantic of a transaction. - /// Expected response: [`TransactionFailureReason`](crate::Response::TransactionFailureReason) + /// Expected response: [`Ok`](crate::Response::Ok) #[serde(rename_all = "camelCase")] VerifyTransactionSemantic { transaction: TransactionDto, inputs: Vec, unlocks: Option>, - #[serde(default, with = "mana_rewards")] - mana_rewards: BTreeMap, + #[serde(default, with = "option_mana_rewards")] + mana_rewards: Option>, protocol_parameters: ProtocolParameters, }, /// Applies mana decay to the given mana. @@ -175,4 +185,6 @@ pub enum UtilsMethod { /// Block block: BlockDto, }, + IotaMainnetProtocolParameters, + ShimmerMainnetProtocolParameters, } diff --git a/bindings/core/src/method/wallet.rs b/bindings/core/src/method/wallet.rs index efd3dd13d3..1522beb9a8 100644 --- a/bindings/core/src/method/wallet.rs +++ b/bindings/core/src/method/wallet.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use crypto::keys::bip44::Bip44; use derivative::Derivative; -use iota_sdk::utils::serde::string; +use iota_sdk::client::api::options::TransactionOptions; #[cfg(feature = "events")] use iota_sdk::wallet::events::types::{WalletEvent, WalletEventType}; // #[cfg(feature = "participation")] @@ -17,19 +17,18 @@ use iota_sdk::wallet::events::types::{WalletEvent, WalletEventType}; // }; use iota_sdk::{ client::{ - api::{input_selection::Burn, PreparedTransactionDataDto, SignedTransactionDataDto}, + api::{transaction_builder::Burn, PreparedTransactionDataDto, SignedTransactionDataDto}, node_manager::node::NodeAuth, - secret::GenerateAddressOptions, }, types::block::{ - address::{Bech32Address, Hrp}, + address::Hrp, output::{AccountId, DelegationId, Output, OutputId, TokenId}, payload::signed_transaction::TransactionId, }, wallet::{ BeginStakingParams, ClientOptions, ConsolidationParams, CreateAccountParams, CreateDelegationParams, - CreateNativeTokenParams, FilterOptions, MintNftParams, OutputParams, OutputsToClaim, SendNativeTokenParams, - SendNftParams, SendParams, SyncOptions, TransactionOptions, + CreateNativeTokenParams, FilterOptions, MintNftParams, OutputParams, OutputsToClaim, SendManaParams, + SendNativeTokenParams, SendNftParams, SendParams, SyncOptions, }, U256, }; @@ -52,7 +51,7 @@ pub enum WalletMethod { /// Expected response: [`Ok`](crate::Response::Ok) #[cfg(feature = "stronghold")] #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] - Backup { + BackupToStrongholdSnapshot { /// The backup destination. destination: PathBuf, /// Stronghold file password. @@ -71,7 +70,7 @@ pub enum WalletMethod { #[cfg(feature = "stronghold")] #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] #[serde(rename_all = "camelCase")] - RestoreBackup { + RestoreFromStrongholdSnapshot { /// The path to the backed up Stronghold. source: PathBuf, /// Stronghold file password. @@ -119,10 +118,6 @@ pub enum WalletMethod { /// Expected response: [`OutputIds`](crate::Response::OutputIds) #[serde(rename_all = "camelCase")] ClaimableOutputs { outputs_to_claim: OutputsToClaim }, - /// Claim outputs. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - #[serde(rename_all = "camelCase")] - ClaimOutputs { output_ids_to_claim: Vec }, // /// Removes a previously registered participation event from local storage. // /// Expected response: [`Ok`](crate::Response::Ok) // #[cfg(feature = "participation")] @@ -308,13 +303,20 @@ pub enum WalletMethod { #[serde(default)] transaction_options: Option, }, - /// Prepare to send base coins. + /// Prepare to send base coins to multiple addresses, or with additional parameters. /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) PrepareSend { params: Vec, #[serde(default)] options: Option, }, + /// Prepare to send mana. + /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) + PrepareSendMana { + params: SendManaParams, + #[serde(default)] + options: Option, + }, /// Prepare to send native tokens. /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) PrepareSendNativeTokens { @@ -357,11 +359,17 @@ pub enum WalletMethod { PrepareExtendStaking { account_id: AccountId, additional_epochs: u32, + #[serde(default)] + options: Option, }, /// Prepare to end staking. /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) #[serde(rename_all = "camelCase")] - PrepareEndStaking { account_id: AccountId }, + PrepareEndStaking { + account_id: AccountId, + #[serde(default)] + options: Option, + }, /// Announce candidacy for an account. /// Expected response: [`BlockId`](crate::Response::BlockId) #[serde(rename_all = "camelCase")] @@ -372,9 +380,9 @@ pub enum WalletMethod { // #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] // #[serde(rename_all = "camelCase")] // PrepareStopParticipating { event_id: ParticipationEventId }, - /// Prepare transaction. + /// Prepare to send outputs. /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) - PrepareTransaction { + PrepareSendOutputs { outputs: Vec, #[serde(default)] options: Option, @@ -398,7 +406,6 @@ pub enum WalletMethod { // options: ParticipationEventRegistrationOptions, // }, /// Checks the transaction state for a provided transaction id until it's accepted. Interval in milliseconds. - /// Returns the block id that contains this transaction. /// Expected response: [`BlockId`](crate::Response::BlockId) #[serde(rename_all = "camelCase")] WaitForTransactionAcceptance { @@ -409,29 +416,6 @@ pub enum WalletMethod { /// Maximum attempts max_attempts: Option, }, - /// Send base coins. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - Send { - #[serde(with = "string")] - amount: u64, - address: Bech32Address, - #[serde(default)] - options: Option, - }, - /// Send base coins to multiple addresses, or with additional parameters. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - SendWithParams { - params: Vec, - #[serde(default)] - options: Option, - }, - /// Send outputs in a transaction. - /// Expected response: [`SentTransaction`](crate::Response::SentTransaction) - SendOutputs { - outputs: Vec, - #[serde(default)] - options: Option, - }, /// Set the alias of the wallet. /// Expected response: [`Ok`](crate::Response::Ok) SetAlias { alias: String }, @@ -471,27 +455,11 @@ pub enum WalletMethod { /// Expected response: [`OutputsData`](crate::Response::OutputsData) #[serde(rename_all = "camelCase")] UnspentOutputs { filter_options: Option }, - /// Emits an event for testing if the event system is working /// Expected response: [`Ok`](crate::Response::Ok) #[cfg(feature = "events")] #[cfg_attr(docsrs, doc(cfg(feature = "events")))] EmitTestEvent { event: WalletEvent }, - - // TODO: reconsider whether to have the following methods on the wallet - /// Generate an address without storing it - /// Expected response: [`Bech32Address`](crate::Response::Bech32Address) - #[serde(rename_all = "camelCase")] - GenerateEd25519Address { - /// Account index - account_index: u32, - /// Account index - address_index: u32, - /// Options - options: Option, - /// Bech32 HRP - bech32_hrp: Option, - }, /// Get the ledger nano status /// Expected response: [`LedgerNanoStatus`](crate::Response::LedgerNanoStatus) #[cfg(feature = "ledger_nano")] diff --git a/bindings/core/src/method_handler/call_method.rs b/bindings/core/src/method_handler/call_method.rs index 44e3574d40..f16e460692 100644 --- a/bindings/core/src/method_handler/call_method.rs +++ b/bindings/core/src/method_handler/call_method.rs @@ -85,7 +85,7 @@ pub async fn call_secret_manager_method method: SecretManagerMethod, ) -> Response where - iota_sdk::client::Error: From, + iota_sdk::client::ClientError: From, { log::debug!("Secret manager method: {method:?}"); let result = diff --git a/bindings/core/src/method_handler/client.rs b/bindings/core/src/method_handler/client.rs index d8a3e31564..f3159e5d5d 100644 --- a/bindings/core/src/method_handler/client.rs +++ b/bindings/core/src/method_handler/client.rs @@ -6,7 +6,6 @@ use iota_sdk::client::mqtt::{MqttPayload, Topic}; use iota_sdk::{ client::{request_funds_from_faucet, Client}, types::{ - api::core::OutputWithMetadataResponse, block::{ output::{ AccountOutputBuilder, BasicOutputBuilder, FoundryOutputBuilder, MinimumOutputAmount, NftOutputBuilder, @@ -18,7 +17,7 @@ use iota_sdk::{ }, }; -use crate::{method::ClientMethod, response::Response, Result}; +use crate::{method::ClientMethod, response::Response}; /// Listen to MQTT events #[cfg(feature = "mqtt")] @@ -53,7 +52,10 @@ where } /// Call a client method. -pub(crate) async fn call_client_method_internal(client: &Client, method: ClientMethod) -> Result { +pub(crate) async fn call_client_method_internal( + client: &Client, + method: ClientMethod, +) -> Result { let response = match method { ClientMethod::BuildAccountOutput { amount, @@ -174,19 +176,20 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM Response::Ok } ClientMethod::GetNode => Response::Node(client.get_node().await?), - ClientMethod::GetNetworkInfo => Response::NetworkInfo(client.get_network_info().await?), ClientMethod::GetNetworkId => Response::NetworkId(client.get_network_id().await?.to_string()), ClientMethod::GetBech32Hrp => Response::Bech32Hrp(client.get_bech32_hrp().await?), ClientMethod::GetProtocolParameters => Response::ProtocolParameters(client.get_protocol_parameters().await?), #[cfg(not(target_family = "wasm"))] ClientMethod::UnhealthyNodes => Response::UnhealthyNodes(client.unhealthy_nodes().await.into_iter().collect()), ClientMethod::GetHealth { url } => Response::Bool(client.get_health(&url).await?), - ClientMethod::GetNodeInfo { url, auth } => Response::NodeInfo(Client::get_node_info(&url, auth).await?), - ClientMethod::GetInfo => Response::Info(client.get_info().await?), + ClientMethod::GetInfo { url, auth } => Response::Info(Client::get_info(&url, auth).await?), + ClientMethod::GetNodeInfo => Response::NodeInfo(client.get_node_info().await?), + ClientMethod::GetNetworkMetrics => Response::NetworkMetrics(client.get_network_metrics().await?), + ClientMethod::GetRoutes => Response::Routes(client.get_routes().await?), ClientMethod::GetAccountCongestion { account_id, work_score } => { Response::Congestion(client.get_account_congestion(&account_id, work_score).await?) } - ClientMethod::GetRewards { output_id, slot_index } => { + ClientMethod::GetOutputManaRewards { output_id, slot_index } => { Response::ManaRewards(client.get_output_mana_rewards(&output_id, slot_index).await?) } ClientMethod::GetValidators { page_size, cursor } => { @@ -212,28 +215,31 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM .await?, ), ClientMethod::GetBlock { block_id } => Response::Block(BlockDto::from(&client.get_block(&block_id).await?)), + ClientMethod::GetBlockRaw { block_id } => Response::Raw(client.get_block_raw(&block_id).await?), ClientMethod::GetBlockMetadata { block_id } => { Response::BlockMetadata(client.get_block_metadata(&block_id).await?) } ClientMethod::GetBlockWithMetadata { block_id } => { Response::BlockWithMetadata(client.get_block_with_metadata(&block_id).await?) } - ClientMethod::GetBlockRaw { block_id } => Response::Raw(client.get_block_raw(&block_id).await?), - ClientMethod::GetOutput { output_id } => Response::OutputWithMetadataResponse( - client - .get_output_with_metadata(&output_id) - .await - .map(OutputWithMetadataResponse::from)?, - ), + ClientMethod::GetOutput { output_id } => Response::OutputResponse(client.get_output(&output_id).await?), + ClientMethod::GetOutputRaw { output_id } => Response::Raw(client.get_output_raw(&output_id).await?), ClientMethod::GetOutputMetadata { output_id } => { Response::OutputMetadata(client.get_output_metadata(&output_id).await?) } ClientMethod::GetOutputWithMetadata { output_id } => { Response::OutputWithMetadata(client.get_output_with_metadata(&output_id).await?) } + ClientMethod::GetOutputs { output_ids } => Response::Outputs(client.get_outputs(&output_ids).await?), + ClientMethod::GetOutputsIgnoreNotFound { output_ids } => { + Response::Outputs(client.get_outputs_ignore_not_found(&output_ids).await?) + } ClientMethod::GetIncludedBlock { transaction_id } => { Response::Block(BlockDto::from(&client.get_included_block(&transaction_id).await?)) } + ClientMethod::GetIncludedBlockRaw { transaction_id } => { + Response::Raw(client.get_included_block_raw(&transaction_id).await?) + } ClientMethod::GetIncludedBlockMetadata { transaction_id } => { Response::BlockMetadata(client.get_included_block_metadata(&transaction_id).await?) } @@ -241,29 +247,25 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM Response::TransactionMetadata(client.get_transaction_metadata(&transaction_id).await?) } ClientMethod::GetCommitment { commitment_id } => { - Response::SlotCommitment(client.get_slot_commitment_by_id(&commitment_id).await?) + Response::SlotCommitment(client.get_commitment(&commitment_id).await?) + } + ClientMethod::GetCommitmentRaw { commitment_id } => { + Response::Raw(client.get_commitment_raw(&commitment_id).await?) } ClientMethod::GetUtxoChanges { commitment_id } => { - Response::UtxoChanges(client.get_utxo_changes_by_slot_commitment_id(&commitment_id).await?) + Response::UtxoChanges(client.get_utxo_changes(&commitment_id).await?) } - ClientMethod::GetUtxoChangesFull { commitment_id } => Response::UtxoChangesFull( - client - .get_utxo_changes_full_by_slot_commitment_id(&commitment_id) - .await?, - ), - // TODO: this should be renamed to `GetCommitmentBySlot` - // https://github.com/iotaledger/iota-sdk/issues/1921 - ClientMethod::GetCommitmentByIndex { slot } => { - Response::SlotCommitment(client.get_slot_commitment_by_slot(slot).await?) - } - // TODO: this should be renamed to `GetUtxoChangesBySlot` - // https://github.com/iotaledger/iota-sdk/issues/1921 - ClientMethod::GetUtxoChangesByIndex { slot } => { + ClientMethod::GetUtxoChangesFull { commitment_id } => { + Response::UtxoChangesFull(client.get_utxo_changes_full(&commitment_id).await?) + } + ClientMethod::GetCommitmentBySlot { slot } => { + Response::SlotCommitment(client.get_commitment_by_slot(slot).await?) + } + ClientMethod::GetCommitmentBySlotRaw { slot } => Response::Raw(client.get_commitment_by_slot_raw(slot).await?), + ClientMethod::GetUtxoChangesBySlot { slot } => { Response::UtxoChanges(client.get_utxo_changes_by_slot(slot).await?) } - // TODO: this should be renamed to `GetUtxoChangesFullBySlot` - // https://github.com/iotaledger/iota-sdk/issues/1921 - ClientMethod::GetUtxoChangesFullByIndex { slot } => { + ClientMethod::GetUtxoChangesFullBySlot { slot } => { Response::UtxoChangesFull(client.get_utxo_changes_full_by_slot(slot).await?) } ClientMethod::OutputIds { query_parameters } => { @@ -294,24 +296,6 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM Response::OutputIdsResponse(client.nft_output_ids(query_parameters).await?) } ClientMethod::NftOutputId { nft_id } => Response::OutputId(client.nft_output_id(nft_id).await?), - ClientMethod::GetOutputs { output_ids } => { - let outputs_response = client - .get_outputs_with_metadata(&output_ids) - .await? - .iter() - .map(OutputWithMetadataResponse::from) - .collect(); - Response::Outputs(outputs_response) - } - ClientMethod::GetOutputsIgnoreErrors { output_ids } => { - let outputs_response = client - .get_outputs_with_metadata_ignore_not_found(&output_ids) - .await? - .iter() - .map(OutputWithMetadataResponse::from) - .collect(); - Response::Outputs(outputs_response) - } ClientMethod::FindBlocks { block_ids } => Response::Blocks( client .find_blocks(&block_ids) @@ -323,17 +307,8 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM ClientMethod::FindInputs { addresses, amount } => { Response::Inputs(client.find_inputs(addresses, amount).await?) } - ClientMethod::HexToBech32 { hex, bech32_hrp } => { - Response::Bech32Address(client.hex_to_bech32(&hex, bech32_hrp).await?) - } - ClientMethod::AccountIdToBech32 { account_id, bech32_hrp } => { - Response::Bech32Address(client.account_id_to_bech32(account_id, bech32_hrp).await?) - } - ClientMethod::NftIdToBech32 { nft_id, bech32_hrp } => { - Response::Bech32Address(client.nft_id_to_bech32(nft_id, bech32_hrp).await?) - } - ClientMethod::HexPublicKeyToBech32Address { hex, bech32_hrp } => { - Response::Bech32Address(client.hex_public_key_to_bech32_address(&hex, bech32_hrp).await?) + ClientMethod::AddressToBech32 { address, bech32_hrp } => { + Response::Bech32Address(client.address_to_bech32(address, bech32_hrp).await?) } ClientMethod::ComputeMinimumOutputAmount { output } => { let storage_score_params = client.get_storage_score_parameters().await?; diff --git a/bindings/core/src/method_handler/secret_manager.rs b/bindings/core/src/method_handler/secret_manager.rs index 15fb53f1cc..ca01227830 100644 --- a/bindings/core/src/method_handler/secret_manager.rs +++ b/bindings/core/src/method_handler/secret_manager.rs @@ -9,6 +9,7 @@ use iota_sdk::{ client::{ api::{GetAddressesOptions, PreparedTransactionData}, secret::{DowncastSecretManager, SecretManage, SignBlock}, + ClientError, }, types::{ block::{address::ToBech32Ext, core::UnsignedBlock, unlock::Unlock, BlockDto}, @@ -16,15 +17,15 @@ use iota_sdk::{ }, }; -use crate::{method::SecretManagerMethod, response::Response, Result}; +use crate::{method::SecretManagerMethod, response::Response}; /// Call a secret manager method. pub(crate) async fn call_secret_manager_method_internal( secret_manager: &S, method: SecretManagerMethod, -) -> Result +) -> Result where - iota_sdk::client::Error: From, + ClientError: From, { let response = match method { SecretManagerMethod::GenerateEd25519Addresses { @@ -40,7 +41,7 @@ where let addresses = secret_manager .generate_ed25519_addresses(coin_type, account_index, range, options) .await - .map_err(iota_sdk::client::Error::from)? + .map_err(ClientError::from)? .into_iter() .map(|a| a.to_bech32(bech32_hrp)) .collect(); @@ -59,7 +60,7 @@ where let addresses = secret_manager .generate_evm_addresses(coin_type, account_index, range, options) .await - .map_err(iota_sdk::client::Error::from)? + .map_err(ClientError::from)? .into_iter() .map(|a| prefix_hex::encode(a.as_ref())) .collect(); @@ -72,7 +73,7 @@ where } else if let Some(SecretManager::LedgerNano(secret_manager)) = secret_manager.downcast::() { secret_manager } else { - return Err(iota_sdk::client::Error::SecretManagerMismatch.into()); + return Err(ClientError::SecretManagerMismatch.into()); }; Response::LedgerNanoStatus(secret_manager.get_ledger_nano_status().await) } @@ -86,7 +87,7 @@ where &protocol_parameters, ) .await - .map_err(iota_sdk::client::Error::from)?; + .map_err(ClientError::from)?; Response::SignedTransaction(transaction.into()) } SecretManagerMethod::SignBlock { unsigned_block, chain } => Response::Block(BlockDto::from( @@ -102,7 +103,7 @@ where let unlock: Unlock = secret_manager .signature_unlock(&transaction_signing_hash, chain) .await - .map_err(iota_sdk::client::Error::from)?; + .map_err(ClientError::from)?; Response::SignatureUnlock(unlock) } @@ -111,7 +112,7 @@ where let signature = secret_manager .sign_ed25519(&msg, chain) .await - .map_err(iota_sdk::client::Error::from)?; + .map_err(ClientError::from)?; Response::Ed25519Signature(signature) } SecretManagerMethod::SignSecp256k1Ecdsa { message, chain } => { @@ -119,7 +120,7 @@ where let (public_key, signature) = secret_manager .sign_secp256k1_ecdsa(&msg, chain) .await - .map_err(iota_sdk::client::Error::from)?; + .map_err(ClientError::from)?; Response::Secp256k1EcdsaSignature { public_key: prefix_hex::encode(public_key.to_bytes()), signature: prefix_hex::encode(signature.to_bytes()), @@ -135,7 +136,7 @@ where secret_manager.store_mnemonic(mnemonic).await?; Response::Ok } else { - return Err(iota_sdk::client::Error::SecretManagerMismatch.into()); + return Err(ClientError::SecretManagerMismatch.into()); } } #[cfg(feature = "stronghold")] @@ -145,7 +146,7 @@ where } else if let Some(SecretManager::Stronghold(secret_manager)) = secret_manager.downcast::() { secret_manager } else { - return Err(iota_sdk::client::Error::SecretManagerMismatch.into()); + return Err(ClientError::SecretManagerMismatch.into()); }; stronghold.set_password(password).await?; Response::Ok @@ -157,7 +158,7 @@ where } else if let Some(SecretManager::Stronghold(secret_manager)) = secret_manager.downcast::() { secret_manager } else { - return Err(iota_sdk::client::Error::SecretManagerMismatch.into()); + return Err(ClientError::SecretManagerMismatch.into()); }; stronghold.change_password(password).await?; Response::Ok @@ -169,7 +170,7 @@ where } else if let Some(SecretManager::Stronghold(secret_manager)) = secret_manager.downcast::() { secret_manager } else { - return Err(iota_sdk::client::Error::SecretManagerMismatch.into()); + return Err(ClientError::SecretManagerMismatch.into()); }; stronghold.clear_key().await; Response::Ok diff --git a/bindings/core/src/method_handler/utils.rs b/bindings/core/src/method_handler/utils.rs index 3b55de23a7..8933f10600 100644 --- a/bindings/core/src/method_handler/utils.rs +++ b/bindings/core/src/method_handler/utils.rs @@ -1,37 +1,33 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crypto::keys::bip39::Mnemonic; +use crypto::{ + hashes::{blake2b::Blake2b256, Digest}, + keys::bip39::Mnemonic, +}; use iota_sdk::{ - client::{hex_public_key_to_bech32_address, hex_to_bech32, verify_mnemonic, Client}, + client::{verify_mnemonic, Client}, types::{ block::{ address::{AccountAddress, Address, ToBech32Ext}, input::UtxoInput, - output::{AccountId, FoundryId, MinimumOutputAmount, NftId, Output, OutputId, TokenId}, + output::{FoundryId, MinimumOutputAmount, Output, OutputId, TokenId}, payload::{signed_transaction::Transaction, SignedTransactionPayload}, semantic::SemanticValidationContext, - Block, Error, + signature::SignatureError, + Block, }, TryFromDto, }, }; use packable::PackableExt; -use crate::{method::UtilsMethod, response::Response, Result}; +use crate::{method::UtilsMethod, response::Response}; /// Call a utils method. -pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result { +pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result { let response = match method { - UtilsMethod::Bech32ToHex { bech32 } => Response::Bech32ToHex(Client::bech32_to_hex(bech32)?), - UtilsMethod::HexToBech32 { hex, bech32_hrp } => Response::Bech32Address(hex_to_bech32(&hex, bech32_hrp)?), - UtilsMethod::AccountIdToBech32 { account_id, bech32_hrp } => { - Response::Bech32Address(account_id.to_bech32(bech32_hrp)) - } - UtilsMethod::NftIdToBech32 { nft_id, bech32_hrp } => Response::Bech32Address(nft_id.to_bech32(bech32_hrp)), - UtilsMethod::HexPublicKeyToBech32Address { hex, bech32_hrp } => { - Response::Bech32Address(hex_public_key_to_bech32_address(&hex, bech32_hrp)?) - } + UtilsMethod::AddressToBech32 { address, bech32_hrp } => Response::Bech32Address(address.to_bech32(bech32_hrp)), UtilsMethod::ParseBech32Address { address } => Response::ParsedBech32Address(address.into_inner()), UtilsMethod::IsAddressValid { address } => Response::Bool(Address::is_valid_bech32(&address)), UtilsMethod::GenerateMnemonic => Response::GeneratedMnemonic(Client::generate_mnemonic()?.to_string()), @@ -50,7 +46,7 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result Response::AccountId(AccountId::from(&output_id)), + UtilsMethod::Blake2b256Hash { bytes } => Response::Hash(prefix_hex::encode(Blake2b256::digest(bytes).to_vec())), UtilsMethod::ComputeFoundryId { account_id, serial_number, @@ -60,7 +56,6 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result Response::NftId(NftId::from(&output_id)), UtilsMethod::ComputeOutputId { id, index } => Response::OutputId(OutputId::new(id, index)), UtilsMethod::ComputeTokenId { account_id, @@ -87,7 +82,11 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result { let message: Vec = prefix_hex::decode(message)?; - Response::Bool(signature.try_verify(&message).map_err(Error::from)?) + Response::Bool( + signature + .try_verify(&message) + .map_err(iota_sdk::client::ClientError::from)?, + ) } UtilsMethod::VerifySecp256k1EcdsaSignature { public_key, @@ -96,9 +95,11 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result { use crypto::signatures::secp256k1_ecdsa; let public_key = prefix_hex::decode(public_key)?; - let public_key = secp256k1_ecdsa::PublicKey::try_from_bytes(&public_key).map_err(Error::from)?; + let public_key = + secp256k1_ecdsa::PublicKey::try_from_bytes(&public_key).map_err(SignatureError::PublicKeyBytes)?; let signature = prefix_hex::decode(signature)?; - let signature = secp256k1_ecdsa::Signature::try_from_bytes(&signature).map_err(Error::from)?; + let signature = + secp256k1_ecdsa::Signature::try_from_bytes(&signature).map_err(SignatureError::SignatureBytes)?; let message: Vec = prefix_hex::decode(message)?; Response::Bool(public_key.verify_keccak256(&signature, &message)) } @@ -122,11 +123,12 @@ pub(crate) fn call_utils_method_internal(method: UtilsMethod) -> Result Result { + Response::ProtocolParameters(iota_sdk::types::block::protocol::iota_mainnet_protocol_parameters().clone()) + } + UtilsMethod::ShimmerMainnetProtocolParameters => Response::ProtocolParameters( + iota_sdk::types::block::protocol::shimmer_mainnet_protocol_parameters().clone(), + ), }; Ok(response) diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index 17979f26e6..cc7086f978 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -6,22 +6,22 @@ use std::time::Duration; use crypto::signatures::ed25519::PublicKey; use iota_sdk::{ client::api::{PreparedTransactionData, SignedTransactionData, SignedTransactionDataDto}, - types::{ - block::{address::ToBech32Ext, output::feature::BlockIssuerKeySource}, - TryFromDto, - }, + types::{block::output::feature::BlockIssuerKeySource, TryFromDto}, wallet::{types::TransactionWithMetadataDto, Wallet}, }; use crate::{method::WalletMethod, response::Response}; /// Call a wallet method. -pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletMethod) -> crate::Result { +pub(crate) async fn call_wallet_method_internal( + wallet: &Wallet, + method: WalletMethod, +) -> Result { let response = match method { - WalletMethod::Accounts => Response::OutputsData(wallet.data().await.accounts().cloned().collect()), + WalletMethod::Accounts => Response::OutputsData(wallet.ledger().await.accounts().cloned().collect()), #[cfg(feature = "stronghold")] - WalletMethod::Backup { destination, password } => { - wallet.backup(destination, password).await?; + WalletMethod::BackupToStrongholdSnapshot { destination, password } => { + wallet.backup_to_stronghold_snapshot(destination, password).await?; Response::Ok } #[cfg(feature = "stronghold")] @@ -45,14 +45,14 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM Response::Bool(is_available) } #[cfg(feature = "stronghold")] - WalletMethod::RestoreBackup { + WalletMethod::RestoreFromStrongholdSnapshot { source, password, ignore_if_coin_type_mismatch, ignore_if_bech32_mismatch, } => { wallet - .restore_backup( + .restore_from_stronghold_snapshot( source, password, ignore_if_coin_type_mismatch, @@ -70,23 +70,6 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let ledger_nano_status = wallet.get_ledger_nano_status().await?; Response::LedgerNanoStatus(ledger_nano_status) } - WalletMethod::GenerateEd25519Address { - account_index, - address_index, - options, - bech32_hrp, - } => { - let address = wallet - .generate_ed25519_address(account_index, address_index, options) - .await?; - - let bech32_hrp = match bech32_hrp { - Some(bech32_hrp) => bech32_hrp, - None => *wallet.address().await.hrp(), - }; - - Response::Bech32Address(address.to_bech32(bech32_hrp)) - } #[cfg(feature = "stronghold")] WalletMethod::SetStrongholdPassword { password } => { wallet.set_stronghold_password(password).await?; @@ -135,26 +118,19 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let output_ids = wallet.claimable_outputs(outputs_to_claim).await?; Response::OutputIds(output_ids) } - WalletMethod::ClaimOutputs { output_ids_to_claim } => { - let transaction = wallet.claim_outputs(output_ids_to_claim.to_vec()).await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } // #[cfg(feature = "participation")] // WalletMethod::DeregisterParticipationEvent { event_id } => { // wallet.deregister_participation_event(&event_id).await?; // Response::Ok // } - WalletMethod::GetAddress => { - let address = wallet.address().await; - Response::Address(address) - } + WalletMethod::GetAddress => Response::Address(wallet.address().await), WalletMethod::GetBalance => Response::Balance(wallet.balance().await?), WalletMethod::GetFoundryOutput { token_id } => { let output = wallet.get_foundry_output(token_id).await?; Response::Output(output) } WalletMethod::GetIncomingTransaction { transaction_id } => wallet - .data() + .ledger() .await .get_incoming_transaction(&transaction_id) .map_or_else( @@ -162,7 +138,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM |transaction| Response::Transaction(Some(Box::new(TransactionWithMetadataDto::from(transaction)))), ), WalletMethod::GetOutput { output_id } => { - Response::OutputData(wallet.data().await.get_output(&output_id).cloned().map(Box::new)) + Response::OutputData(wallet.ledger().await.get_output(&output_id).cloned().map(Box::new)) } // #[cfg(feature = "participation")] // WalletMethod::GetParticipationEvent { event_id } => { @@ -191,7 +167,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM // } WalletMethod::GetTransaction { transaction_id } => Response::Transaction( wallet - .data() + .ledger() .await .get_transaction(&transaction_id) .map(TransactionWithMetadataDto::from) @@ -213,7 +189,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM } => { let data = if let Some(public_key_str) = public_key { let public_key = PublicKey::try_from_bytes(prefix_hex::decode(public_key_str)?) - .map_err(iota_sdk::wallet::Error::from)?; + .map_err(iota_sdk::wallet::WalletError::from)?; wallet .prepare_implicit_account_transition(&output_id, public_key) .await? @@ -227,11 +203,11 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM Response::PreparedTransaction(data) } WalletMethod::ImplicitAccounts => { - Response::OutputsData(wallet.data().await.implicit_accounts().cloned().collect()) + Response::OutputsData(wallet.ledger().await.implicit_accounts().cloned().collect()) } WalletMethod::IncomingTransactions => Response::Transactions( wallet - .data() + .ledger() .await .incoming_transactions() .values() @@ -239,16 +215,16 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM .collect(), ), WalletMethod::Outputs { filter_options } => { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; Response::OutputsData(if let Some(filter) = filter_options { - wallet_data.filtered_outputs(filter).cloned().collect() + wallet_ledger.filtered_outputs(filter).cloned().collect() } else { - wallet_data.outputs().values().cloned().collect() + wallet_ledger.outputs().values().cloned().collect() }) } WalletMethod::PendingTransactions => Response::Transactions( wallet - .data() + .ledger() .await .pending_transactions() .map(TransactionWithMetadataDto::from) @@ -315,6 +291,10 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let data = wallet.prepare_send(params, options).await?; Response::PreparedTransaction(data) } + WalletMethod::PrepareSendMana { params, options } => { + let data = wallet.prepare_send_mana(params, options).await?; + Response::PreparedTransaction(data) + } WalletMethod::PrepareSendNativeTokens { params, options } => { let data = wallet.prepare_send_native_tokens(params.clone(), options).await?; Response::PreparedTransaction(data) @@ -343,12 +323,15 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM WalletMethod::PrepareExtendStaking { account_id, additional_epochs, + options, } => { - let data = wallet.prepare_extend_staking(account_id, additional_epochs).await?; + let data = wallet + .prepare_extend_staking(account_id, additional_epochs, options) + .await?; Response::PreparedTransaction(data) } - WalletMethod::PrepareEndStaking { account_id } => { - let data = wallet.prepare_end_staking(account_id).await?; + WalletMethod::PrepareEndStaking { account_id, options } => { + let data = wallet.prepare_end_staking(account_id, options).await?; Response::PreparedTransaction(data) } WalletMethod::AnnounceCandidacy { account_id } => { @@ -359,8 +342,8 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM // let data = wallet.prepare_stop_participating(event_id).await?; // Response::PreparedTransaction(data) // } - WalletMethod::PrepareTransaction { outputs, options } => { - let data = wallet.prepare_transaction(outputs, options).await?; + WalletMethod::PrepareSendOutputs { outputs, options } => { + let data = wallet.prepare_send_outputs(outputs, options).await?; Response::PreparedTransaction(data) } // #[cfg(feature = "participation")] @@ -378,26 +361,10 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM interval, max_attempts, } => { - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction_id, interval, max_attempts) .await?; - Response::BlockId(block_id) - } - WalletMethod::Send { - amount, - address, - options, - } => { - let transaction = wallet.send(amount, address, options).await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } - WalletMethod::SendWithParams { params, options } => { - let transaction = wallet.send_with_params(params, options).await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) - } - WalletMethod::SendOutputs { outputs, options } => { - let transaction = wallet.send_outputs(outputs, options).await?; - Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) + Response::Ok } WalletMethod::SetAlias { alias } => { wallet.set_alias(&alias).await?; @@ -444,7 +411,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM WalletMethod::Sync { options } => Response::Balance(wallet.sync(options).await?), WalletMethod::Transactions => Response::Transactions( wallet - .data() + .ledger() .await .transactions() .values() @@ -452,11 +419,11 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM .collect(), ), WalletMethod::UnspentOutputs { filter_options } => { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; Response::OutputsData(if let Some(filter) = filter_options { - wallet_data.filtered_unspent_outputs(filter).cloned().collect() + wallet_ledger.filtered_unspent_outputs(filter).cloned().collect() } else { - wallet_data.unspent_outputs().values().cloned().collect() + wallet_ledger.unspent_outputs().values().cloned().collect() }) } }; diff --git a/bindings/core/src/panic.rs b/bindings/core/src/panic.rs index 5e46d78d10..a7a7f9df37 100644 --- a/bindings/core/src/panic.rs +++ b/bindings/core/src/panic.rs @@ -9,7 +9,7 @@ use std::{ use backtrace::Backtrace; use futures::{Future, FutureExt}; -use crate::{response::Response, Result}; +use crate::response::Response; fn panic_to_response_message(panic: Box) -> Response { let msg = panic.downcast_ref::().map_or_else( @@ -27,9 +27,9 @@ fn panic_to_response_message(panic: Box) -> Response { } #[cfg(not(target_family = "wasm"))] -pub(crate) async fn convert_async_panics(f: impl FnOnce() -> F + Send) -> Result +pub(crate) async fn convert_async_panics(f: impl FnOnce() -> F + Send) -> Result where - F: Future> + Send, + F: Future> + Send, { AssertUnwindSafe(f()) .catch_unwind() @@ -39,9 +39,9 @@ where #[cfg(target_family = "wasm")] #[allow(clippy::future_not_send)] -pub(crate) async fn convert_async_panics(f: impl FnOnce() -> F) -> Result +pub(crate) async fn convert_async_panics(f: impl FnOnce() -> F) -> Result where - F: Future>, + F: Future>, { AssertUnwindSafe(f()) .catch_unwind() @@ -49,7 +49,7 @@ where .unwrap_or_else(|panic| Ok(panic_to_response_message(panic))) } -pub(crate) fn convert_panics Result>(f: F) -> Result { +pub(crate) fn convert_panics Result>(f: F) -> Result { match catch_unwind(AssertUnwindSafe(f)) { Ok(result) => result, Err(panic) => Ok(panic_to_response_message(panic)), diff --git a/bindings/core/src/response.rs b/bindings/core/src/response.rs index 7e2d85b9f1..928e877dfc 100644 --- a/bindings/core/src/response.rs +++ b/bindings/core/src/response.rs @@ -10,28 +10,25 @@ use iota_sdk::client::secret::LedgerNanoStatus; use iota_sdk::{ client::{ api::{PreparedTransactionData, SignedTransactionDataDto}, + node_api::core::routes::NodeInfoResponse, node_manager::node::Node, - NetworkInfo, NodeInfoWrapper, }, types::{ api::{ core::{ - BlockMetadataResponse, BlockWithMetadataResponse, CommitteeResponse, CongestionResponse, - InfoResponse as NodeInfo, IssuanceBlockHeaderResponse, ManaRewardsResponse, OutputWithMetadataResponse, - TransactionMetadataResponse, UtxoChangesFullResponse, UtxoChangesResponse, ValidatorResponse, - ValidatorsResponse, + BlockMetadataResponse, BlockWithMetadataResponse, CommitteeResponse, CongestionResponse, InfoResponse, + IssuanceBlockHeaderResponse, ManaRewardsResponse, NetworkMetricsResponse, OutputResponse, + OutputWithMetadataResponse, RoutesResponse, TransactionMetadataResponse, UtxoChangesFullResponse, + UtxoChangesResponse, ValidatorResponse, ValidatorsResponse, }, plugins::indexer::OutputIdsResponse, }, block::{ address::{Address, Bech32Address, Hrp}, input::UtxoInput, - output::{ - AccountId, DecayedMana, FoundryId, NftId, Output, OutputId, OutputMetadata, OutputWithMetadata, TokenId, - }, + output::{DecayedMana, FoundryId, Output, OutputId, OutputMetadata, TokenId}, payload::{dto::SignedTransactionPayloadDto, signed_transaction::TransactionId}, protocol::ProtocolParameters, - semantic::TransactionFailureReason, signature::Ed25519Signature, slot::{SlotCommitment, SlotCommitmentId}, unlock::Unlock, @@ -73,9 +70,6 @@ pub enum Response { /// - [`GetNode`](crate::method::ClientMethod::GetNode) Node(Node), /// Response for: - /// - [`GetNetworkInfo`](crate::method::ClientMethod::GetNetworkInfo) - NetworkInfo(NetworkInfo), - /// Response for: /// - [`GetNetworkId`](crate::method::ClientMethod::GetNetworkId) NetworkId(String), /// Response for: @@ -102,11 +96,17 @@ pub enum Response { #[cfg(not(target_family = "wasm"))] UnhealthyNodes(HashSet), /// Response for: + /// - [`GetInfo`](crate::method::ClientMethod::GetInfo) + Info(InfoResponse), + /// Response for: /// - [`GetNodeInfo`](crate::method::ClientMethod::GetNodeInfo) - NodeInfo(NodeInfo), + NodeInfo(NodeInfoResponse), /// Response for: - /// - [`GetInfo`](crate::method::ClientMethod::GetInfo) - Info(NodeInfoWrapper), + /// - [`GetNetworkMetrics`](crate::method::ClientMethod::GetNetworkMetrics) + NetworkMetrics(NetworkMetricsResponse), + /// Response for: + /// - [`GetRoutes`](crate::method::ClientMethod::GetRoutes) + Routes(RoutesResponse), /// Response for: /// - [`GetAccountCongestion`](crate::method::ClientMethod::GetAccountCongestion) Congestion(CongestionResponse), @@ -156,21 +156,25 @@ pub enum Response { BlockWithMetadata(BlockWithMetadataResponse), /// Response for: /// - [`GetBlockRaw`](crate::method::ClientMethod::GetBlockRaw) + /// - [`GetOutputRaw`](crate::method::ClientMethod::GetOutputRaw) + /// - [`GetIncludedBlockRaw`](crate::method::ClientMethod::GetIncludedBlockRaw) + /// - [`GetCommitmentRaw`](crate::method::ClientMethod::GetCommitmentRaw) + /// - [`GetCommitmentBySlotRaw`](crate::method::ClientMethod::GetCommitmentBySlotRaw) /// - [`BlockBytes`](crate::method::UtilsMethod::BlockBytes) Raw(Vec), /// Response for: /// - [`GetOutput`](crate::method::ClientMethod::GetOutput) - OutputWithMetadataResponse(OutputWithMetadataResponse), + OutputResponse(OutputResponse), /// Response for: /// - [`GetOutputMetadata`](crate::method::ClientMethod::GetOutputMetadata) OutputMetadata(OutputMetadata), /// Response for: /// - [`GetOutputWithMetadata`](crate::method::ClientMethod::GetOutputWithMetadata) - OutputWithMetadata(OutputWithMetadata), + OutputWithMetadata(OutputWithMetadataResponse), /// Response for: /// - [`GetOutputs`](crate::method::ClientMethod::GetOutputs) - /// - [`GetOutputsIgnoreErrors`](crate::method::ClientMethod::GetOutputsIgnoreErrors) - Outputs(Vec), + /// - [`GetOutputsIgnoreNotFound`](crate::method::ClientMethod::GetOutputsIgnoreNotFound) + Outputs(Vec), /// Response for: /// - [`AccountOutputId`](crate::method::ClientMethod::AccountOutputId) /// - [`AnchorOutputId`](crate::method::ClientMethod::AnchorOutputId) @@ -197,9 +201,6 @@ pub enum Response { /// [`OutputIdToUtxoInput`](crate::method::UtilsMethod::OutputIdToUtxoInput) Input(UtxoInput), /// Response for: - /// - [`Bech32ToHex`](crate::method::UtilsMethod::Bech32ToHex) - Bech32ToHex(String), - /// Response for: /// - [`ParseBech32Address`](crate::method::UtilsMethod::ParseBech32Address) ParsedBech32Address(Address), /// Response for: @@ -212,29 +213,18 @@ pub enum Response { /// - [`TransactionId`](crate::method::UtilsMethod::TransactionId) TransactionId(TransactionId), /// Response for: - /// - [`ComputeAccountId`](crate::method::UtilsMethod::ComputeAccountId) - AccountId(AccountId), - /// Response for: - /// - [`ComputeNftId`](crate::method::UtilsMethod::ComputeNftId) - NftId(NftId), - /// Response for: /// - [`ComputeFoundryId`](crate::method::UtilsMethod::ComputeFoundryId) FoundryId(FoundryId), /// Response for: + /// - [`Blake2b256Hash`](crate::method::UtilsMethod::Blake2b256Hash) /// - [`TransactionSigningHash`](crate::method::UtilsMethod::TransactionSigningHash) Hash(String), - /// Response for [`GetNodeInfo`](crate::method::ClientMethod::GetNodeInfo) - NodeInfoWrapper(NodeInfoWrapper), - /// Response for [`Bech32ToHex`](crate::method::UtilsMethod::Bech32ToHex) - HexAddress(String), /// Response for [`OutputHexBytes`](crate::method::UtilsMethod::OutputHexBytes) HexBytes(String), /// Response for [`CallPluginRoute`](crate::method::ClientMethod::CallPluginRoute) CustomJson(serde_json::Value), /// Response for [`ComputeSlotCommitmentId`](crate::method::UtilsMethod::ComputeSlotCommitmentId) SlotCommitmentId(SlotCommitmentId), - /// Response for [`VerifyTransactionSemantic`](crate::method::UtilsMethod::VerifyTransactionSemantic). - TransactionFailureReason(Option), // Responses in client and wallet /// Response for: @@ -246,10 +236,8 @@ pub enum Response { /// - [`PrepareOutput`](crate::method::WalletMethod::PrepareOutput) Output(Output), /// Response for: - /// - [`AccountIdToBech32`](crate::method::ClientMethod::AccountIdToBech32) - /// - [`HexPublicKeyToBech32Address`](crate::method::ClientMethod::HexPublicKeyToBech32Address) - /// - [`HexToBech32`](crate::method::ClientMethod::HexToBech32) - /// - [`NftIdToBech32`](crate::method::ClientMethod::NftIdToBech32) + /// - [`AddressToBech32`](crate::method::ClientMethod::AddressToBech32) + /// - [`AddressToBech32`](crate::method::UtilsMethod::AddressToBech32) /// - [`ImplicitAccountCreationAddress`](crate::method::WalletMethod::ImplicitAccountCreationAddress) Bech32Address(Bech32Address), /// - [`Faucet`](crate::method::ClientMethod::RequestFundsFromFaucet) @@ -266,7 +254,6 @@ pub enum Response { /// - [`BlockId`](crate::method::UtilsMethod::BlockId) /// - [`PostBlock`](crate::method::ClientMethod::PostBlock) /// - [`PostBlockRaw`](crate::method::ClientMethod::PostBlockRaw) - /// - [`WaitForTransactionAcceptance`](crate::method::WalletMethod::WaitForTransactionAcceptance) BlockId(BlockId), /// Response for: /// - [`GetHealth`](crate::method::ClientMethod::GetHealth) @@ -275,12 +262,12 @@ pub enum Response { /// - [`VerifySecp256k1EcdsaSignature`](crate::method::UtilsMethod::VerifySecp256k1EcdsaSignature) Bool(bool), /// Response for: - /// - [`Backup`](crate::method::WalletMethod::Backup), + /// - [`BackupToStrongholdSnapshot`](crate::method::WalletMethod::BackupToStrongholdSnapshot), /// - [`ClearListeners`](crate::method::WalletMethod::ClearListeners) /// - [`ClearStrongholdPassword`](crate::method::WalletMethod::ClearStrongholdPassword), /// - [`DeregisterParticipationEvent`](crate::method::WalletMethod::DeregisterParticipationEvent), /// - [`EmitTestEvent`](crate::method::WalletMethod::EmitTestEvent), - /// - [`RestoreBackup`](crate::method::WalletMethod::RestoreBackup), + /// - [`RestoreFromStrongholdSnapshot`](crate::method::WalletMethod::RestoreFromStrongholdSnapshot), /// - [`SetAlias`](crate::method::WalletMethod::SetAlias), /// - [`SetClientOptions`](crate::method::WalletMethod::SetClientOptions), /// - [`SetDefaultSyncOptions`](crate::method::WalletMethod::SetDefaultSyncOptions), @@ -290,6 +277,7 @@ pub enum Response { /// - [`StoreMnemonic`](crate::method::WalletMethod::StoreMnemonic), /// - [`StopBackgroundSync`](crate::method::WalletMethod::StopBackgroundSync), /// - [`VerifyTransactionSyntax`](crate::method::UtilsMethod::VerifyTransactionSyntax), + /// - [`WaitForTransactionAcceptance`](crate::method::WalletMethod::WaitForTransactionAcceptance) Ok, /// Response for any method that returns an error. Error(Error), @@ -329,11 +317,12 @@ pub enum Response { /// - [`PrepareMeltNativeToken`](crate::method::WalletMethod::PrepareMeltNativeToken) /// - [`PrepareMintNativeToken`](crate::method::WalletMethod::PrepareMintNativeToken), /// - [`PrepareMintNfts`](crate::method::WalletMethod::PrepareMintNfts), - /// - [`PrepareSend`](crate::method::WalletMethod::PrepareSend), + /// - [`PrepareSendMana`](crate::method::WalletMethod::PrepareSendMana), /// - [`PrepareSendNativeTokens`](crate::method::WalletMethod::PrepareSendNativeTokens), /// - [`PrepareSendNft`](crate::method::WalletMethod::PrepareSendNft), + /// - [`PrepareSend`](crate::method::WalletMethod::PrepareSend), /// - [`PrepareStopParticipating`](crate::method::WalletMethod::PrepareStopParticipating) - /// - [`PrepareTransaction`](crate::method::WalletMethod::PrepareTransaction) + /// - [`PrepareSendOutputs`](crate::method::WalletMethod::PrepareSendOutputs) /// - [`PrepareVote`](crate::method::WalletMethod::PrepareVote) /// - [`PrepareImplicitAccountTransition`](crate::method::WalletMethod::PrepareImplicitAccountTransition) PreparedTransaction(PreparedTransactionData), @@ -360,9 +349,6 @@ pub enum Response { /// - [`Sync`](crate::method::WalletMethod::Sync) Balance(Balance), /// Response for: - /// - [`ClaimOutputs`](crate::method::WalletMethod::ClaimOutputs) - /// - [`Send`](crate::method::WalletMethod::Send) - /// - [`SendOutputs`](crate::method::WalletMethod::SendOutputs) /// - [`SignAndSubmitTransaction`](crate::method::WalletMethod::SignAndSubmitTransaction) /// - [`SubmitAndStoreTransaction`](crate::method::WalletMethod::SubmitAndStoreTransaction) SentTransaction(TransactionWithMetadataDto), diff --git a/bindings/core/tests/combined.rs b/bindings/core/tests/combined.rs index 6c2e89254a..06e4822fcd 100644 --- a/bindings/core/tests/combined.rs +++ b/bindings/core/tests/combined.rs @@ -17,14 +17,14 @@ use iota_sdk::{ }, }; use iota_sdk_bindings_core::{ - call_client_method, call_secret_manager_method, CallMethod, ClientMethod, Response, Result, SecretManagerMethod, + call_client_method, call_secret_manager_method, CallMethod, ClientMethod, Error, Response, SecretManagerMethod, WalletMethod, WalletOptions, }; use pretty_assertions::assert_eq; #[cfg(feature = "storage")] #[tokio::test] -async fn create_wallet() -> Result<()> { +async fn create_wallet() -> Result<(), Error> { let storage_path = "test-storage/create_wallet"; std::fs::remove_dir_all(storage_path).ok(); @@ -41,7 +41,12 @@ async fn create_wallet() -> Result<()> { let wallet = WalletOptions::default() .with_storage_path(storage_path.to_string()) - .with_client_options(ClientBuilder::new().from_json(client_options).unwrap()) + .with_client_options( + ClientBuilder::new() + .from_json(client_options) + .unwrap() + .with_protocol_parameters(iota_sdk::types::block::protocol::iota_mainnet_protocol_parameters().clone()), + ) .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .with_secret_manager(serde_json::from_str::(secret_manager).unwrap()) .build() @@ -62,7 +67,7 @@ async fn create_wallet() -> Result<()> { #[cfg(feature = "storage")] #[tokio::test] -async fn client_from_wallet() -> Result<()> { +async fn client_from_wallet() -> Result<(), Error> { let storage_path = "test-storage/client_from_wallet"; std::fs::remove_dir_all(storage_path).ok(); @@ -79,7 +84,12 @@ async fn client_from_wallet() -> Result<()> { let wallet = WalletOptions::default() .with_storage_path(storage_path.to_string()) - .with_client_options(ClientBuilder::new().from_json(client_options).unwrap()) + .with_client_options( + ClientBuilder::new() + .from_json(client_options) + .unwrap() + .with_protocol_parameters(iota_sdk::types::block::protocol::iota_mainnet_protocol_parameters().clone()), + ) .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .with_secret_manager(serde_json::from_str::(secret_manager).unwrap()) .build() diff --git a/bindings/core/tests/secrets_debug.rs b/bindings/core/tests/secrets_debug.rs index 6f57d1eba5..84bf98556e 100644 --- a/bindings/core/tests/secrets_debug.rs +++ b/bindings/core/tests/secrets_debug.rs @@ -26,6 +26,6 @@ fn method_interface_secrets_debug() { let wallet_options = WalletOptions::default().with_secret_manager(SecretManagerDto::Placeholder); assert_eq!( format!("{:?}", wallet_options), - "WalletOptions { address: None, alias: None, bip_path: None, client_options: None, secret_manager: Some(), storage_path: None }" + "WalletOptions { address: None, bip_path: None, alias: None, client_options: None, secret_manager: Some(), storage_path: None }" ); } diff --git a/bindings/core/tests/secrets_manager.rs b/bindings/core/tests/secrets_manager.rs index 2192cbae09..adbdf8e575 100644 --- a/bindings/core/tests/secrets_manager.rs +++ b/bindings/core/tests/secrets_manager.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::client::{api::GetAddressesOptions, constants::ETHER_COIN_TYPE, secret::SecretManager}; -use iota_sdk_bindings_core::{call_secret_manager_method, Response, Result, SecretManagerMethod}; +use iota_sdk_bindings_core::{call_secret_manager_method, Error, Response, SecretManagerMethod}; use pretty_assertions::assert_eq; #[tokio::test] -async fn generate_ed25519_addresses() -> Result<()> { +async fn generate_ed25519_addresses() -> Result<(), Error> { let secret_manager = SecretManager::try_from_mnemonic( "endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river".to_owned(), )?; @@ -28,7 +28,7 @@ async fn generate_ed25519_addresses() -> Result<()> { } #[tokio::test] -async fn generate_evm_addresses() -> Result<()> { +async fn generate_evm_addresses() -> Result<(), Error> { let secret_manager = SecretManager::try_from_mnemonic( "endorse answer radar about source reunion marriage tag sausage weekend frost daring base attack because joke dream slender leisure group reason prepare broken river".to_owned(), )?; diff --git a/bindings/core/tests/serialize_error.rs b/bindings/core/tests/serialize_error.rs index b34229c024..a83d4adb9a 100644 --- a/bindings/core/tests/serialize_error.rs +++ b/bindings/core/tests/serialize_error.rs @@ -3,8 +3,8 @@ use crypto::keys::bip44::Bip44; use iota_sdk::{ - client::{constants::SHIMMER_COIN_TYPE, Error as ClientError}, - wallet::Error as WalletError, + client::{constants::SHIMMER_COIN_TYPE, ClientError}, + wallet::WalletError, }; use iota_sdk_bindings_core::Error; use pretty_assertions::assert_eq; diff --git a/bindings/core/tests/utils.rs b/bindings/core/tests/utils.rs index 9b03fc45c8..358041f6c6 100644 --- a/bindings/core/tests/utils.rs +++ b/bindings/core/tests/utils.rs @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::types::block::address::{Bech32Address, Hrp}; -use iota_sdk_bindings_core::{call_utils_method, Response, Result, UtilsMethod}; +use iota_sdk_bindings_core::{call_utils_method, Response, UtilsMethod}; use pretty_assertions::assert_eq; #[tokio::test] -async fn utils() -> Result<()> { +async fn utils() -> Result<(), Box> { let response = call_utils_method(UtilsMethod::GenerateMnemonic); match response { Response::GeneratedMnemonic(mnemonic) => println!("{:?}", serde_json::to_string(&mnemonic)?), @@ -15,15 +15,15 @@ async fn utils() -> Result<()> { let bech32_address = Bech32Address::try_from_str("rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy")?; - let method = UtilsMethod::Bech32ToHex { - bech32: bech32_address.clone(), + let method = UtilsMethod::ParseBech32Address { + address: bech32_address.clone(), }; let response = call_utils_method(method); match response { - Response::Bech32ToHex(hex) => { - match call_utils_method(UtilsMethod::HexToBech32 { - hex, + Response::ParsedBech32Address(address) => { + match call_utils_method(UtilsMethod::AddressToBech32 { + address, bech32_hrp: Hrp::from_str_unchecked("rms"), }) { Response::Bech32Address(address_bech32) => { diff --git a/bindings/nodejs/README.md b/bindings/nodejs/README.md index 1b3b99bdf4..acdb5907bb 100644 --- a/bindings/nodejs/README.md +++ b/bindings/nodejs/README.md @@ -71,7 +71,7 @@ Prebuild requires that the binary is in `build/Release` as though it was built w The following example creates a [`Client`](https://wiki.iota.org/shimmer/iota-sdk/references/nodejs/classes/Client/) instance connected to the [Shimmer Testnet](https://api.testnet.shimmer.network), and retrieves the node's information by -calling [`Client.getInfo()`](https://wiki.iota.org/shimmer/iota-sdk/references/nodejs/classes/Client/#getinfo), +calling [`Client.getNodeInfo()`](https://wiki.iota.org/shimmer/iota-sdk/references/nodejs/classes/Client/#getnodeinfo), and then print the node's information. ```javascript @@ -85,7 +85,7 @@ async function run() { }); try { - const nodeInfo = await client.getInfo(); + const nodeInfo = await client.getNodeInfo(); console.log('Node info: ', nodeInfo); } catch (error) { console.error('Error: ', error); diff --git a/bindings/nodejs/examples/client/07-get-block-data.ts b/bindings/nodejs/examples/client/07-get-block-data.ts index 7302d6d9e4..bcd25d2373 100644 --- a/bindings/nodejs/examples/client/07-get-block-data.ts +++ b/bindings/nodejs/examples/client/07-get-block-data.ts @@ -23,7 +23,7 @@ async function run() { try { // Fetch a block ID from the node. - const blockIds = await client.getTips(); + const blockIds = (await client.getIssuance()).strongParents; console.log('Block IDs:', blockIds, '\n'); // Get the metadata for the block. diff --git a/bindings/nodejs/examples/client/10-mqtt.ts b/bindings/nodejs/examples/client/10-mqtt.ts index 79b4133e3a..3d77ac5c57 100644 --- a/bindings/nodejs/examples/client/10-mqtt.ts +++ b/bindings/nodejs/examples/client/10-mqtt.ts @@ -24,7 +24,7 @@ async function run() { }); // Array of topics to subscribe to - // Topics can be found here https://studio.asyncapi.com/?url=https://raw.githubusercontent.com/iotaledger/tips/main/tips/TIP-0028/event-api.yml + // Topics can be found here https://studio.asyncapi.com/?url=https://raw.githubusercontent.com/iotaledger/tips/tip48/tips/TIP-0048/asyncapi3.yaml const topics = ['blocks']; const callback = function (error: Error, data: string) { diff --git a/bindings/nodejs/examples/client/11-build-output.ts b/bindings/nodejs/examples/client/11-build-output.ts index fb44f9e71f..4af2342088 100644 --- a/bindings/nodejs/examples/client/11-build-output.ts +++ b/bindings/nodejs/examples/client/11-build-output.ts @@ -11,7 +11,6 @@ import { SenderFeature, TagFeature, StorageDepositReturnUnlockCondition, - Ed25519Address, ExpirationUnlockCondition, TimelockUnlockCondition, utf8ToHex, @@ -36,12 +35,12 @@ async function run() { }); try { - const hexAddress = Utils.bech32ToHex( + const ed25519Address = Utils.parseBech32Address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', ); const addressUnlockCondition: UnlockCondition = - new AddressUnlockCondition(new Ed25519Address(hexAddress)); + new AddressUnlockCondition(ed25519Address); // Build most basic output with amount and a single address unlock condition const basicOutput = await client.buildBasicOutput({ @@ -55,7 +54,9 @@ async function run() { const basicOutputWithMetadata = await client.buildBasicOutput({ amount: BigInt(1000000), unlockConditions: [addressUnlockCondition], - features: [new MetadataFeature(utf8ToHex('Hello World!'))], + features: [ + new MetadataFeature({ data: utf8ToHex('Hello World!') }), + ], }); console.log(JSON.stringify(basicOutputWithMetadata, null, 2)); @@ -66,7 +67,7 @@ async function run() { unlockConditions: [ addressUnlockCondition, new StorageDepositReturnUnlockCondition( - new Ed25519Address(hexAddress), + ed25519Address, '1000000', ), ], @@ -79,10 +80,7 @@ async function run() { amount: BigInt(1000000), unlockConditions: [ addressUnlockCondition, - new ExpirationUnlockCondition( - new Ed25519Address(hexAddress), - 1, - ), + new ExpirationUnlockCondition(ed25519Address, 1), ], }); @@ -112,7 +110,7 @@ async function run() { const basicOutputWithSender = await client.buildBasicOutput({ amount: BigInt(1000000), unlockConditions: [addressUnlockCondition], - features: [new SenderFeature(new Ed25519Address(hexAddress))], + features: [new SenderFeature(ed25519Address)], }); console.log(JSON.stringify(basicOutputWithSender, null, 2)); diff --git a/bindings/nodejs/examples/client/12-get-raw-block.ts b/bindings/nodejs/examples/client/12-get-raw-block.ts index 37788b4b81..ac5aecec5a 100644 --- a/bindings/nodejs/examples/client/12-get-raw-block.ts +++ b/bindings/nodejs/examples/client/12-get-raw-block.ts @@ -23,7 +23,7 @@ async function run() { try { // Get a random block ID. - const blockId = (await client.getTips())[0]; + const blockId = (await client.getIssuance()).strongParents[0]; const rawBytes = await client.getBlockRaw(blockId); console.log('Block bytes: ', rawBytes); diff --git a/bindings/nodejs/examples/client/13-build-account-output.ts b/bindings/nodejs/examples/client/13-build-account-output.ts index f1dfcf2992..9d3772329a 100644 --- a/bindings/nodejs/examples/client/13-build-account-output.ts +++ b/bindings/nodejs/examples/client/13-build-account-output.ts @@ -7,7 +7,6 @@ import { Utils, MetadataFeature, SenderFeature, - Ed25519Address, IssuerFeature, AddressUnlockCondition, utf8ToHex, @@ -32,23 +31,21 @@ async function run() { }); try { - const hexAddress = Utils.bech32ToHex( + const ed25519Address = Utils.parseBech32Address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', ); const accountOutput = await client.buildAccountOutput({ accountId: '0x0000000000000000000000000000000000000000000000000000000000000000', - unlockConditions: [ - new AddressUnlockCondition(new Ed25519Address(hexAddress)), - ], + unlockConditions: [new AddressUnlockCondition(ed25519Address)], features: [ - new SenderFeature(new Ed25519Address(hexAddress)), - new MetadataFeature(utf8ToHex('hello')), + new SenderFeature(ed25519Address), + new MetadataFeature({ data: utf8ToHex('hello') }), ], immutableFeatures: [ - new IssuerFeature(new Ed25519Address(hexAddress)), - new MetadataFeature(utf8ToHex('hello')), + new IssuerFeature(ed25519Address), + new MetadataFeature({ data: utf8ToHex('hello') }), ], }); diff --git a/bindings/nodejs/examples/client/15-build-nft-output.ts b/bindings/nodejs/examples/client/15-build-nft-output.ts index 1cd9ab3f80..c91fba145e 100644 --- a/bindings/nodejs/examples/client/15-build-nft-output.ts +++ b/bindings/nodejs/examples/client/15-build-nft-output.ts @@ -10,7 +10,6 @@ import { TagFeature, MetadataFeature, SenderFeature, - Ed25519Address, IssuerFeature, Irc27Metadata, } from '@iota/sdk'; @@ -34,7 +33,7 @@ async function run() { }); try { - const hexAddress = Utils.bech32ToHex( + const ed25519Address = Utils.parseBech32Address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', ); @@ -47,16 +46,16 @@ async function run() { const nftOutput = await client.buildNftOutput({ // NftId needs to be null the first time nftId: '0x0000000000000000000000000000000000000000000000000000000000000000', - unlockConditions: [ - new AddressUnlockCondition(new Ed25519Address(hexAddress)), - ], + unlockConditions: [new AddressUnlockCondition(ed25519Address)], immutableFeatures: [ - new IssuerFeature(new Ed25519Address(hexAddress)), + new IssuerFeature(ed25519Address), tip27ImmutableMetadata.asFeature(), ], features: [ - new SenderFeature(new Ed25519Address(hexAddress)), - new MetadataFeature(utf8ToHex('mutable metadata')), + new SenderFeature(ed25519Address), + new MetadataFeature({ + data: utf8ToHex('mutable metadata'), + }), new TagFeature(utf8ToHex('my tag')), ], }); diff --git a/bindings/nodejs/examples/client/get-validators.ts b/bindings/nodejs/examples/client/get-validators.ts new file mode 100644 index 0000000000..aab1570964 --- /dev/null +++ b/bindings/nodejs/examples/client/get-validators.ts @@ -0,0 +1,42 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { Client, initLogger } from '@iota/sdk'; +require('dotenv').config({ path: '.env' }); + +// Run with command: +// yarn run-example ./client/get-validators.ts [PAGE_SIZE] [CURSOR] + +// This example returns the validators known by the node by querying the corresponding endpoint. +// You can provide a custom PAGE_SIZE and additionally a CURSOR from a previous request. +async function run() { + initLogger(); + for (const envVar of ['NODE_URL']) { + if (!(envVar in process.env)) { + throw new Error(`.env ${envVar} is undefined, see .env.example`); + } + } + + const client = await Client.create({ + // Insert your node URL in the .env. + nodes: [process.env.NODE_URL as string], + }); + + let pageSize = 1; + let cursor = ''; + if (process.argv.length > 1) { + pageSize = parseInt(process.argv[2]); + if (process.argv.length > 2) { + cursor = process.argv[3]; + } + } + + try { + const validators = await client.getValidators(pageSize, cursor); + console.log(validators); + } catch (error) { + console.error('Error: ', error); + } +} + +void run().then(() => process.exit()); diff --git a/bindings/nodejs/examples/client/getting-started.ts b/bindings/nodejs/examples/client/getting-started.ts index 8312a25f33..6e51332ed0 100644 --- a/bindings/nodejs/examples/client/getting-started.ts +++ b/bindings/nodejs/examples/client/getting-started.ts @@ -14,8 +14,8 @@ async function run() { }); try { - const nodeInfo = (await client.getInfo()).nodeInfo; - console.log(nodeInfo); + const info = (await client.getNodeInfo()).info; + console.log(info); } catch (error) { if ( error instanceof ClientError && diff --git a/bindings/nodejs/examples/how_tos/account_output/create.ts b/bindings/nodejs/examples/how_tos/account_output/create.ts index c81e1b9b6f..bd97aca5a8 100644 --- a/bindings/nodejs/examples/how_tos/account_output/create.ts +++ b/bindings/nodejs/examples/how_tos/account_output/create.ts @@ -49,12 +49,9 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); balance = await wallet.sync(); diff --git a/bindings/nodejs/examples/how_tos/account_output/destroy.ts b/bindings/nodejs/examples/how_tos/account_output/destroy.ts index deb923a74b..0d5782d01b 100644 --- a/bindings/nodejs/examples/how_tos/account_output/destroy.ts +++ b/bindings/nodejs/examples/how_tos/account_output/destroy.ts @@ -57,12 +57,9 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); console.log(`Destroyed account output ${accountId}`); diff --git a/bindings/nodejs/examples/how_tos/account_output/request-funds.ts b/bindings/nodejs/examples/how_tos/account_output/request-funds.ts index 5810703e76..ee8fd0aa22 100644 --- a/bindings/nodejs/examples/how_tos/account_output/request-funds.ts +++ b/bindings/nodejs/examples/how_tos/account_output/request-funds.ts @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Utils, Wallet, initLogger } from '@iota/sdk'; +import { Utils, Wallet, initLogger, AccountAddress } from '@iota/sdk'; // This example uses secrets in environment variables for simplicity which should not be done in production. // @@ -41,8 +41,8 @@ async function run() { const client = await wallet.getClient(); // Get Account address - const accountAddress = Utils.accountIdToBech32( - accountId, + const accountAddress = Utils.addressToBech32( + new AccountAddress(accountId), await client.getBech32Hrp(), ); diff --git a/bindings/nodejs/examples/how_tos/account_output/send-amount.ts b/bindings/nodejs/examples/how_tos/account_output/send-amount.ts index bf046dc034..0c18455776 100644 --- a/bindings/nodejs/examples/how_tos/account_output/send-amount.ts +++ b/bindings/nodejs/examples/how_tos/account_output/send-amount.ts @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Wallet, initLogger, Utils } from '@iota/sdk'; +import { Wallet, initLogger, Utils, AccountAddress } from '@iota/sdk'; // This example uses secrets in environment variables for simplicity which should not be done in production. // @@ -52,8 +52,8 @@ async function run() { const client = await wallet.getClient(); // Get Account address - const accountAddress = Utils.accountIdToBech32( - accountId, + const accountAddress = Utils.addressToBech32( + new AccountAddress(accountId), await client.getBech32Hrp(), ); diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts index ad52edfaf7..8d6f216af4 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/advanced_transaction.ts @@ -3,7 +3,6 @@ import { AddressUnlockCondition, - Ed25519Address, TimelockUnlockCondition, Utils, Wallet, @@ -49,10 +48,8 @@ async function run() { const basicOutput = await client.buildBasicOutput({ unlockConditions: [ new AddressUnlockCondition( - new Ed25519Address( - Utils.bech32ToHex( - 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', - ), + Utils.parseBech32Address( + 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', ), ), new TimelockUnlockCondition(slotIndex), @@ -63,10 +60,10 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); console.log('Waiting until transaction is accepted...'); - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, + await wallet.waitForTransactionAcceptance(transaction.transactionId); + console.log( + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); - console.log(`Block sent: ${process.env.EXPLORER_URL}/block/${blockId}`); } catch (error) { console.error('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts index 9d308e5adf..6086825e6c 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/claim_transaction.ts @@ -44,10 +44,10 @@ async function run() { const transaction = await wallet.claimOutputs(output_ids); console.log(`Transaction sent: ${transaction.transactionId}`); - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, + await wallet.waitForTransactionAcceptance(transaction.transactionId); + console.log( + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); - console.log(`Block sent: ${process.env.EXPLORER_URL}/block/${blockId}`); } catch (error) { console.error('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts b/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts index 7b0f99df78..1fbc3ee4a0 100644 --- a/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts +++ b/bindings/nodejs/examples/how_tos/advanced_transactions/send_micro_transaction.ts @@ -45,11 +45,11 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); - console.log(`Block sent: ${process.env.EXPLORER_URL}/block/${blockId}`); + console.log( + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, + ); } catch (error) { console.error('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/client/get-info.ts b/bindings/nodejs/examples/how_tos/client/get-info.ts index 6f44ba72ca..0b915a71d0 100644 --- a/bindings/nodejs/examples/how_tos/client/get-info.ts +++ b/bindings/nodejs/examples/how_tos/client/get-info.ts @@ -22,8 +22,8 @@ async function run() { }); try { - const nodeInfo = (await client.getInfo()).nodeInfo; - console.log(nodeInfo); + const info = (await client.getNodeInfo()).info; + console.log(info); } catch (error) { console.error('Error: ', error); } diff --git a/bindings/nodejs/examples/how_tos/native_tokens/burn.ts b/bindings/nodejs/examples/how_tos/native_tokens/burn.ts index 390d02e89a..d86f893d58 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/burn.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/burn.ts @@ -53,13 +53,10 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); balance = await wallet.sync(); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/create.ts b/bindings/nodejs/examples/how_tos/native_tokens/create.ts index abe0c70940..4b017033a4 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/create.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/create.ts @@ -38,13 +38,12 @@ async function run() { .then((prepared) => prepared.send()); console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( + await wallet.waitForTransactionAcceptance( transaction.transactionId, ); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); await wallet.sync(); @@ -71,13 +70,10 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); console.log(`Created token: ${prepared.tokenId()}`); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts b/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts index 0398b73dd5..f0a4bc1508 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/destroy-foundry.ts @@ -40,12 +40,9 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); balance = await wallet.sync(); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts index 581963a3b7..5384e41612 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/melt.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/melt.ts @@ -47,13 +47,10 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); balance = await wallet.sync(); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/mint.ts b/bindings/nodejs/examples/how_tos/native_tokens/mint.ts index a5e7219613..dc0e068f93 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/mint.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/mint.ts @@ -48,13 +48,10 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); balance = await wallet.sync(); diff --git a/bindings/nodejs/examples/how_tos/native_tokens/send.ts b/bindings/nodejs/examples/how_tos/native_tokens/send.ts index b4a4963907..c4b9ae7187 100644 --- a/bindings/nodejs/examples/how_tos/native_tokens/send.ts +++ b/bindings/nodejs/examples/how_tos/native_tokens/send.ts @@ -58,13 +58,12 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( + await wallet.waitForTransactionAcceptance( transaction.transactionId, ); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); balance = await wallet.sync(); diff --git a/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts index 16b834845f..2896d24c04 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/00_mint_issuer_nft.ts @@ -54,12 +54,9 @@ async function run() { }; const transaction = await wallet.mintNfts([params]); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); transaction.payload.transaction.outputs.forEach( diff --git a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts index a7a3538992..ce79c8447a 100644 --- a/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts +++ b/bindings/nodejs/examples/how_tos/nft_collection/01_mint_collection_nft.ts @@ -1,7 +1,14 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { MintNftParams, NftId, Utils, Wallet, Irc27Metadata } from '@iota/sdk'; +import { + MintNftParams, + NftId, + Utils, + Wallet, + Irc27Metadata, + NftAddress, +} from '@iota/sdk'; require('dotenv').config({ path: '.env' }); // The NFT collection size @@ -49,7 +56,10 @@ async function run() { const client = await wallet.getClient(); const bech32Hrp = await client.getBech32Hrp(); - const issuer = Utils.nftIdToBech32(issuerNftId, bech32Hrp); + const issuer = Utils.addressToBech32( + new NftAddress(issuerNftId), + bech32Hrp, + ); const nftMintParams = []; // Create the metadata with another index for each @@ -79,12 +89,11 @@ async function run() { ); const transaction = await wallet.mintNfts(chunk); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( + await wallet.waitForTransactionAcceptance( transaction.transactionId, ); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); // Sync so the new outputs are available again for new transactions diff --git a/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts b/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts index efd4288ecd..c0b079596a 100644 --- a/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/burn_nft.ts @@ -53,12 +53,9 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); console.log(`Burned NFT ${nftId}`); diff --git a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts index 4627c9b848..aa2ca76030 100644 --- a/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/mint_nft.ts @@ -3,7 +3,6 @@ import { AddressUnlockCondition, - Ed25519Address, IssuerFeature, MintNftParams, SenderFeature, @@ -73,44 +72,36 @@ async function run() { let transaction = await wallet.mintNfts([params]); console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - let blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); console.log('Minted NFT 1'); // Build an NFT manually by using the `NftOutputBuilder` const client = await wallet.getClient(); - const hexAddress = Utils.bech32ToHex(senderAddress); + const ed25519Address = Utils.parseBech32Address(senderAddress); const output = await client.buildNftOutput({ amount: NFT2_AMOUNT, nftId: '0x0000000000000000000000000000000000000000000000000000000000000000', unlockConditions: [ new AddressUnlockCondition( - new Ed25519Address(Utils.bech32ToHex(NFT1_OWNER_ADDRESS)), + Utils.parseBech32Address(NFT1_OWNER_ADDRESS), ), ], - immutableFeatures: [ - new IssuerFeature(new Ed25519Address(hexAddress)), - ], - features: [new SenderFeature(new Ed25519Address(hexAddress))], + immutableFeatures: [new IssuerFeature(ed25519Address)], + features: [new SenderFeature(ed25519Address)], }); transaction = await wallet.sendOutputs([output]); console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); console.log('Minted NFT 2'); diff --git a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts index 6ad95d06e1..66f0daf689 100644 --- a/bindings/nodejs/examples/how_tos/nfts/send_nft.ts +++ b/bindings/nodejs/examples/how_tos/nfts/send_nft.ts @@ -59,13 +59,10 @@ async function run() { console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); // To send an NFT with expiration unlock condition prepareOutput() can be used like this: diff --git a/bindings/nodejs/examples/how_tos/outputs/features.ts b/bindings/nodejs/examples/how_tos/outputs/features.ts index c6bb77e9ac..6dcbc60e74 100644 --- a/bindings/nodejs/examples/how_tos/outputs/features.ts +++ b/bindings/nodejs/examples/how_tos/outputs/features.ts @@ -10,7 +10,6 @@ import { MetadataFeature, SenderFeature, TagFeature, - Ed25519Address, IssuerFeature, utf8ToHex, } from '@iota/sdk'; @@ -26,34 +25,34 @@ async function run() { const client = await Client.create({}); try { - const hexAddress = Utils.bech32ToHex( + const ed25519Address = Utils.parseBech32Address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', ); const addressUnlockCondition: UnlockCondition = - new AddressUnlockCondition(new Ed25519Address(hexAddress)); + new AddressUnlockCondition(ed25519Address); // Output with sender feature const nftOutputWithSender = await client.buildNftOutput({ nftId: '0x0000000000000000000000000000000000000000000000000000000000000000', unlockConditions: [addressUnlockCondition], - features: [new SenderFeature(new Ed25519Address(hexAddress))], + features: [new SenderFeature(ed25519Address)], }); // Output with issuer feature const nftOutputWithIssuer = await client.buildNftOutput({ nftId: '0x0000000000000000000000000000000000000000000000000000000000000000', unlockConditions: [addressUnlockCondition], - immutableFeatures: [ - new IssuerFeature(new Ed25519Address(hexAddress)), - ], + immutableFeatures: [new IssuerFeature(ed25519Address)], }); // Output with metadata feature const nftOutputWithMetadata = await client.buildNftOutput({ nftId: '0x0000000000000000000000000000000000000000000000000000000000000000', unlockConditions: [addressUnlockCondition], - features: [new MetadataFeature(utf8ToHex('Hello, World!'))], + features: [ + new MetadataFeature({ data: utf8ToHex('Hello, World!') }), + ], }); // Output with immutable metadata feature @@ -61,7 +60,7 @@ async function run() { nftId: '0x0000000000000000000000000000000000000000000000000000000000000000', unlockConditions: [addressUnlockCondition], immutableFeatures: [ - new MetadataFeature(utf8ToHex('Hello, World!')), + new MetadataFeature({ data: utf8ToHex('Hello, World!') }), ], }); diff --git a/bindings/nodejs/examples/how_tos/outputs/unlock-conditions.ts b/bindings/nodejs/examples/how_tos/outputs/unlock-conditions.ts index 4be6b6fd61..83e38a4c28 100644 --- a/bindings/nodejs/examples/how_tos/outputs/unlock-conditions.ts +++ b/bindings/nodejs/examples/how_tos/outputs/unlock-conditions.ts @@ -8,7 +8,6 @@ import { UnlockCondition, AddressUnlockCondition, StorageDepositReturnUnlockCondition, - Ed25519Address, ExpirationUnlockCondition, TimelockUnlockCondition, SimpleTokenScheme, @@ -27,13 +26,13 @@ async function run() { const client = await Client.create({}); try { - const hexAddress = Utils.bech32ToHex( + const ed25519Address = Utils.parseBech32Address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy', ); - const accountHexAddress = Utils.bech32ToHex( + const accountAddress = Utils.parseBech32Address( 'rms1pr59qm43mjtvhcajfmupqf23x29llam88yecn6pyul80rx099krmv2fnnux', - ); + ) as AccountAddress; const tokenSchema = new SimpleTokenScheme( BigInt(50), @@ -42,7 +41,7 @@ async function run() { ); const addressUnlockCondition: UnlockCondition = - new AddressUnlockCondition(new Ed25519Address(hexAddress)); + new AddressUnlockCondition(ed25519Address); // Most simple output const basicOutput = await client.buildBasicOutput({ @@ -54,7 +53,7 @@ async function run() { unlockConditions: [ addressUnlockCondition, new StorageDepositReturnUnlockCondition( - new Ed25519Address(hexAddress), + ed25519Address, '1000000', ), ], @@ -72,10 +71,7 @@ async function run() { const basicOutputWithExpiration = await client.buildBasicOutput({ unlockConditions: [ addressUnlockCondition, - new ExpirationUnlockCondition( - new Ed25519Address(hexAddress), - 1, - ), + new ExpirationUnlockCondition(ed25519Address, 1), ], }); @@ -84,9 +80,7 @@ async function run() { serialNumber: 1, tokenScheme: tokenSchema, unlockConditions: [ - new ImmutableAccountAddressUnlockCondition( - new AccountAddress(accountHexAddress), - ), + new ImmutableAccountAddressUnlockCondition(accountAddress), ], }); diff --git a/bindings/nodejs/examples/how_tos/sign_and_verify_ed25519/sign-ed25519.ts b/bindings/nodejs/examples/how_tos/sign_and_verify_ed25519/sign-ed25519.ts index 5513d2d398..179d1d54c4 100644 --- a/bindings/nodejs/examples/how_tos/sign_and_verify_ed25519/sign-ed25519.ts +++ b/bindings/nodejs/examples/how_tos/sign_and_verify_ed25519/sign-ed25519.ts @@ -65,8 +65,8 @@ async function run() { `Public key: ${ed25519Signature.publicKey}\nSignature: ${ed25519Signature.signature}`, ); - const bech32Address = Utils.hexPublicKeyToBech32Address( - ed25519Signature.publicKey, + const bech32Address = Utils.addressToBech32( + Utils.publicKeyHash(ed25519Signature.publicKey), 'rms', ); console.log('Address: ' + bech32Address); diff --git a/bindings/nodejs/examples/how_tos/wallet/consolidate-outputs.ts b/bindings/nodejs/examples/how_tos/wallet/consolidate-outputs.ts index ab4ce6f88e..9e28f71cb4 100644 --- a/bindings/nodejs/examples/how_tos/wallet/consolidate-outputs.ts +++ b/bindings/nodejs/examples/how_tos/wallet/consolidate-outputs.ts @@ -50,7 +50,7 @@ async function run() { console.log(`OUTPUT #${i}`); console.log( '- address: %s\n- amount: %d\n- native token: %s', - Utils.hexToBech32(address.toString(), 'rms'), + Utils.addressToBech32(address, 'rms'), output.getAmount(), output instanceof CommonOutput ? (output as CommonOutput).getNativeToken() ?? [] @@ -68,14 +68,10 @@ async function run() { console.log('Transaction sent: %s', transaction.transactionId); // Wait for the consolidation transaction to get accepted - const blockId = wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - 'Transaction accepted: %s/block/$s', - process.env.EXPLORER_URL, - blockId, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); // Sync wallet @@ -88,7 +84,7 @@ async function run() { console.log(`OUTPUT #${i}`); console.log( '- address: %s\n- amount: %d\n- native tokens: %s', - Utils.hexToBech32(address.toString(), 'rms'), + Utils.addressToBech32(address, 'rms'), output.getAmount(), output instanceof CommonOutput ? (output as CommonOutput).getNativeToken() diff --git a/bindings/nodejs/examples/how_tos/wallet/create-wallet.ts b/bindings/nodejs/examples/how_tos/wallet/create-wallet.ts index 882caa0736..1609378d19 100644 --- a/bindings/nodejs/examples/how_tos/wallet/create-wallet.ts +++ b/bindings/nodejs/examples/how_tos/wallet/create-wallet.ts @@ -44,18 +44,7 @@ async function run() { // The mnemonic can't be retrieved from the Stronghold file, so make a backup in a secure place! await secretManager.storeMnemonic(process.env.MNEMONIC as string); - const walletAddress = await secretManager.generateEd25519Addresses({ - coinType: CoinType.IOTA, - accountIndex: 0, - range: { - start: 0, - end: 1, - }, - bech32Hrp: 'tst', - }); - const walletOptions: WalletOptions = { - address: walletAddress[0], storagePath: process.env.WALLET_DB_PATH, clientOptions: { nodes: [process.env.NODE_URL as string], diff --git a/bindings/nodejs/examples/secret_manager/generate-addresses.ts b/bindings/nodejs/examples/secret_manager/generate-addresses.ts index 80fc98025e..0ca6dee384 100644 --- a/bindings/nodejs/examples/secret_manager/generate-addresses.ts +++ b/bindings/nodejs/examples/secret_manager/generate-addresses.ts @@ -49,7 +49,7 @@ async function run() { start: 0, end: 4, }, - options: { internal: true }, + options: { internal: true, ledgerNanoPrompt: false }, }); console.log( 'List of generated internal addresses:', diff --git a/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts b/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts index 13cfaf558e..0e4f9c59b1 100644 --- a/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts +++ b/bindings/nodejs/examples/wallet/06-send-micro-transaction.ts @@ -45,13 +45,10 @@ async function run() { }); console.log(`Transaction sent: ${transaction.transactionId}`); - // Wait for transaction to get accepted - const blockId = await wallet.waitForTransactionAcceptance( - transaction.transactionId, - ); + await wallet.waitForTransactionAcceptance(transaction.transactionId); console.log( - `Tx accepted in block: ${process.env.EXPLORER_URL}/block/${blockId}`, + `Tx accepted: ${process.env.EXPLORER_URL}/transactions/${transaction.transactionId}`, ); } catch (error) { console.log('Error: ', error); diff --git a/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts b/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts index 480dbf7fa9..abd9c5562a 100644 --- a/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts +++ b/bindings/nodejs/examples/wallet/17-check-unlock-conditions.ts @@ -1,7 +1,13 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { AddressUnlockCondition, BasicOutput, Output, Utils } from '@iota/sdk'; +import { + AddressUnlockCondition, + BasicOutput, + Ed25519Address, + Output, + Utils, +} from '@iota/sdk'; import { getUnlockedWallet } from './common'; @@ -27,7 +33,9 @@ async function run() { amount: AMOUNT, }); - const hexEncodedwalletAddress = Utils.bech32ToHex(walletAddress); + const hexEncodedwalletAddress = Utils.parseBech32Address( + walletAddress, + ) as Ed25519Address; if (output instanceof BasicOutput) { const basicOutput = output as BasicOutput; @@ -36,7 +44,7 @@ async function run() { basicOutput.unlockConditions.length === 1 && basicOutput.unlockConditions[0] instanceof AddressUnlockCondition && - hexEncodedwalletAddress.includes( + hexEncodedwalletAddress.pubKeyHash.includes( ( basicOutput .unlockConditions[0] as AddressUnlockCondition diff --git a/bindings/nodejs/examples/wallet/events.ts b/bindings/nodejs/examples/wallet/events.ts index d0f8f4f5fd..70b60e2538 100644 --- a/bindings/nodejs/examples/wallet/events.ts +++ b/bindings/nodejs/examples/wallet/events.ts @@ -4,7 +4,7 @@ import { WalletEvent, TransactionProgressWalletEvent, - SelectingInputsProgress, + BuildingTransactionProgress, } from '@iota/sdk'; import { getUnlockedWallet } from './common'; require('dotenv').config({ path: '.env' }); @@ -30,7 +30,9 @@ async function run() { await wallet.listen([], callback); await wallet.emitTestEvent( - new TransactionProgressWalletEvent(new SelectingInputsProgress()), + new TransactionProgressWalletEvent( + new BuildingTransactionProgress(), + ), ); await wallet.destroy(); diff --git a/bindings/nodejs/examples/wallet/getting-started.ts b/bindings/nodejs/examples/wallet/getting-started.ts index 6d08c99e2d..95d119ce79 100644 --- a/bindings/nodejs/examples/wallet/getting-started.ts +++ b/bindings/nodejs/examples/wallet/getting-started.ts @@ -44,18 +44,7 @@ async function run() { // The mnemonic can't be retrieved from the Stronghold file, so make a backup in a secure place! await secretManager.storeMnemonic(mnemonic); - const wallet_address = await secretManager.generateEd25519Addresses({ - coinType: CoinType.IOTA, - accountIndex: 0, - range: { - start: 0, - end: 1, - }, - bech32Hrp: 'tst', - }); - const walletOptions: WalletOptions = { - address: wallet_address[0], storagePath: WALLET_DB_PATH, clientOptions: { nodes: [NODE_URL as string], diff --git a/bindings/nodejs/lib/client/client-method-handler.ts b/bindings/nodejs/lib/client/client-method-handler.ts index 96c89759b0..353b35e1aa 100644 --- a/bindings/nodejs/lib/client/client-method-handler.ts +++ b/bindings/nodejs/lib/client/client-method-handler.ts @@ -8,7 +8,7 @@ import { listenMqtt, destroyClient, } from '../bindings'; -import type { IClientOptions, __ClientMethods__ } from '../types/client'; +import type { ClientOptions, __ClientMethods__ } from '../types/client'; /** * The MethodHandler which sends the commands to the Rust side. @@ -26,7 +26,7 @@ export class ClientMethodHandler { /** * @param options The client options. */ - static async create(options: IClientOptions): Promise { + static async create(options: ClientOptions): Promise { try { const methodHandler = await createClient(JSON.stringify(options)); return new ClientMethodHandler(methodHandler); diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index 86b5983084..5f1e7dcf7d 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -3,11 +3,9 @@ import { ClientMethodHandler } from './client-method-handler'; import { - IClientOptions, - PreparedTransactionData, - INetworkInfo, - INode, - IAuth, + ClientOptions, + Node, + Auth, AccountOutputBuilderParams, BasicOutputBuilderParams, FoundryOutputBuilderParams, @@ -20,11 +18,7 @@ import { NftOutputQueryParameters, OutputQueryParameters, } from '../types/client'; -import type { INodeInfoWrapper } from '../types/client/nodeInfo'; -import { - Bip44, - SecretManagerType, -} from '../types/secret_manager/secret-manager'; +import type { NodeInfoResponse } from '../types/client/nodeInfo'; import { AccountOutput, BasicOutput, @@ -32,9 +26,7 @@ import { NftOutput, Output, BlockId, - UnlockCondition, Payload, - SignedTransactionPayload, parseBlock, Block, AccountId, @@ -47,11 +39,13 @@ import { SlotIndex, SlotCommitmentId, SlotCommitment, + EpochIndex, + Address, } from '../types/block'; import { HexEncodedString } from '../utils'; import { - IBlockMetadata, - INodeInfo, + BlockMetadataResponse, + InfoResponse, UTXOInput, Response, OutputId, @@ -59,20 +53,29 @@ import { u64, TransactionId, Bech32Address, - IBlockWithMetadata, - TransactionMetadata, + BlockWithMetadataResponse, + TransactionMetadataResponse, } from '../types'; import { OutputResponse, - IOutputsResponse, + OutputsResponse, CongestionResponse, UtxoChangesResponse, UtxoChangesFullResponse, + CommitteeResponse, + IssuanceBlockHeaderResponse, + OutputMetadataResponse, + OutputWithMetadataResponse, + NetworkMetricsResponse, } from '../types/models/api'; +import { RoutesResponse } from '../types/models/api/routes-response'; import { plainToInstance } from 'class-transformer'; import { ManaRewardsResponse } from '../types/models/api/mana-rewards-response'; -import { ValidatorsResponse } from '../types/models/api/validators-response'; +import { + ValidatorResponse, + ValidatorsResponse, +} from '../types/models/api/validators-response'; /** The Client to interact with nodes. */ export class Client { @@ -88,38 +91,88 @@ export class Client { /** * @param options The client options. */ - static async create(options: IClientOptions): Promise { + static async create(options: ClientOptions): Promise { return new Client(await ClientMethodHandler.create(options)); } async destroy(): Promise { return this.methodHandler.destroy(); } + // Node routes. + + /** + * Returns the health of the node. + * GET /health + */ + async getHealth(url: string): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getHealth', + data: { + url, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Returns the available API route groups of the node. + * GET /api/routes + */ + async getRoutes(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getRoutes', + }); + + return JSON.parse(response).payload; + } + /** * Get the node information together with the url of the used node. */ - async getInfo(): Promise { + async getNodeInfo(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getNodeInfo', + }); + + return JSON.parse(response).payload; + } + + /** + * Returns general information about the node. + * GET /api/core/v3/info + * + * @param url The URL of the node. + * @param auth An authentication object (e.g. JWT). + */ + async getInfo(url: string, auth?: Auth): Promise { const response = await this.methodHandler.callMethod({ name: 'getInfo', + data: { + url, + auth, + }, }); return JSON.parse(response).payload; } /** - * Get the network related information such as network_id. + * Get the network metrics. */ - async getNetworkInfo(): Promise { + async getNetworkMetrics(): Promise { const response = await this.methodHandler.callMethod({ - name: 'getNetworkInfo', + name: 'getNetworkMetrics', }); return JSON.parse(response).payload; } + // Accounts routes. + /** - * Check the readiness of the node to issue a new block, the reference mana cost based on the rate setter and - * current network congestion, and the block issuance credits of the requested account. + * Checks if the account is ready to issue a block. + * GET /api/core/v3/accounts/{bech32Address}/congestion */ async getAccountCongestion( accountId: AccountId, @@ -136,16 +189,23 @@ export class Client { return JSON.parse(response).payload; } + // Rewards routes. + /** - * Returns the totally available Mana rewards of an account or delegation output decayed up to endEpoch index + * Returns the total available Mana rewards of an account or delegation output decayed up to `epochEnd` index * provided in the response. + * Note that rewards for an epoch only become available at the beginning of the next epoch. If the end epoch of a + * staking feature is equal or greater than the current epoch, the rewards response will not include the potential + * future rewards for those epochs. `epochStart` and `epochEnd` indicates the actual range for which reward value + * is returned and decayed for. + * GET /api/core/v3/rewards/{outputId} */ - async getRewards( + async getOutputManaRewards( outputId: OutputId, slotIndex?: SlotIndex, ): Promise { const response = await this.methodHandler.callMethod({ - name: 'getRewards', + name: 'getOutputManaRewards', data: { outputId, slotIndex, @@ -155,8 +215,11 @@ export class Client { return JSON.parse(response).payload; } + // Validators routes. + /** * Returns information of all registered validators and if they are active, ordered by their holding stake. + * GET /api/core/v3/validators */ async getValidators( pageSize?: number, @@ -174,9 +237,10 @@ export class Client { } /** - * Return information about a validator. + * Return information about a staker (registered validator). + * GET /api/core/v3/validators/{bech32Address} */ - async getValidator(accountId: AccountId): Promise { + async getValidator(accountId: AccountId): Promise { const response = await this.methodHandler.callMethod({ name: 'getValidator', data: { @@ -187,51 +251,41 @@ export class Client { return JSON.parse(response).payload; } - /** - * Get output from a given output ID. - */ - async getOutput(outputId: OutputId): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getOutput', - data: { - outputId, - }, - }); - - const parsed = JSON.parse(response) as Response; - return plainToInstance(OutputResponse, parsed.payload); - } + // Committee routes. /** - * Fetch OutputResponse from given output IDs. Requests are sent in parallel. + * Returns the information of committee members at the given epoch index. If epoch index is not provided, the + * current committee members are returned. + * GET /api/core/v3/committee/?epochIndex */ - async getOutputs(outputIds: OutputId[]): Promise { + async getCommittee(epochIndex?: EpochIndex): Promise { const response = await this.methodHandler.callMethod({ - name: 'getOutputs', + name: 'getCommittee', data: { - outputIds, + epochIndex, }, }); - const parsed = JSON.parse(response) as Response; - return plainToInstance(OutputResponse, parsed.payload); + return JSON.parse(response).payload; } + // Blocks routes. + /** - * Request tips from the node. - * The tips can be considered as non-lazy and are therefore ideal for attaching a block to the Tangle. - * @returns An array of tips represented by their block IDs. + * Returns information that is ideal for attaching a block in the network. + * GET /api/core/v3/blocks/issuance */ - async getTips(): Promise { + async getIssuance(): Promise { const response = await this.methodHandler.callMethod({ - name: 'getTips', + name: 'getIssuance', }); return JSON.parse(response).payload; } /** - * Post a block in JSON format. + * Returns the BlockId of the submitted block. + * POST /api/core/v3/blocks * * @param block The block to post. * @returns The block ID once the block has been posted. @@ -248,49 +302,52 @@ export class Client { } /** - * Get a block in JSON format. + * Returns the BlockId of the submitted block. + * POST /api/core/v3/blocks * - * @param blockId The corresponding block ID of the requested block. - * @returns The requested block. + * @param blockBytes The block as raw bytes. + * @returns The ID of the posted block. */ - async getBlock(blockId: BlockId): Promise { + async postBlockRaw(blockBytes: Uint8Array): Promise { const response = await this.methodHandler.callMethod({ - name: 'getBlock', + name: 'postBlockRaw', data: { - blockId, + blockBytes, }, }); - const parsed = JSON.parse(response) as Response; - return parseBlock(parsed.payload); + return JSON.parse(response).payload; } /** - * Get the metadata of a block. + * Finds a block by its ID and returns it as object. + * GET /api/core/v3/blocks/{blockId} * - * @param blockId The corresponding block ID of the requested block metadata. - * @returns The requested block metadata. + * @param blockId The corresponding block ID of the requested block. + * @returns The requested block. */ - async getBlockMetadata(blockId: BlockId): Promise { + async getBlock(blockId: BlockId): Promise { const response = await this.methodHandler.callMethod({ - name: 'getBlockMetadata', + name: 'getBlock', data: { blockId, }, }); - return JSON.parse(response).payload; + const parsed = JSON.parse(response) as Response; + return parseBlock(parsed.payload); } /** - * Get a block with its metadata. + * Finds a block by its ID and returns it as raw bytes. + * GET /api/core/v3/blocks/{blockId} * - * @param blockId The corresponding block ID of the requested block. - * @returns The requested block with its metadata. + * @param blockId The block ID of the requested block. + * @returns The raw bytes of the requested block. */ - async getBlockWithMetadata(blockId: BlockId): Promise { + async getBlockRaw(blockId: BlockId): Promise { const response = await this.methodHandler.callMethod({ - name: 'getBlockWithMetadata', + name: 'getBlockRaw', data: { blockId, }, @@ -300,156 +357,70 @@ export class Client { } /** - * Find inputs from addresses for a given amount (useful for offline signing). + * Returns the metadata of a block. + * GET /api/core/v3/blocks/{blockId}/metadata * - * @param addresses A list of included addresses. - * @param amount The amount to find inputs for. - * @returns An array of UTXO inputs. + * @param blockId The corresponding block ID of the requested block metadata. + * @returns The requested block metadata. */ - async findInputs(addresses: string[], amount: u64): Promise { + async getBlockMetadata(blockId: BlockId): Promise { const response = await this.methodHandler.callMethod({ - name: 'findInputs', + name: 'getBlockMetadata', data: { - addresses, - amount: Number(amount), + blockId, }, }); - const parsed = JSON.parse(response) as Response; - return plainToInstance(UTXOInput, parsed.payload); + return JSON.parse(response).payload; } /** - * Sign a transaction. + * Returns a block with its metadata. + * GET /api/core/v2/blocks/{blockId}/full * - * @param secretManager One of the supported secret managers. - * @param preparedTransactionData An instance of `PreparedTransactionData`. - * @returns The corresponding signed transaction payload. + * @param blockId The corresponding block ID of the requested block. + * @returns The requested block with its metadata. */ - async signTransaction( - secretManager: SecretManagerType, - preparedTransactionData: PreparedTransactionData, - ): Promise { + async getBlockWithMetadata( + blockId: BlockId, + ): Promise { const response = await this.methodHandler.callMethod({ - name: 'signTransaction', + name: 'getBlockWithMetadata', data: { - secretManager, - preparedTransactionData, + blockId, }, }); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(SignedTransactionPayload, parsed.payload); + return JSON.parse(response).payload; } - /** - * Create a signature unlock using the given secret manager. - * - * @param secretManager One of the supported secret managers. - * @param transactionSigningHash The signing hash of the transaction. - * @param chain A BIP44 chain - * @returns The corresponding unlock condition. - */ - async signatureUnlock( - secretManager: SecretManagerType, - transactionSigningHash: HexEncodedString, - chain: Bip44, - ): Promise { - const response = await this.methodHandler.callMethod({ - name: 'signatureUnlock', - data: { - secretManager, - transactionSigningHash, - chain, - }, - }); - - return UnlockCondition.parse(JSON.parse(response).payload); - } + // UTXO routes. /** - * Build an unsigned block. - * - * @param issuerId The identifier of the block issuer account. - * @param payload The payload to post. - * @returns The block ID followed by the block containing the payload. + * Finds an output by its ID and returns it as object. + * GET /api/core/v3/outputs/{outputId} */ - async buildBasicBlock( - issuerId: AccountId, - payload?: Payload, - ): Promise { + async getOutput(outputId: OutputId): Promise { const response = await this.methodHandler.callMethod({ - name: 'buildBasicBlock', + name: 'getOutput', data: { - issuerId, - payload, + outputId, }, }); - const parsed = JSON.parse(response) as Response; - return parseUnsignedBlock(parsed.payload); - } - - /** - * Get a node candidate from the healthy node pool. - */ - async getNode(): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getNode', - }); - - return JSON.parse(response).payload; - } - - /** - * Get the ID of the network the node is connected to. - */ - async getNetworkId(): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getNetworkId', - }); - return JSON.parse(response).payload; - } - - /** - * Get the Bech32 HRP (human readable part) of the network the node is connected to. - */ - async getBech32Hrp(): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getBech32Hrp', - }); - - return JSON.parse(response).payload; - } - - /** - * Get the token supply. - */ - async getTokenSupply(): Promise { - return BigInt((await this.getProtocolParameters()).tokenSupply); - } - - /** - * Get the protocol parameters. - */ - async getProtocolParameters(): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getProtocolParameters', - }); - - return JSON.parse(response).payload; + const parsed = JSON.parse(response) as Response; + return plainToInstance(OutputResponse, parsed.payload); } /** - * Get the health of a node. + * Finds an output by its ID and returns it as raw bytes. + * GET /api/core/v3/outputs/{outputId} */ - async getHealth(url: string): Promise { + async getOutputRaw(outputId: OutputId): Promise { const response = await this.methodHandler.callMethod({ - name: 'getHealth', + name: 'getOutputRaw', data: { - url, + outputId, }, }); @@ -457,17 +428,16 @@ export class Client { } /** - * Get the info about the node. - * - * @param url The URL of the node. - * @param auth An authentication object (e.g. JWT). + * Finds output metadata by output ID. + * GET /api/core/v3/outputs/{outputId}/metadata */ - async getNodeInfo(url: string, auth?: IAuth): Promise { + async getOutputMetadata( + outputId: OutputId, + ): Promise { const response = await this.methodHandler.callMethod({ - name: 'getNodeInfo', + name: 'getOutputMetadata', data: { - url, - auth, + outputId, }, }); @@ -475,94 +445,109 @@ export class Client { } /** - * Post block as raw bytes, returns the block ID. - * - * @param block The block. - * @returns The ID of the posted block. + * Finds an output with its metadata by output ID. + * GET /api/core/v3/outputs/{outputId}/full */ - async postBlockRaw(block: Block): Promise { + async getOutputWithMetadata( + outputId: OutputId, + ): Promise { const response = await this.methodHandler.callMethod({ - name: 'postBlockRaw', + name: 'getOutputWithMetadata', data: { - block, + outputId, }, }); - return JSON.parse(response).payload; + const parsed = JSON.parse( + response, + ) as Response; + return plainToInstance(OutputWithMetadataResponse, parsed.payload); } /** - * Get block as raw bytes. + * Returns the earliest confirmed block containing the transaction with the given ID. + * GET /api/core/v3/transactions/{transactionId}/included-block * - * @param blockId The block ID of the requested block. - * @returns The raw bytes of the requested block. + * @param transactionId The ID of the transaction. + * @returns The included block that contained the transaction. */ - async getBlockRaw(blockId: BlockId): Promise { + async getIncludedBlock(transactionId: TransactionId): Promise { const response = await this.methodHandler.callMethod({ - name: 'getBlockRaw', + name: 'getIncludedBlock', data: { - blockId, + transactionId, }, }); - return JSON.parse(response).payload; + const parsed = JSON.parse(response) as Response; + return parseBlock(parsed.payload); } /** - * Get the included block of a given transaction. + * Returns the earliest confirmed block containing the transaction with the given ID, as raw bytes. + * GET /api/core/v3/transactions/{transactionId}/included-block * * @param transactionId The ID of the transaction. * @returns The included block that contained the transaction. */ - async getIncludedBlock(transactionId: TransactionId): Promise { + async getIncludedBlockRaw( + transactionId: TransactionId, + ): Promise { const response = await this.methodHandler.callMethod({ - name: 'getIncludedBlock', + name: 'getIncludedBlockRaw', data: { transactionId, }, }); - const parsed = JSON.parse(response) as Response; - return parseBlock(parsed.payload); + + return JSON.parse(response).payload; } /** - * Get the metadata of the included block of a given transaction. + * Returns the metadata of the earliest block containing the tx that was confirmed. + * GET /api/core/v3/transactions/{transactionId}/included-block/metadata * * @param transactionId The ID of the transaction. * @returns The included block that contained the transaction. */ async getIncludedBlockMetadata( transactionId: TransactionId, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'getIncludedBlockMetadata', data: { transactionId, }, }); + return JSON.parse(response).payload; } /** - * Find the metadata of a transaction. + * Finds the metadata of a transaction. + * GET /api/core/v3/transactions/{transactionId}/metadata * * @param transactionId The ID of the transaction. * @returns The transaction metadata. */ async getTransactionMetadata( transactionId: TransactionId, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'getTransactionMetadata', data: { transactionId, }, }); + return JSON.parse(response).payload; } + // Commitments routes. + /** - * Look up a commitment by a given commitment ID. + * Finds a slot commitment by its ID and returns it as object. + * GET /api/core/v3/commitments/{commitmentId} * * @param commitmentId Commitment ID of the commitment to look up. * @returns The commitment. @@ -576,11 +561,32 @@ export class Client { commitmentId, }, }); + return JSON.parse(response).payload; } /** - * Get all UTXO changes of a given slot by Commitment ID. + * Finds a slot commitment by its ID and returns it as raw bytes. + * GET /api/core/v3/commitments/{commitmentId} + * + * @param commitmentId Commitment ID of the commitment to look up. + * @returns The commitment as raw bytes. + */ + async getCommitmentRaw( + commitmentId: SlotCommitmentId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getCommitmentRaw', + data: { + commitmentId, + }, + }); + + return JSON.parse(response).payload; + } + /** + * Get all UTXO changes of a given slot by slot commitment ID. + * GET /api/core/v3/commitments/{commitmentId}/utxo-changes * * @param commitmentId Commitment ID of the commitment to look up. * @returns The UTXO changes. @@ -594,11 +600,13 @@ export class Client { commitmentId, }, }); + return JSON.parse(response).payload; } /** - * Get all full UTXO changes of a given slot by Commitment ID. + * Get all full UTXO changes of a given slot by slot commitment ID. + * GET /api/core/v3/commitments/{commitmentId}/utxo-changes/full * * @param commitmentId Commitment ID of the commitment to look up. * @returns The UTXO changes. @@ -612,34 +620,56 @@ export class Client { commitmentId, }, }); + return JSON.parse(response).payload; } /** - * Look up a commitment by a given commitment index. + * Finds a slot commitment by slot index and returns it as object. + * GET /api/core/v3/commitments/by-slot/{slot} * * @param slot Index of the commitment to look up. * @returns The commitment. */ - async getCommitmentByIndex(slot: SlotIndex): Promise { + async getCommitmentBySlot(slot: SlotIndex): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getCommitmentBySlot', + data: { + slot, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Finds a slot commitment by slot index and returns it as raw bytes. + * GET /api/core/v3/commitments/by-slot/{slot} + * + * @param slot Index of the commitment to look up. + * @returns The commitment as raw bytes. + */ + async getCommitmentBySlotRaw(slot: SlotIndex): Promise { const response = await this.methodHandler.callMethod({ - name: 'getCommitmentByIndex', + name: 'getCommitmentBySlotRaw', data: { slot, }, }); + return JSON.parse(response).payload; } /** - * Get all UTXO changes of a given slot by commitment index. + * Get all UTXO changes of a given slot by its index. + * GET /api/core/v3/commitments/by-slot/{slot}/utxo-changes * * @param slot Index of the commitment to look up. * @returns The UTXO changes. */ - async getUtxoChangesByIndex(slot: SlotIndex): Promise { + async getUtxoChangesBySlot(slot: SlotIndex): Promise { const response = await this.methodHandler.callMethod({ - name: 'getUtxoChangesByIndex', + name: 'getUtxoChangesBySlot', data: { slot, }, @@ -648,16 +678,17 @@ export class Client { } /** - * Get all full UTXO changes of a given slot by commitment index. + * Get all full UTXO changes of a given slot by its index. + * GET /api/core/v3/commitments/by-slot/{slot}/utxo-changes/full * * @param slot Index of the commitment to look up. * @returns The UTXO changes. */ - async getUtxoChangesFullByIndex( + async getUtxoChangesFullBySlot( slot: SlotIndex, ): Promise { const response = await this.methodHandler.callMethod({ - name: 'getUtxoChangesFullByIndex', + name: 'getUtxoChangesFullBySlot', data: { slot, }, @@ -665,135 +696,184 @@ export class Client { return JSON.parse(response).payload; } + // High level API routes. + /** - * Convert a hex encoded address to a Bech32 encoded address. - * - * @param hex The hexadecimal string representation of an address. - * @param bech32Hrp The Bech32 HRP (human readable part) to be used. - * @returns The corresponding Bech32 address. + * Fetch OutputResponse from given output IDs. Requests are sent in parallel. */ - async hexToBech32( - hex: HexEncodedString, - bech32Hrp?: string, - ): Promise { + async getOutputs(outputIds: OutputId[]): Promise { const response = await this.methodHandler.callMethod({ - name: 'hexToBech32', + name: 'getOutputs', data: { - hex, - bech32Hrp, + outputIds, }, }); - return JSON.parse(response).payload; + const parsed = JSON.parse(response) as Response; + return plainToInstance(OutputResponse, parsed.payload); } /** - * Transforms an account id to a bech32 encoded address. + * Get outputs from provided output IDs (requests are sent + * in parallel and errors are ignored, can be useful for spent outputs) * - * @param accountId An account ID. - * @param bech32Hrp The Bech32 HRP (human readable part) to be used. - * @returns The corresponding Bech32 address. + * @param outputIds An array of output IDs. + * @returns An array of corresponding output responses. */ - async accountIdToBech32( - accountId: AccountId, - bech32Hrp?: string, - ): Promise { + async getOutputsIgnoreNotFound( + outputIds: OutputId[], + ): Promise { const response = await this.methodHandler.callMethod({ - name: 'accountIdToBech32', + name: 'getOutputsIgnoreNotFound', data: { - accountId, - bech32Hrp, + outputIds, }, }); - return JSON.parse(response).payload; + const parsed = JSON.parse(response) as Response; + return plainToInstance(OutputResponse, parsed.payload); } /** - * Convert an NFT ID to a Bech32 encoded address. + * Find blocks by their IDs. * - * @param nftId An NFT ID. - * @param bech32Hrp The Bech32 HRP (human readable part) to be used. - * @returns The corresponding Bech32 address. + * @param blockIds An array of `BlockId`s. + * @returns An array of corresponding blocks. */ - async nftIdToBech32( - nftId: NftId, - bech32Hrp?: string, - ): Promise { + async findBlocks(blockIds: BlockId[]): Promise { const response = await this.methodHandler.callMethod({ - name: 'nftIdToBech32', + name: 'findBlocks', data: { - nftId, - bech32Hrp, + blockIds, }, }); - return JSON.parse(response).payload; + const parsed = JSON.parse(response) as Response; + return parsed.payload.map((p) => parseBlock(p)); } /** - * Convert a hex encoded public key to a Bech32 encoded address. + * Find inputs from addresses for a given amount (useful for offline signing). * - * @param hex The hexadecimal string representation of a public key. - * @param bech32Hrp The Bech32 HRP (human readable part) to be used. - * @returns The corresponding Bech32 address. + * @param addresses A list of included addresses. + * @param amount The amount to find inputs for. + * @returns An array of UTXO inputs. */ - async hexPublicKeyToBech32Address( - hex: HexEncodedString, - bech32Hrp?: string, - ): Promise { + async findInputs(addresses: string[], amount: u64): Promise { const response = await this.methodHandler.callMethod({ - name: 'hexPublicKeyToBech32Address', + name: 'findInputs', data: { - hex, - bech32Hrp, + addresses, + amount: Number(amount), }, }); - return JSON.parse(response).payload; + const parsed = JSON.parse(response) as Response; + return plainToInstance(UTXOInput, parsed.payload); } + // Other routes. + /** - * Get outputs from provided output IDs (requests are sent - * in parallel and errors are ignored, can be useful for spent outputs) + * Build an unsigned block. * - * @param outputIds An array of output IDs. - * @returns An array of corresponding output responses. + * @param issuerId The identifier of the block issuer account. + * @param payload The payload to post. + * @returns The block ID followed by the block containing the payload. */ - async getOutputsIgnoreErrors( - outputIds: OutputId[], - ): Promise { + async buildBasicBlock( + issuerId: AccountId, + payload?: Payload, + ): Promise { const response = await this.methodHandler.callMethod({ - name: 'getOutputsIgnoreErrors', + name: 'buildBasicBlock', data: { - outputIds, + issuerId, + payload, }, }); - const parsed = JSON.parse(response) as Response; - return plainToInstance(OutputResponse, parsed.payload); + + const parsed = JSON.parse(response) as Response; + return parseUnsignedBlock(parsed.payload); } /** - * Find blocks by their IDs. + * Get a node candidate from the healthy node pool. + */ + async getNode(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getNode', + }); + + return JSON.parse(response).payload; + } + + /** + * Get the ID of the network the node is connected to. + */ + async getNetworkId(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getNetworkId', + }); + + return JSON.parse(response).payload; + } + + /** + * Get the Bech32 HRP (human readable part) of the network the node is connected to. + */ + async getBech32Hrp(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getBech32Hrp', + }); + + return JSON.parse(response).payload; + } + + /** + * Get the token supply. + */ + async getTokenSupply(): Promise { + return BigInt((await this.getProtocolParameters()).tokenSupply); + } + + /** + * Get the protocol parameters. + */ + async getProtocolParameters(): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getProtocolParameters', + }); + + return JSON.parse(response).payload; + } + + /** + * Converts an address to its bech32 representation * - * @param blockIds An array of `BlockId`s. - * @returns An array of corresponding blocks. + * @param address An address. + * @param bech32Hrp The Bech32 HRP (human readable part) to be used. + * @returns The corresponding Bech32 address. */ - async findBlocks(blockIds: BlockId[]): Promise { + async addressToBech32( + address: Address, + bech32Hrp?: string, + ): Promise { const response = await this.methodHandler.callMethod({ - name: 'findBlocks', + name: 'addressToBech32', data: { - blockIds, + address, + bech32Hrp, }, }); - const parsed = JSON.parse(response) as Response; - return parsed.payload.map((p) => parseBlock(p)); + + return JSON.parse(response).payload; } /** * Return the unhealthy nodes. */ - async unhealthyNodes(): Promise> { + async unhealthyNodes(): Promise> { const response = await this.methodHandler.callMethod({ name: 'unhealthyNodes', }); @@ -918,6 +998,7 @@ export class Client { output, }, }); + return JSON.parse(response).payload; } @@ -980,7 +1061,7 @@ export class Client { */ async outputIds( queryParameters: OutputQueryParameters, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'outputIds', data: { @@ -996,7 +1077,7 @@ export class Client { */ async basicOutputIds( queryParameters: BasicOutputQueryParameters, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'basicOutputIds', data: { @@ -1015,7 +1096,7 @@ export class Client { */ async accountOutputIds( queryParameters: AccountOutputQueryParameters, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'accountOutputIds', data: { @@ -1051,7 +1132,7 @@ export class Client { */ async anchorOutputIds( queryParameters: AnchorOutputQueryParameters, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'anchorOutputIds', data: { @@ -1087,7 +1168,7 @@ export class Client { */ async delegationOutputIds( queryParameters: DelegationOutputQueryParameters, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'delegationOutputIds', data: { @@ -1123,7 +1204,7 @@ export class Client { */ async foundryOutputIds( queryParameters: FoundryOutputQueryParameters, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'foundryOutputIds', data: { @@ -1159,7 +1240,7 @@ export class Client { */ async nftOutputIds( queryParameters: NftOutputQueryParameters, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'nftOutputIds', data: { diff --git a/bindings/nodejs/lib/logger.ts b/bindings/nodejs/lib/logger.ts index 16c8e1128f..8f6ab02bad 100644 --- a/bindings/nodejs/lib/logger.ts +++ b/bindings/nodejs/lib/logger.ts @@ -1,15 +1,15 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { ILoggerConfig } from './types/logger-config'; +import type { LoggerConfig } from './types/logger-config'; import { initLogger as initLoggerBinding } from './bindings'; -const defaultLoggerConfig: ILoggerConfig = { +const defaultLoggerConfig: LoggerConfig = { colorEnabled: true, name: './iota-sdk.log', levelFilter: 'debug', }; /** Initialize logger, if no arguments are provided a default config will be used. */ -export const initLogger = (config: ILoggerConfig = defaultLoggerConfig) => +export const initLogger = (config: LoggerConfig = defaultLoggerConfig) => initLoggerBinding(JSON.stringify(config)); diff --git a/bindings/nodejs/lib/secret_manager/secret-manager.ts b/bindings/nodejs/lib/secret_manager/secret-manager.ts index 82196bd6c5..3f65aa864d 100644 --- a/bindings/nodejs/lib/secret_manager/secret-manager.ts +++ b/bindings/nodejs/lib/secret_manager/secret-manager.ts @@ -3,7 +3,7 @@ import { SecretManagerMethodHandler } from './secret-manager-method-handler'; import type { - IGenerateAddressesOptions, + GenerateAddressesOptions, PreparedTransactionData, LedgerNanoStatus, } from '../types/client'; @@ -21,6 +21,7 @@ import { UnsignedBlock, Block, parseBlock, + Bech32Address, } from '../types'; import { plainToInstance } from 'class-transformer'; @@ -44,14 +45,14 @@ export class SecretManager { } /** - * Generate Ed25519 addresses. + * Generate multiple Ed25519 addresses at once. * * @param generateAddressesOptions Options to generate addresses. * @returns An array of generated addresses. */ async generateEd25519Addresses( - generateAddressesOptions: IGenerateAddressesOptions, - ): Promise { + generateAddressesOptions: GenerateAddressesOptions, + ): Promise { const response = await this.methodHandler.callMethod({ name: 'generateEd25519Addresses', data: { @@ -69,7 +70,7 @@ export class SecretManager { * @returns An array of generated addresses. */ async generateEvmAddresses( - generateAddressesOptions: IGenerateAddressesOptions, + generateAddressesOptions: GenerateAddressesOptions, ): Promise { const response = await this.methodHandler.callMethod({ name: 'generateEvmAddresses', diff --git a/bindings/nodejs/lib/types/block/output/feature.ts b/bindings/nodejs/lib/types/block/output/feature.ts index 1d56e6d08a..1f1e1ade19 100644 --- a/bindings/nodejs/lib/types/block/output/feature.ts +++ b/bindings/nodejs/lib/types/block/output/feature.ts @@ -13,6 +13,11 @@ import { EpochIndex } from '../../block/slot'; import { NativeToken } from '../../models/native-token'; import { HexEncodedString } from '../../utils/hex-encoding'; +/** + * Printable ASCII characters. + */ +export declare type PrintableASCII = string; + /** * All of the feature block types. */ @@ -88,14 +93,30 @@ class IssuerFeature extends Feature { */ class MetadataFeature extends Feature { /** Defines metadata (arbitrary binary data) that will be stored in the output. */ - readonly data: string; + readonly entries: { [key: PrintableASCII]: HexEncodedString }; /** - * @param data The metadata stored with the feature. + * @param entries The metadata stored with the feature. */ - constructor(data: string) { + constructor(entries: { [key: PrintableASCII]: HexEncodedString }) { super(FeatureType.Metadata); - this.data = data; + this.entries = entries; + } +} + +/** + * A Metadata Feature that can only be changed by the State Controller. + */ +class StateMetadataFeature extends Feature { + /** Defines metadata (arbitrary binary data) that will be stored in the output. */ + readonly entries: { [key: PrintableASCII]: HexEncodedString }; + + /** + * @param entries The metadata stored with the feature. + */ + constructor(entries: { [key: PrintableASCII]: HexEncodedString }) { + super(FeatureType.StateMetadata); + this.entries = entries; } } @@ -213,6 +234,7 @@ const FeatureDiscriminator = { { value: SenderFeature, name: FeatureType.Sender as any }, { value: IssuerFeature, name: FeatureType.Issuer as any }, { value: MetadataFeature, name: FeatureType.Metadata as any }, + { value: StateMetadataFeature, name: FeatureType.StateMetadata as any }, { value: TagFeature, name: FeatureType.Tag as any }, { value: NativeTokenFeature, name: FeatureType.NativeToken as any }, { value: BlockIssuerFeature, name: FeatureType.BlockIssuer as any }, @@ -227,6 +249,7 @@ export { SenderFeature, IssuerFeature, MetadataFeature, + StateMetadataFeature, TagFeature, NativeTokenFeature, BlockIssuerFeature, diff --git a/bindings/nodejs/lib/types/block/output/irc-27.ts b/bindings/nodejs/lib/types/block/output/irc-27.ts index 19825e3197..86f2cf20c9 100644 --- a/bindings/nodejs/lib/types/block/output/irc-27.ts +++ b/bindings/nodejs/lib/types/block/output/irc-27.ts @@ -88,7 +88,7 @@ class Irc27Metadata { } asFeature(): MetadataFeature { - return new MetadataFeature(this.asHex()); + return new MetadataFeature({ 'irc-27': this.asHex() }); } } diff --git a/bindings/nodejs/lib/types/block/output/irc-30.ts b/bindings/nodejs/lib/types/block/output/irc-30.ts index daba7224a7..ea9e40868d 100644 --- a/bindings/nodejs/lib/types/block/output/irc-30.ts +++ b/bindings/nodejs/lib/types/block/output/irc-30.ts @@ -61,7 +61,7 @@ class Irc30Metadata { } asFeature(): MetadataFeature { - return new MetadataFeature(this.asHex()); + return new MetadataFeature({ 'irc-30': this.asHex() }); } } diff --git a/bindings/nodejs/lib/types/block/output/output.ts b/bindings/nodejs/lib/types/block/output/output.ts index c72e96dd8c..0637133718 100644 --- a/bindings/nodejs/lib/types/block/output/output.ts +++ b/bindings/nodejs/lib/types/block/output/output.ts @@ -14,6 +14,7 @@ import { TokenScheme, TokenSchemeDiscriminator } from './token-scheme'; import { AccountId, NftId, AnchorId, DelegationId } from '../id'; import { EpochIndex } from '../../block/slot'; import { NativeToken } from '../../models/native-token'; +import { Address, AddressDiscriminator, AccountAddress } from '../address'; export type OutputId = HexEncodedString; @@ -344,7 +345,10 @@ class DelegationOutput extends Output { /** * The Account ID of the validator to which this output is delegating. */ - readonly validatorId: AccountId; + @Type(() => Address, { + discriminator: AddressDiscriminator, + }) + readonly validatorAddress: Address; /** * The index of the first epoch for which this output delegates. */ @@ -365,7 +369,7 @@ class DelegationOutput extends Output { * @param amount The amount of the output. * @param delegatedAmount The amount of delegated coins. * @param delegationId Unique identifier of the Delegation Output, which is the BLAKE2b-256 hash of the Output ID that created it. - * @param validatorId The Account ID of the validator to which this output is delegating. + * @param validatorAddress The Account address of the validator to which this output is delegating. * @param startEpoch The index of the first epoch for which this output delegates. * @param endEpoch The index of the last epoch for which this output delegates. * @param unlockConditions The unlock conditions of the output. @@ -374,7 +378,7 @@ class DelegationOutput extends Output { amount: u64, delegatedAmount: u64, delegationId: DelegationId, - validatorId: AccountId, + validatorAddress: AccountAddress, startEpoch: EpochIndex, endEpoch: EpochIndex, unlockConditions: UnlockCondition[], @@ -382,7 +386,7 @@ class DelegationOutput extends Output { super(OutputType.Delegation, amount); this.delegatedAmount = delegatedAmount; this.delegationId = delegationId; - this.validatorId = validatorId; + this.validatorAddress = validatorAddress; this.startEpoch = startEpoch; this.endEpoch = endEpoch; this.unlockConditions = unlockConditions; diff --git a/bindings/nodejs/lib/types/client/bridge/client.ts b/bindings/nodejs/lib/types/client/bridge/client.ts index 5bd79f7de2..80252b747f 100644 --- a/bindings/nodejs/lib/types/client/bridge/client.ts +++ b/bindings/nodejs/lib/types/client/bridge/client.ts @@ -1,10 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { - Bip44, - SecretManagerType, -} from '../../secret_manager/secret-manager'; import type { AccountId, Block, @@ -18,8 +14,9 @@ import type { Payload, SlotIndex, SlotCommitmentId, + EpochIndex, + Address, } from '../../block'; -import type { PreparedTransactionData } from '../prepared-transaction-data'; import type { AccountOutputQueryParameters, AnchorOutputQueryParameters, @@ -29,7 +26,7 @@ import type { NftOutputQueryParameters, OutputQueryParameters, } from '../query-parameters'; -import type { IAuth } from '../network'; +import type { Auth } from '../network'; import type { BasicOutputBuilderParams } from '../output_builder_params/basic-output-params'; import type { AccountOutputBuilderParams } from '../output_builder_params/account-output-params'; import type { FoundryOutputBuilderParams } from '../output_builder_params/foundry-output-params'; @@ -37,38 +34,36 @@ import type { NftOutputBuilderParams } from '../output_builder_params/nft-output import { HexEncodedString } from '../../utils'; import { TransactionId } from '../..'; -export interface __GetInfoMethod__ { - name: 'getInfo'; -} +// Node routes. -export interface __GetOutputMethod__ { - name: 'getOutput'; +export interface __GetHealthMethod__ { + name: 'getHealth'; data: { - outputId: OutputId; + url: string; }; } -export interface __GetOutputsMethod__ { - name: 'getOutputs'; - data: { - outputIds: OutputId[]; - }; +export interface __GetRoutesMethod__ { + name: 'getRoutes'; } -export interface __PostBlockMethod__ { - name: 'postBlock'; +export interface __GetNodeInfoMethod__ { + name: 'getNodeInfo'; +} + +export interface __GetInfoMethod__ { + name: 'getInfo'; data: { - block: Block; + url: string; + auth?: Auth; }; } -export interface __GetTipsMethod__ { - name: 'getTips'; +export interface __GetNetworkMetricsMethod__ { + name: 'getNetworkMetrics'; } -export interface __GetNetworkInfoMethod__ { - name: 'getNetworkInfo'; -} +// Accounts routes. export interface __GetAccountCongestionMethod__ { name: 'getAccountCongestion'; @@ -78,14 +73,18 @@ export interface __GetAccountCongestionMethod__ { }; } -export interface __GetRewardsMethod__ { - name: 'getRewards'; +// Rewards routes. + +export interface __GetOutputManaRewardsMethod__ { + name: 'getOutputManaRewards'; data: { outputId: OutputId; slotIndex?: SlotIndex; }; } +// Validators routes. + export interface __GetValidatorsMethod__ { name: 'getValidators'; data: { @@ -101,102 +100,104 @@ export interface __GetValidatorMethod__ { }; } -export interface __GetBlockMethod__ { - name: 'getBlock'; +// Committee routes. + +export interface __GetCommitteeMethod__ { + name: 'getCommittee'; data: { - blockId: BlockId; + epochIndex?: EpochIndex; }; } -export interface __GetBlockMetadataMethod__ { - name: 'getBlockMetadata'; +// Blocks routes. + +export interface __GetIssuanceMethod__ { + name: 'getIssuance'; +} + +export interface __GetBlockMethod__ { + name: 'getBlock'; data: { blockId: BlockId; }; } -export interface __GetBlockWithMetadataMethod__ { - name: 'getBlockWithMetadata'; +export interface __GetBlockRawMethod__ { + name: 'getBlockRaw'; data: { blockId: BlockId; }; } -export interface __FindInputsMethod__ { - name: 'findInputs'; +export interface __PostBlockMethod__ { + name: 'postBlock'; data: { - addresses: string[]; - amount: number; + block: Block; }; } -export interface __SignTransactionMethod__ { - name: 'signTransaction'; +export interface __PostBlockRawMethod__ { + name: 'postBlockRaw'; data: { - secretManager: SecretManagerType; - preparedTransactionData: PreparedTransactionData; + blockBytes: Uint8Array; }; } -export interface __SignatureUnlockMethod__ { - name: 'signatureUnlock'; +export interface __GetBlockMetadataMethod__ { + name: 'getBlockMetadata'; data: { - secretManager: SecretManagerType; - transactionSigningHash: HexEncodedString; - chain: Bip44; + blockId: BlockId; }; } -export interface __BuildBasicBlockMethod__ { - name: 'buildBasicBlock'; +export interface __GetBlockWithMetadataMethod__ { + name: 'getBlockWithMetadata'; data: { - issuerId: AccountId; - payload?: Payload; + blockId: BlockId; }; } -export interface __GetNodeMethod__ { - name: 'getNode'; -} - -export interface __GetNetworkIdMethod__ { - name: 'getNetworkId'; -} +// UTXO routes. -export interface __GetBech32HrpMethod__ { - name: 'getBech32Hrp'; +export interface __GetOutputMethod__ { + name: 'getOutput'; + data: { + outputId: OutputId; + }; } -export interface __GetProtocolParametersMethod__ { - name: 'getProtocolParameters'; +export interface __GetOutputRawMethod__ { + name: 'getOutputRaw'; + data: { + outputId: OutputId; + }; } -export interface __GetHealthMethod__ { - name: 'getHealth'; +export interface __GetOutputMetadataMethod__ { + name: 'getOutputMetadata'; data: { - url: string; + outputId: OutputId; }; } -export interface __GetNodeInfoMethod__ { - name: 'getNodeInfo'; +export interface __GetOutputWithMetadataMethod__ { + name: 'getOutputWithMetadata'; data: { - url: string; - auth?: IAuth; + outputId: OutputId; }; } -export interface __PostBlockRawMethod__ { - name: 'postBlockRaw'; +export interface __GetOutputsMethod__ { + name: 'getOutputs'; data: { - block: Block; + outputIds: OutputId[]; }; } -export interface __GetBlockRawMethod__ { - name: 'getBlockRaw'; +export interface __GetOutputsIgnoreNotFoundMethod__ { + name: 'getOutputsIgnoreNotFound'; data: { - blockId: BlockId; + outputIds: OutputId[]; }; } @@ -207,6 +208,13 @@ export interface __GetIncludedBlockMethod__ { }; } +export interface __GetIncludedBlockRawMethod__ { + name: 'getIncludedBlockRaw'; + data: { + transactionId: TransactionId; + }; +} + export interface __GetIncludedBlockMetadataMethod__ { name: 'getIncludedBlockMetadata'; data: { @@ -221,6 +229,8 @@ export interface __GetTransactionMetadataMethod__ { }; } +// Commitments routes. + export interface __GetCommitmentMethod__ { name: 'getCommitment'; data: { @@ -228,6 +238,13 @@ export interface __GetCommitmentMethod__ { }; } +export interface __GetCommitmentRawMethod__ { + name: 'getCommitmentRaw'; + data: { + commitmentId: SlotCommitmentId; + }; +} + export interface __GetUtxoChangesMethod__ { name: 'getUtxoChanges'; data: { @@ -242,63 +259,73 @@ export interface __GetUtxoChangesFullMethod__ { }; } -export interface __GetCommitmentByIndexMethod__ { - name: 'getCommitmentByIndex'; +export interface __GetCommitmentBySlotMethod__ { + name: 'getCommitmentBySlot'; data: { slot: SlotIndex; }; } -export interface __GetUtxoChangesByIndexMethod__ { - name: 'getUtxoChangesByIndex'; +export interface __GetCommitmentBySlotRawMethod__ { + name: 'getCommitmentBySlotRaw'; data: { slot: SlotIndex; }; } -export interface __GetUtxoChangesFullByIndexMethod__ { - name: 'getUtxoChangesFullByIndex'; +export interface __GetUtxoChangesBySlotMethod__ { + name: 'getUtxoChangesBySlot'; data: { slot: SlotIndex; }; } -export interface __HexToBech32Method__ { - name: 'hexToBech32'; +export interface __GetUtxoChangesFullBySlotMethod__ { + name: 'getUtxoChangesFullBySlot'; data: { - hex: HexEncodedString; - bech32Hrp?: string; + slot: SlotIndex; }; } -export interface __AccountIdToBech32Method__ { - name: 'accountIdToBech32'; +// Other routes. + +export interface __FindInputsMethod__ { + name: 'findInputs'; data: { - accountId: AccountId; - bech32Hrp?: string; + addresses: string[]; + amount: number; }; } -export interface __NftIdToBech32Method__ { - name: 'nftIdToBech32'; +export interface __BuildBasicBlockMethod__ { + name: 'buildBasicBlock'; data: { - nftId: NftId; - bech32Hrp?: string; + issuerId: AccountId; + payload?: Payload; }; } -export interface __HexPublicKeyToBech32AddressMethod__ { - name: 'hexPublicKeyToBech32Address'; - data: { - hex: HexEncodedString; - bech32Hrp?: string; - }; +export interface __GetNodeMethod__ { + name: 'getNode'; +} + +export interface __GetNetworkIdMethod__ { + name: 'getNetworkId'; +} + +export interface __GetBech32HrpMethod__ { + name: 'getBech32Hrp'; } -export interface __GetOutputsIgnoreErrorsMethod__ { - name: 'getOutputsIgnoreErrors'; +export interface __GetProtocolParametersMethod__ { + name: 'getProtocolParameters'; +} + +export interface __AddressToBech32Method__ { + name: 'addressToBech32'; data: { - outputIds: OutputId[]; + address: Address; + bech32Hrp?: string; }; } diff --git a/bindings/nodejs/lib/types/client/bridge/index.ts b/bindings/nodejs/lib/types/client/bridge/index.ts index 743c428eda..560f6940e3 100644 --- a/bindings/nodejs/lib/types/client/bridge/index.ts +++ b/bindings/nodejs/lib/types/client/bridge/index.ts @@ -3,22 +3,27 @@ import type { __GetInfoMethod__, + __GetRoutesMethod__, __GetOutputIdsMethod__, __GetBasicOutputIdsMethod__, __GetOutputMethod__, + __GetOutputRawMethod__, __GetOutputsMethod__, + __GetOutputsIgnoreNotFoundMethod__, + __GetOutputMetadataMethod__, + __GetOutputWithMetadataMethod__, __PostBlockMethod__, - __GetTipsMethod__, - __GetNetworkInfoMethod__, + __PostBlockRawMethod__, __GetAccountCongestionMethod__, - __GetRewardsMethod__, + __GetOutputManaRewardsMethod__, + __GetCommitteeMethod__, __GetValidatorsMethod__, __GetValidatorMethod__, + __GetIssuanceMethod__, __GetBlockMethod__, __GetBlockMetadataMethod__, __GetBlockWithMetadataMethod__, __FindInputsMethod__, - __SignTransactionMethod__, __BuildBasicBlockMethod__, __GetNodeMethod__, __GetNetworkIdMethod__, @@ -26,21 +31,21 @@ import type { __GetProtocolParametersMethod__, __GetHealthMethod__, __GetNodeInfoMethod__, - __PostBlockRawMethod__, + __GetNetworkMetricsMethod__, __GetBlockRawMethod__, __GetIncludedBlockMethod__, + __GetIncludedBlockRawMethod__, __GetIncludedBlockMetadataMethod__, __GetTransactionMetadataMethod__, __GetCommitmentMethod__, + __GetCommitmentRawMethod__, __GetUtxoChangesMethod__, __GetUtxoChangesFullMethod__, - __GetCommitmentByIndexMethod__, - __GetUtxoChangesByIndexMethod__, - __GetUtxoChangesFullByIndexMethod__, - __HexToBech32Method__, - __AccountIdToBech32Method__, - __NftIdToBech32Method__, - __HexPublicKeyToBech32AddressMethod__, + __GetCommitmentBySlotMethod__, + __GetCommitmentBySlotRawMethod__, + __GetUtxoChangesBySlotMethod__, + __GetUtxoChangesFullBySlotMethod__, + __AddressToBech32Method__, __AccountOutputIdsMethod__, __AccountOutputIdMethod__, __AnchorOutputIdsMethod__, @@ -51,7 +56,6 @@ import type { __FoundryOutputIdMethod__, __NftOutputIdsMethod__, __NftOutputIdMethod__, - __GetOutputsIgnoreErrorsMethod__, __FindBlocksMethod__, __UnhealthyNodesMethod__, __BuildBasicOutputMethod__, @@ -59,7 +63,6 @@ import type { __BuildFoundryOutputMethod__, __BuildNftOutputMethod__, __ClearListenersMethod__, - __SignatureUnlockMethod__, __ComputeMinimumOutputAmountMethod__, __RequestFundsFromFaucetMethod__, __CallPluginRouteMethod__, @@ -68,23 +71,27 @@ import type { export type __ClientMethods__ = | __GetInfoMethod__ - | __GetOutputMethod__ + | __GetRoutesMethod__ | __GetOutputIdsMethod__ | __GetBasicOutputIdsMethod__ + | __GetOutputMethod__ + | __GetOutputRawMethod__ | __GetOutputsMethod__ + | __GetOutputsIgnoreNotFoundMethod__ + | __GetOutputMetadataMethod__ + | __GetOutputWithMetadataMethod__ | __PostBlockMethod__ - | __GetTipsMethod__ - | __GetNetworkInfoMethod__ + | __PostBlockRawMethod__ | __GetAccountCongestionMethod__ - | __GetRewardsMethod__ + | __GetOutputManaRewardsMethod__ + | __GetCommitteeMethod__ | __GetValidatorsMethod__ | __GetValidatorMethod__ + | __GetIssuanceMethod__ | __GetBlockMethod__ | __GetBlockMetadataMethod__ | __GetBlockWithMetadataMethod__ | __FindInputsMethod__ - | __SignTransactionMethod__ - | __SignatureUnlockMethod__ | __BuildBasicBlockMethod__ | __GetNodeMethod__ | __GetNetworkIdMethod__ @@ -92,21 +99,21 @@ export type __ClientMethods__ = | __GetProtocolParametersMethod__ | __GetHealthMethod__ | __GetNodeInfoMethod__ - | __PostBlockRawMethod__ + | __GetNetworkMetricsMethod__ | __GetBlockRawMethod__ | __GetIncludedBlockMethod__ + | __GetIncludedBlockRawMethod__ | __GetIncludedBlockMetadataMethod__ | __GetTransactionMetadataMethod__ | __GetCommitmentMethod__ + | __GetCommitmentRawMethod__ | __GetUtxoChangesMethod__ | __GetUtxoChangesFullMethod__ - | __GetCommitmentByIndexMethod__ - | __GetUtxoChangesByIndexMethod__ - | __GetUtxoChangesFullByIndexMethod__ - | __HexToBech32Method__ - | __AccountIdToBech32Method__ - | __NftIdToBech32Method__ - | __HexPublicKeyToBech32AddressMethod__ + | __GetCommitmentBySlotMethod__ + | __GetCommitmentBySlotRawMethod__ + | __GetUtxoChangesBySlotMethod__ + | __GetUtxoChangesFullBySlotMethod__ + | __AddressToBech32Method__ | __AccountOutputIdsMethod__ | __AccountOutputIdMethod__ | __AnchorOutputIdsMethod__ @@ -117,7 +124,6 @@ export type __ClientMethods__ = | __FoundryOutputIdMethod__ | __NftOutputIdsMethod__ | __NftOutputIdMethod__ - | __GetOutputsIgnoreErrorsMethod__ | __FindBlocksMethod__ | __UnhealthyNodesMethod__ | __BuildBasicOutputMethod__ diff --git a/bindings/nodejs/lib/types/client/burn.ts b/bindings/nodejs/lib/types/client/burn.ts index 6cfdf9da23..2cc60d5a6a 100644 --- a/bindings/nodejs/lib/types/client/burn.ts +++ b/bindings/nodejs/lib/types/client/burn.ts @@ -2,16 +2,28 @@ // SPDX-License-Identifier: Apache-2.0 import { u256 } from '../utils'; -import { AccountId, FoundryId, NftId, TokenId } from '../block/id'; +import { + AccountId, + DelegationId, + FoundryId, + NftId, + TokenId, +} from '../block/id'; /** A DTO for [`Burn`] */ export interface Burn { + /** Burn initial excess mana (only from inputs/outputs that have been specified manually) */ + mana?: boolean; + /** Burn generated mana */ + generatedMana?: boolean; /** Accounts to burn */ accounts?: AccountId[]; - /** NFTs to burn */ - nfts?: NftId[]; /** Foundries to burn */ foundries?: FoundryId[]; + /** NFTs to burn */ + nfts?: NftId[]; + /** Delegations to burn */ + delegations?: DelegationId[]; /** Amounts of native tokens to burn */ nativeTokens?: Map; } diff --git a/bindings/nodejs/lib/types/client/client-options.ts b/bindings/nodejs/lib/types/client/client-options.ts index 2f4c5e1bbb..43b524e83e 100644 --- a/bindings/nodejs/lib/types/client/client-options.ts +++ b/bindings/nodejs/lib/types/client/client-options.ts @@ -1,17 +1,19 @@ -// Copyright 2021-2023 IOTA Stiftung +// Copyright 2021-2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { IMqttBrokerOptions, INetworkInfo, INode } from './network'; + +import { ProtocolParameters } from '../models/api/info/node-info-protocol'; +import type { MqttBrokerOptions, Node } from './network'; /** Options for the client builder */ -export interface IClientOptions { +export interface ClientOptions { /** Nodes which will be tried first for all requests */ - primaryNodes?: Array; + primaryNodes?: Array; /** A list of nodes. */ - nodes?: Array; + nodes?: Array; /** If the node health status should be ignored */ ignoreNodeHealth?: boolean; - /** Interval in which nodes will be checked for their sync status and the NetworkInfo gets updated */ - nodeSyncInterval?: IDuration; + /** Interval in which nodes will be checked for their sync status and the network info gets updated */ + nodeSyncInterval?: Duration; /** If node quorum is enabled. Will compare the responses from multiple nodes and only returns the * response if quorum_threshold of the nodes return the same one */ @@ -20,18 +22,20 @@ export interface IClientOptions { minQuorumSize?: number; /** % of nodes that have to return the same response so it gets accepted */ quorumThreshold?: number; - /** Data related to the used network */ - networkInfo?: INetworkInfo; + /** The User-Agent header for requests */ + userAgent?: string; /** Options for the MQTT broker */ - brokerOptions?: IMqttBrokerOptions; + brokerOptions?: MqttBrokerOptions; + /** Protocol parameters */ + protocolParameters?: ProtocolParameters; /** Timeout for API requests */ - apiTimeout?: IDuration; + apiTimeout?: Duration; /** The maximum parallel API requests. */ maxParallelApiRequests?: number; } /** Time duration */ -export interface IDuration { +export interface Duration { /** Seconds. */ secs: number; /** Nanoseconds. */ diff --git a/bindings/nodejs/lib/types/client/generate-addresses-options.ts b/bindings/nodejs/lib/types/client/generate-addresses-options.ts index 33e4eb2499..342baf257e 100644 --- a/bindings/nodejs/lib/types/client/generate-addresses-options.ts +++ b/bindings/nodejs/lib/types/client/generate-addresses-options.ts @@ -1,32 +1,34 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 import type { CoinType } from './constants'; -import type { IRange } from './range'; +import type { Range } from './range'; +// TODO: Rename (to GetAddressOptions) and refactor (move out range field), +// so we can use it for the single address generation method as well? /** * Input options for GenerateAddresses */ -export interface IGenerateAddressesOptions { +export interface GenerateAddressesOptions { coinType?: CoinType; accountIndex?: number; - range?: IRange; + range?: Range; /** * Bech32 human readable part */ bech32Hrp?: string; - options?: IGenerateAddressOptions; + options?: GenerateAddressOptions; } /** * Options provided to Generate Address */ -export interface IGenerateAddressOptions { +export interface GenerateAddressOptions { /** - * Internal addresses + * Whether to generate an internal address. */ - internal?: boolean; + internal: boolean; /** * Display the address on ledger devices. */ - ledgerNanoPrompt?: boolean; + ledgerNanoPrompt: boolean; } diff --git a/bindings/nodejs/lib/types/client/network.ts b/bindings/nodejs/lib/types/client/network.ts index 14d1570688..5205938171 100644 --- a/bindings/nodejs/lib/types/client/network.ts +++ b/bindings/nodejs/lib/types/client/network.ts @@ -1,8 +1,6 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { ProtocolParameters } from '../models/info'; - /** * Network types. */ @@ -16,7 +14,7 @@ export enum Network { /** * Basic Auth or JWT. */ -export interface IAuth { +export interface Auth { /** JWT authentication parameters. */ jwt?: string; /** Basic authentication parameters. */ @@ -26,7 +24,7 @@ export interface IAuth { /** * Options for the MQTT broker. */ -export interface IMqttBrokerOptions { +export interface MqttBrokerOptions { /** Whether the MQTT broker should be automatically disconnected when all topics are unsubscribed or not. */ automaticDisconnect?: boolean; /** Sets the timeout in seconds used for the MQTT operations. */ @@ -42,21 +40,13 @@ export interface IMqttBrokerOptions { /** * A node object for the client. */ -export interface INode { +export interface Node { /** The URL of the node. */ url: string; /** The authentication parameters. */ - auth?: IAuth; + auth?: Auth; /** Whether the node is disabled or not. */ disabled?: boolean; /** Whether the node is a permanode or not. */ permanode?: boolean; } - -/** - * Struct containing network related information - */ -export interface INetworkInfo { - /** Protocol parameters */ - protocolParameters: ProtocolParameters; -} diff --git a/bindings/nodejs/lib/types/client/nodeInfo.ts b/bindings/nodejs/lib/types/client/nodeInfo.ts index 54818426f4..5fa0ebeede 100644 --- a/bindings/nodejs/lib/types/client/nodeInfo.ts +++ b/bindings/nodejs/lib/types/client/nodeInfo.ts @@ -1,12 +1,12 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { INodeInfo } from '../models/info'; +import { InfoResponse } from '../models/api/info'; /** NodeInfo wrapper which contains the node info and the url from the node (useful when multiple nodes are used) */ -export interface INodeInfoWrapper { +export interface NodeInfoResponse { /** The node info */ - nodeInfo: INodeInfo; + info: InfoResponse; /** The url of the node */ url: string; } diff --git a/bindings/nodejs/lib/types/client/prepared-transaction-data.ts b/bindings/nodejs/lib/types/client/prepared-transaction-data.ts index 141662507e..cfdc7ad9e1 100644 --- a/bindings/nodejs/lib/types/client/prepared-transaction-data.ts +++ b/bindings/nodejs/lib/types/client/prepared-transaction-data.ts @@ -5,7 +5,7 @@ import { Type } from 'class-transformer'; import { Address, AddressDiscriminator } from '../block/address'; import { Output, OutputDiscriminator } from '../block/output/output'; import { Transaction } from '../block/payload/signed_transaction'; -import { IOutputMetadataResponse } from '../models/api'; +import { OutputMetadataResponse } from '../models/api'; import { Bip44 } from '../secret_manager'; import { HexEncodedString, NumericString } from '../utils'; @@ -45,7 +45,7 @@ export class InputSigningData { /** * The output metadata */ - outputMetadata!: IOutputMetadataResponse; + outputMetadata!: OutputMetadataResponse; /** * The chain derived from seed, only for ed25519 addresses */ diff --git a/bindings/nodejs/lib/types/client/range.ts b/bindings/nodejs/lib/types/client/range.ts index 8212a1f866..d0715f518a 100644 --- a/bindings/nodejs/lib/types/client/range.ts +++ b/bindings/nodejs/lib/types/client/range.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 /** A range with start and end values. */ -export interface IRange { +export interface Range { /** The start index. */ start: number; /** The end index. */ diff --git a/bindings/nodejs/lib/types/logger-config.ts b/bindings/nodejs/lib/types/logger-config.ts index eccf15e4d0..bddca4d189 100644 --- a/bindings/nodejs/lib/types/logger-config.ts +++ b/bindings/nodejs/lib/types/logger-config.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 /** Logger output configuration. */ -export interface ILoggerConfig { +export interface LoggerConfig { /** Name of an output file, or `stdout` for standard output.*/ name?: string; /** Log level filter of an output.*/ diff --git a/bindings/nodejs/lib/types/models/block-failure-reason.ts b/bindings/nodejs/lib/types/models/api/block-failure-reason.ts similarity index 100% rename from bindings/nodejs/lib/types/models/block-failure-reason.ts rename to bindings/nodejs/lib/types/models/api/block-failure-reason.ts diff --git a/bindings/nodejs/lib/types/models/api/block-id-response.ts b/bindings/nodejs/lib/types/models/api/block-id-response.ts deleted file mode 100644 index 47f17f48e1..0000000000 --- a/bindings/nodejs/lib/types/models/api/block-id-response.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { BlockId } from '../../block'; - -/** - * Block id response. - */ -export interface IBlockIdResponse { - /** - * The block id. - */ - blockId: BlockId; -} diff --git a/bindings/nodejs/lib/types/models/api/block-response.ts b/bindings/nodejs/lib/types/models/api/block-response.ts new file mode 100644 index 0000000000..1d0836fb1b --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/block-response.ts @@ -0,0 +1,43 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { BlockState } from '../state'; +import { BlockFailureReason } from './block-failure-reason'; +import { Block, BlockId } from '../../block'; +import { TransactionMetadataResponse } from './transaction-response'; + +/** + * Response from the metadata endpoint. + */ +export interface BlockMetadataResponse { + /** + * The block id. + */ + blockId: BlockId; + /** + * The block state. + */ + blockState: BlockState; + /** + * The block failure reason. + */ + blockFailureReason?: BlockFailureReason; + /** + * The metadata of the transaction in the block. + */ + transactionMetadata?: TransactionMetadataResponse; +} + +/** + * Response from the full endpoint. + */ +export interface BlockWithMetadataResponse { + /** + * The block. + */ + block: Block; + /** + * The block metadata. + */ + metadata: BlockMetadataResponse; +} diff --git a/bindings/nodejs/lib/types/models/api/committee-response.ts b/bindings/nodejs/lib/types/models/api/committee-response.ts new file mode 100644 index 0000000000..a659493dd6 --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/committee-response.ts @@ -0,0 +1,49 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { Bech32Address, EpochIndex } from '../../block'; +import { NumericString } from '../../utils'; + +/** + * Returns information of a validator (committee member). + */ +export interface CommitteeMember { + /** + * Account address of the validator. + */ + address: Bech32Address; + /** + * The total stake of the pool, including delegators. + */ + poolStake: NumericString; + /** + * The stake of a validator. + */ + validatorStake: NumericString; + /** + * The fixed cost of the validator, which it receives as part of its Mana rewards. + */ + fixedCost: NumericString; +} + +/** + * Returns the validator information of the committee. + */ +export interface CommitteeResponse { + /** + * The epoch index of the committee. + */ + epoch: EpochIndex; + /** + * The total amount of delegated and staked IOTA tokens in the selected committee. + */ + totalStake: NumericString; + /** + * The total amount of staked IOTA tokens in the selected committee. + */ + totalValidatorStake: NumericString; + /** + * The validators of the committee. + */ + committee: CommitteeMember[]; +} diff --git a/bindings/nodejs/lib/types/models/api/index.ts b/bindings/nodejs/lib/types/models/api/index.ts index 703fe9a5b0..e1c8aa9f6d 100644 --- a/bindings/nodejs/lib/types/models/api/index.ts +++ b/bindings/nodejs/lib/types/models/api/index.ts @@ -1,14 +1,21 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +export * from './info'; export * from './plugins'; - -export * from './block-id-response'; +export * from './block-failure-reason'; +export * from './block-response'; +export * from './committee-response'; export * from './congestion-response'; +export * from './issuance-response'; export * from './mana-rewards-response'; +export * from './network-metrics'; +export * from './output-id-proof'; export * from './output-metadata-response'; export * from './output-response'; export * from './response'; -export * from './tips-response'; -export * from './validators-response'; +export * from './routes-response'; +export * from './transaction-failure-reason'; +export * from './transaction-response'; export * from './utxo-changes-response'; +export * from './validators-response'; diff --git a/bindings/nodejs/lib/types/models/info/index.ts b/bindings/nodejs/lib/types/models/api/info/index.ts similarity index 74% rename from bindings/nodejs/lib/types/models/info/index.ts rename to bindings/nodejs/lib/types/models/api/info/index.ts index 4e5bdc3502..0a6f82288a 100644 --- a/bindings/nodejs/lib/types/models/info/index.ts +++ b/bindings/nodejs/lib/types/models/api/info/index.ts @@ -3,7 +3,5 @@ export * from './node-info'; export * from './node-info-base-token'; -export * from './node-info-metrics'; export * from './node-info-protocol'; export * from './node-info-status'; -export * from './routes-response'; diff --git a/bindings/nodejs/lib/types/models/info/node-info-base-token.ts b/bindings/nodejs/lib/types/models/api/info/node-info-base-token.ts similarity index 93% rename from bindings/nodejs/lib/types/models/info/node-info-base-token.ts rename to bindings/nodejs/lib/types/models/api/info/node-info-base-token.ts index daf6c721c1..640e6a444f 100644 --- a/bindings/nodejs/lib/types/models/info/node-info-base-token.ts +++ b/bindings/nodejs/lib/types/models/api/info/node-info-base-token.ts @@ -4,7 +4,7 @@ /** * The base token info of the node. */ -export interface INodeInfoBaseToken { +export interface BaseTokenResponse { /** * The base token name. */ diff --git a/bindings/nodejs/lib/types/models/info/node-info-protocol.ts b/bindings/nodejs/lib/types/models/api/info/node-info-protocol.ts similarity index 93% rename from bindings/nodejs/lib/types/models/info/node-info-protocol.ts rename to bindings/nodejs/lib/types/models/api/info/node-info-protocol.ts index 584a661cc8..117c37c651 100644 --- a/bindings/nodejs/lib/types/models/info/node-info-protocol.ts +++ b/bindings/nodejs/lib/types/models/api/info/node-info-protocol.ts @@ -1,22 +1,22 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { StorageScoreParameters } from '../storage-score'; -import { EpochIndex } from '../../block/slot'; -import { u64 } from '../../utils/type-aliases'; +import type { StorageScoreParameters } from '../../storage-score'; +import { EpochIndex } from '../../../block/slot'; +import { NumericString } from '../../../utils'; /** - * The Protocol Info. + * The Protocol Parameters response. */ -interface ProtocolInfo { - /** - * The start epoch of the set of protocol parameters. - */ - startEpoch: EpochIndex; +interface ProtocolParametersResponse { /** * The protocol parameters. */ parameters: ProtocolParameters; + /** + * The start epoch of the set of protocol parameters. + */ + startEpoch: EpochIndex; } /** @@ -54,7 +54,7 @@ interface ProtocolParameters { /** * Current supply of base token. */ - tokenSupply: u64; + tokenSupply: NumericString; /** * Genesis Slot defines the slot of the genesis. */ @@ -62,7 +62,7 @@ interface ProtocolParameters { /** * The genesis timestamp at which the slots start to count. */ - genesisUnixTimestamp: u64; + genesisUnixTimestamp: NumericString; /** * The duration of a slot, in seconds. */ @@ -145,11 +145,11 @@ interface RewardsParameters { /** * Decay Balancing Constant Exponent is the exponent used for calculation of the initial reward. */ - initialTargetRewardsRate: u64; + initialTargetRewardsRate: NumericString; /** * The rate of Mana rewards after the bootstrapping phase. */ - finalTargetRewardsRate: u64; + finalTargetRewardsRate: NumericString; /** * Pool Coefficient Exponent is the exponent used for shifting operation * in the pool rewards calculations. @@ -239,6 +239,10 @@ interface ManaParameters { * The scaling of ManaDecayFactorEpochsSum expressed as an exponent of 2. */ decayFactorEpochsSumExponent: number; + /** + * Decay factor for 1 year. + */ + annualDecayFactorPercentage: number; } /** @@ -248,15 +252,15 @@ interface CongestionControlParameters { /** * The minimum value of the reference Mana cost. */ - minReferenceManaCost: u64; + minReferenceManaCost: NumericString; /** * The increase step size of the reference Mana cost. */ - increase: u64; + increase: NumericString; /** * The decrease step size of the reference Mana cost. */ - decrease: u64; + decrease: NumericString; /** * The threshold for increasing the reference Mana cost. */ @@ -298,7 +302,7 @@ interface VersionSignalingParameters { } export { - ProtocolInfo, + ProtocolParametersResponse, ProtocolParameters, RewardsParameters, WorkScoreParameters, diff --git a/bindings/nodejs/lib/types/models/info/node-info-status.ts b/bindings/nodejs/lib/types/models/api/info/node-info-status.ts similarity index 92% rename from bindings/nodejs/lib/types/models/info/node-info-status.ts rename to bindings/nodejs/lib/types/models/api/info/node-info-status.ts index ff8d30c223..c9d36c88de 100644 --- a/bindings/nodejs/lib/types/models/info/node-info-status.ts +++ b/bindings/nodejs/lib/types/models/api/info/node-info-status.ts @@ -1,12 +1,12 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { SlotIndex, EpochIndex } from '../../block/slot'; +import { SlotIndex, EpochIndex } from '../../../block/slot'; /** * Response from the /info endpoint. */ -export interface INodeInfoStatus { +export interface StatusResponse { /** * Tells whether the node is healthy or not. */ diff --git a/bindings/nodejs/lib/types/models/api/info/node-info.ts b/bindings/nodejs/lib/types/models/api/info/node-info.ts new file mode 100644 index 0000000000..fbe4c1ecc3 --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/info/node-info.ts @@ -0,0 +1,31 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import type { BaseTokenResponse } from './node-info-base-token'; +import type { ProtocolParametersResponse } from './node-info-protocol'; +import type { StatusResponse } from './node-info-status'; +/** + * Response from the /info endpoint. + */ +export interface InfoResponse { + /** + * The name of the node. + */ + name: string; + /** + * The semantic version of the node. + */ + version: string; + /** + * The status of the node. + */ + status: StatusResponse; + /** + * The protocol parameters. + */ + protocolParameters: ProtocolParametersResponse[]; + /** + * The base token info of the node. + */ + baseToken: BaseTokenResponse; +} diff --git a/bindings/nodejs/lib/types/models/api/issuance-response.ts b/bindings/nodejs/lib/types/models/api/issuance-response.ts new file mode 100644 index 0000000000..c45b59e607 --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/issuance-response.ts @@ -0,0 +1,36 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { BlockId, SlotCommitment, SlotIndex } from '../../block'; +import { NumericString } from '../../utils'; + +/** + * Information that is used to attach a block in the network. + * Response of GET /api/core/v3/blocks/issuance + */ +export interface IssuanceBlockHeaderResponse { + /** + * Blocks that are strongly directly approved. + */ + strongParents: BlockId[]; + /** + * Latest issuing time of the returned parents. + */ + latestParentBlockIssuingTime: NumericString; + /** + * The slot index of the latest finalized slot. + */ + latestFinalizedSlot: SlotIndex; + /** + * The latest slot commitment. + */ + latestCommitment: SlotCommitment; + /** + * Blocks that are weakly directly approved. + */ + weakParents?: BlockId[]; + /** + * Blocks that are directly referenced to adjust opinion. + */ + shallowLikeParents?: BlockId[]; +} diff --git a/bindings/nodejs/lib/types/models/info/node-info-metrics.ts b/bindings/nodejs/lib/types/models/api/network-metrics.ts similarity index 76% rename from bindings/nodejs/lib/types/models/info/node-info-metrics.ts rename to bindings/nodejs/lib/types/models/api/network-metrics.ts index 4a1cf74b5e..ab783806f8 100644 --- a/bindings/nodejs/lib/types/models/info/node-info-metrics.ts +++ b/bindings/nodejs/lib/types/models/api/network-metrics.ts @@ -1,10 +1,10 @@ -// Copyright 2023 IOTA Stiftung +// Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 /** - * Response from the /info endpoint. + * Metrics information about the network. */ -export interface INodeInfoMetrics { +export interface NetworkMetricsResponse { /** * The current rate of new blocks per second. */ diff --git a/bindings/nodejs/lib/types/models/api/output-id-proof.ts b/bindings/nodejs/lib/types/models/api/output-id-proof.ts new file mode 100644 index 0000000000..fedab6ba4c --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/output-id-proof.ts @@ -0,0 +1,120 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +// Temp solution for not double parsing JSON +import { plainToInstance } from 'class-transformer'; +import { SlotIndex } from '../../block'; +import { HexEncodedString } from '../../utils'; + +/** + * OutputCommitmentProof types. + */ +export enum OutputCommitmentProofType { + /** Denotes a HashableNode. */ + HashableNode = 0, + /** Denotes a LeafHash. */ + LeafHash = 1, + /** Denotes a ValueHash. */ + ValueHash = 2, +} + +export abstract class OutputCommitmentProof { + readonly type: OutputCommitmentProofType; + + /** + * @param type The type of OutputCommitmentProof. + */ + constructor(type: OutputCommitmentProofType) { + this.type = type; + } + + /** + * Parse an OutputCommitmentProof from a plain JSON object. + */ + public static parse(data: any): OutputCommitmentProof { + if (data.type == OutputCommitmentProofType.HashableNode) { + return plainToInstance(HashableNode, data) as any as HashableNode; + } else if (data.type == OutputCommitmentProofType.LeafHash) { + return plainToInstance(LeafHash, data) as any as LeafHash; + } else if (data.type == OutputCommitmentProofType.ValueHash) { + return plainToInstance(ValueHash, data) as any as ValueHash; + } + throw new Error('Invalid JSON'); + } +} + +/** + * Contains the hashes of the left and right children of a node in the OutputCommitmentProof tree. + */ +export class HashableNode extends OutputCommitmentProof { + readonly l: OutputCommitmentProof; + readonly r: OutputCommitmentProof; + + /** + * @param l Output commitment proof of the left subtree. + * @param r Output commitment proof of the right subtree. + */ + constructor(l: OutputCommitmentProof, r: OutputCommitmentProof) { + super(OutputCommitmentProofType.HashableNode); + this.l = l; + this.r = r; + } +} + +/** + * Contains the hash of a leaf in the OutputCommitmentProof tree. + */ +export class LeafHash extends OutputCommitmentProof { + readonly hash: HexEncodedString; + + /** + * @param hash The hash of the leaf. + */ + constructor(hash: HexEncodedString) { + super(OutputCommitmentProofType.LeafHash); + this.hash = hash; + } +} + +/** + * Contains the hash of the value for which the OutputCommitmentProof is being computed. + */ +export class ValueHash extends OutputCommitmentProof { + readonly hash: HexEncodedString; + + /** + * @param hash The hash of the value. + */ + constructor(hash: HexEncodedString) { + super(OutputCommitmentProofType.ValueHash); + this.hash = hash; + } +} + +/** + * The proof of the output identifier. + */ +export class OutputIdProof { + readonly slot: SlotIndex; + readonly outputIndex: number; + readonly transactionCommitment: HexEncodedString; + readonly outputCommitmentProof: OutputCommitmentProof; + + /** + * @param slot The slot index of the output. + * @param outputIndex The index of the output within the corresponding transaction. + * @param transactionCommitment The commitment of the transaction that created the output. Hex-encoded with 0x prefix. + * @param outputCommitmentProof The proof of the output commitment. + */ + constructor( + slot: SlotIndex, + outputIndex: number, + transactionCommitment: HexEncodedString, + outputCommitmentProof: OutputCommitmentProof, + ) { + this.slot = slot; + this.outputIndex = outputIndex; + this.transactionCommitment = transactionCommitment; + this.outputCommitmentProof = outputCommitmentProof; + } +} diff --git a/bindings/nodejs/lib/types/models/api/output-metadata-response.ts b/bindings/nodejs/lib/types/models/api/output-metadata-response.ts index f0938fd0f9..baf9c63b4d 100644 --- a/bindings/nodejs/lib/types/models/api/output-metadata-response.ts +++ b/bindings/nodejs/lib/types/models/api/output-metadata-response.ts @@ -1,15 +1,51 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { TransactionId } from '../..'; +import { OutputIdProof, TransactionId } from '../..'; import { BlockId } from '../../block/id'; -import { OutputId } from '../../block/output'; +import { Output, OutputId } from '../../block/output'; import { SlotCommitmentId, SlotIndex } from '../../block/slot'; +/** + * Metadata of the output if it is included in the ledger. + */ +export interface OutputInclusionMetadata { + /** + * Slot in which the output was included. + */ + slot: SlotIndex; + /** + * Transaction ID that created the output. + */ + transactionId: TransactionId; + /** + * Commitment ID that includes the creation of the output. + */ + commitmentId?: SlotCommitmentId; +} + +/** + * Metadata of the output if it is marked as spent in the ledger. + */ +export interface OutputConsumptionMetadata { + /** + * Slot in which the output was spent. + */ + slot: SlotIndex; + /** + * Transaction ID that spent the output. + */ + transactionId: TransactionId; + /** + * Commitment ID that includes the spending of the output. + */ + commitmentId?: SlotCommitmentId; +} + /** * Metadata of an output. */ -export interface IOutputMetadataResponse { +export interface OutputMetadataResponse { /** * The ID of the output. */ @@ -21,11 +57,11 @@ export interface IOutputMetadataResponse { /** * Metadata of the output if it is included in the ledger. */ - included: IOutputInclusionMetadata; + included: OutputInclusionMetadata; /** * Metadata of the output if it is marked as spent in the ledger. */ - spent?: IOutputConsumptionMetadata; + spent?: OutputConsumptionMetadata; /** * Latest commitment ID of the node. */ @@ -33,37 +69,34 @@ export interface IOutputMetadataResponse { } /** - * Metadata of the output if it is included in the ledger. + * An output with its output id proof and its metadata. + * Response of GET /api/core/v3/outputs/{outputId}/full. */ -export interface IOutputInclusionMetadata { +export class OutputWithMetadataResponse { /** - * Slot in which the output was included. + * One of the possible output types. */ - slot: SlotIndex; + output!: Output; /** - * Transaction ID that created the output. + * The associated Output ID proof. */ - transactionId: TransactionId; + outputIdProof!: OutputIdProof; /** - * Commitment ID that includes the creation of the output. + * The metadata of the output. */ - commitmentId?: SlotCommitmentId; + metadata!: OutputMetadataResponse; } /** - * Metadata of the output if it is marked as spent in the ledger. + * An output and its metadata. */ -export interface IOutputConsumptionMetadata { - /** - * Slot in which the output was spent. - */ - slot: SlotIndex; +export class OutputWithMetadata { /** - * Transaction ID that spent the output. + * One of the possible output types. */ - transactionId: TransactionId; + output!: Output; /** - * Commitment ID that includes the spending of the output. + * The metadata of the output. */ - commitmentId?: SlotCommitmentId; + metadata!: OutputMetadataResponse; } diff --git a/bindings/nodejs/lib/types/models/api/output-response.ts b/bindings/nodejs/lib/types/models/api/output-response.ts index 81e4d10756..1a3c4fc53a 100644 --- a/bindings/nodejs/lib/types/models/api/output-response.ts +++ b/bindings/nodejs/lib/types/models/api/output-response.ts @@ -1,23 +1,25 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 + import { Output, OutputDiscriminator } from '../../block/output'; -import type { IOutputMetadataResponse } from './output-metadata-response'; +import { OutputIdProof } from './output-id-proof'; import { Type } from 'class-transformer'; /** - * Details of an output. + * An output with its output id proof. + * Response of GET /api/core/v3/outputs/{outputId}. */ export class OutputResponse { /** - * The metadata about the output. - */ - metadata!: IOutputMetadataResponse; - /** - * The output. + * One of the possible output types. */ @Type(() => Output, { discriminator: OutputDiscriminator, }) output!: Output; + /** + * The proof of the output identifier. + */ + outputIdProof!: OutputIdProof; } diff --git a/bindings/nodejs/lib/types/models/api/plugins/indexer/output-response.ts b/bindings/nodejs/lib/types/models/api/plugins/indexer/output-response.ts index f00c5db191..0b35f28722 100644 --- a/bindings/nodejs/lib/types/models/api/plugins/indexer/output-response.ts +++ b/bindings/nodejs/lib/types/models/api/plugins/indexer/output-response.ts @@ -7,7 +7,7 @@ import { NumericString } from '../../../../utils'; /** * Details of an outputs response from the indexer plugin. */ -export interface IOutputsResponse { +export interface OutputsResponse { /** * The committed slot at which these outputs were available at. */ diff --git a/bindings/nodejs/lib/types/models/api/response.ts b/bindings/nodejs/lib/types/models/api/response.ts index 663df75a1c..a4effad65e 100644 --- a/bindings/nodejs/lib/types/models/api/response.ts +++ b/bindings/nodejs/lib/types/models/api/response.ts @@ -4,7 +4,7 @@ /** * Base response data. */ -export interface IResponse { +export interface Response { /** * Optional error in the response. */ diff --git a/bindings/nodejs/lib/types/models/info/routes-response.ts b/bindings/nodejs/lib/types/models/api/routes-response.ts similarity index 84% rename from bindings/nodejs/lib/types/models/info/routes-response.ts rename to bindings/nodejs/lib/types/models/api/routes-response.ts index 0712212564..aaf8dbf016 100644 --- a/bindings/nodejs/lib/types/models/info/routes-response.ts +++ b/bindings/nodejs/lib/types/models/api/routes-response.ts @@ -4,7 +4,7 @@ /** * Response from the /routes endpoint. */ -export interface IRoutesResponse { +export interface RoutesResponse { /** * The routes the node exposes. */ diff --git a/bindings/nodejs/lib/types/models/api/tips-response.ts b/bindings/nodejs/lib/types/models/api/tips-response.ts deleted file mode 100644 index b1c07c7304..0000000000 --- a/bindings/nodejs/lib/types/models/api/tips-response.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { BlockId } from '../../block/id'; - -/** - * Response from the tips endpoint. - */ -export interface ITipsResponse { - /** - * The block ids of the tip. - */ - tips: BlockId[]; -} diff --git a/bindings/nodejs/lib/types/models/transaction-failure-reason.ts b/bindings/nodejs/lib/types/models/api/transaction-failure-reason.ts similarity index 84% rename from bindings/nodejs/lib/types/models/transaction-failure-reason.ts rename to bindings/nodejs/lib/types/models/api/transaction-failure-reason.ts index a5b5934b5a..653df684ec 100644 --- a/bindings/nodejs/lib/types/models/transaction-failure-reason.ts +++ b/bindings/nodejs/lib/types/models/api/transaction-failure-reason.ts @@ -33,48 +33,47 @@ export enum TransactionFailureReason { SenderFeatureNotUnlocked = 25, IssuerFeatureNotUnlocked = 26, StakingRewardInputMissing = 27, - StakingBlockIssuerFeatureMissing = 28, - StakingCommitmentInputMissing = 29, - StakingRewardClaimingInvalid = 30, - StakingFeatureRemovedBeforeUnbonding = 31, - StakingFeatureModifiedBeforeUnbonding = 32, - StakingStartEpochInvalid = 33, - StakingEndEpochTooEarly = 34, - BlockIssuerCommitmentInputMissing = 35, - BlockIssuanceCreditInputMissing = 36, - BlockIssuerNotExpired = 37, - BlockIssuerExpiryTooEarly = 38, - ManaMovedOffBlockIssuerAccount = 39, - AccountLocked = 40, - TimelockCommitmentInputMissing = 41, - TimelockNotExpired = 42, - ExpirationCommitmentInputMissing = 43, - ExpirationNotUnlockable = 44, - ReturnAmountNotFulFilled = 45, - NewChainOutputHasNonZeroedId = 46, - ChainOutputImmutableFeaturesChanged = 47, - ImplicitAccountDestructionDisallowed = 48, - MultipleImplicitAccountCreationAddresses = 49, - AccountInvalidFoundryCounter = 50, - AnchorInvalidStateTransition = 51, - AnchorInvalidGovernanceTransition = 52, - FoundryTransitionWithoutAccount = 53, - FoundrySerialInvalid = 54, - DelegationCommitmentInputMissing = 55, - DelegationRewardInputMissing = 56, - DelegationRewardsClaimingInvalid = 57, - DelegationOutputTransitionedTwice = 58, - DelegationModified = 59, - DelegationStartEpochInvalid = 60, - DelegationAmountMismatch = 61, - DelegationEndEpochNotZero = 62, - DelegationEndEpochInvalid = 63, - CapabilitiesNativeTokenBurningNotAllowed = 64, - CapabilitiesManaBurningNotAllowed = 65, - CapabilitiesAccountDestructionNotAllowed = 66, - CapabilitiesAnchorDestructionNotAllowed = 67, - CapabilitiesFoundryDestructionNotAllowed = 68, - CapabilitiesNftDestructionNotAllowed = 69, + StakingCommitmentInputMissing = 28, + StakingRewardClaimingInvalid = 29, + StakingFeatureRemovedBeforeUnbonding = 30, + StakingFeatureModifiedBeforeUnbonding = 31, + StakingStartEpochInvalid = 32, + StakingEndEpochTooEarly = 33, + BlockIssuerCommitmentInputMissing = 34, + BlockIssuanceCreditInputMissing = 35, + BlockIssuerNotExpired = 36, + BlockIssuerExpiryTooEarly = 37, + ManaMovedOffBlockIssuerAccount = 38, + AccountLocked = 39, + TimelockCommitmentInputMissing = 40, + TimelockNotExpired = 41, + ExpirationCommitmentInputMissing = 42, + ExpirationNotUnlockable = 43, + ReturnAmountNotFulFilled = 44, + NewChainOutputHasNonZeroedId = 45, + ChainOutputImmutableFeaturesChanged = 46, + ImplicitAccountDestructionDisallowed = 47, + MultipleImplicitAccountCreationAddresses = 48, + AccountInvalidFoundryCounter = 49, + AnchorInvalidStateTransition = 50, + AnchorInvalidGovernanceTransition = 51, + FoundryTransitionWithoutAccount = 52, + FoundrySerialInvalid = 53, + DelegationCommitmentInputMissing = 54, + DelegationRewardInputMissing = 55, + DelegationRewardsClaimingInvalid = 56, + DelegationOutputTransitionedTwice = 57, + DelegationModified = 58, + DelegationStartEpochInvalid = 59, + DelegationAmountMismatch = 60, + DelegationEndEpochNotZero = 61, + DelegationEndEpochInvalid = 62, + CapabilitiesNativeTokenBurningNotAllowed = 63, + CapabilitiesManaBurningNotAllowed = 64, + CapabilitiesAccountDestructionNotAllowed = 65, + CapabilitiesAnchorDestructionNotAllowed = 66, + CapabilitiesFoundryDestructionNotAllowed = 67, + CapabilitiesNftDestructionNotAllowed = 68, SemanticValidationFailed = 255, } @@ -138,8 +137,6 @@ export const TRANSACTION_FAILURE_REASON_STRINGS: { 'Issuer feature is not unlocked.', [TransactionFailureReason.StakingRewardInputMissing]: 'Staking feature removal or resetting requires a reward input.', - [TransactionFailureReason.StakingBlockIssuerFeatureMissing]: - 'Block issuer feature missing for account with staking feature.', [TransactionFailureReason.StakingCommitmentInputMissing]: 'Staking feature validation requires a commitment input.', [TransactionFailureReason.StakingRewardClaimingInvalid]: diff --git a/bindings/nodejs/lib/types/models/api/transaction-response.ts b/bindings/nodejs/lib/types/models/api/transaction-response.ts new file mode 100644 index 0000000000..1d81c66520 --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/transaction-response.ts @@ -0,0 +1,24 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { TransactionState } from '../state'; +import { TransactionId } from '../../block'; +import { TransactionFailureReason } from './transaction-failure-reason'; + +/** + * Metadata of a transaction. + */ +export interface TransactionMetadataResponse { + /** + * The transaction id. + */ + transactionId: TransactionId; + /** + * The transaction state. + */ + transactionState: TransactionState; + /** + * The transaction failure reason. + */ + transactionFailureReason?: TransactionFailureReason; +} diff --git a/bindings/nodejs/lib/types/models/block-metadata.ts b/bindings/nodejs/lib/types/models/block-metadata.ts deleted file mode 100644 index 0f6029c87e..0000000000 --- a/bindings/nodejs/lib/types/models/block-metadata.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import { BlockState, TransactionState } from './state'; -import { BlockFailureReason } from './block-failure-reason'; -import { Block, BlockId, TransactionId } from '../block'; -import { TransactionFailureReason } from './transaction-failure-reason'; - -/** - * Response from the metadata endpoint. - */ -export interface IBlockMetadata { - /** - * The block id. - */ - blockId: BlockId; - /** - * The block state. - */ - blockState: BlockState; - /** - * The block failure reason. - */ - blockFailureReason?: BlockFailureReason; - /** - * The metadata of the transaction in the block. - */ - transactionMetadata?: TransactionMetadata; -} - -/** - * Response from the full endpoint. - */ -export interface IBlockWithMetadata { - /** - * The block. - */ - block: Block; - /** - * The block metadata. - */ - metadata: IBlockMetadata; -} - -/** - * Metadata of a transaction. - */ -export interface TransactionMetadata { - /** - * The transaction id. - */ - transactionId: TransactionId; - /** - * The transaction state. - */ - transactionState: TransactionState; - /** - * The transaction failure reason. - */ - transactionFailureReason?: TransactionFailureReason; -} diff --git a/bindings/nodejs/lib/types/models/index.ts b/bindings/nodejs/lib/types/models/index.ts index a4290a39a3..573fe4c4a9 100644 --- a/bindings/nodejs/lib/types/models/index.ts +++ b/bindings/nodejs/lib/types/models/index.ts @@ -2,10 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 export * from './api'; -export * from './info'; - -export * from './transaction-failure-reason'; -export * from './block-metadata'; export * from './native-token'; -export * from './storage-score'; export * from './state'; +export * from './storage-score'; diff --git a/bindings/nodejs/lib/types/models/info/node-info.ts b/bindings/nodejs/lib/types/models/info/node-info.ts deleted file mode 100644 index 4d55c75da2..0000000000 --- a/bindings/nodejs/lib/types/models/info/node-info.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { INodeInfoBaseToken } from './node-info-base-token'; -import type { INodeInfoMetrics } from './node-info-metrics'; -import type { ProtocolInfo } from './node-info-protocol'; -import type { INodeInfoStatus } from './node-info-status'; -/** - * Response from the /info endpoint. - */ -export interface INodeInfo { - /** - * The name of the node. - */ - name: string; - /** - * The semantic version of the node. - */ - version: string; - /** - * The status of the node. - */ - status: INodeInfoStatus; - /** - * The metrics for the node. - */ - metrics: INodeInfoMetrics; - /** - * The protocol parameters. - */ - protocolParameters: ProtocolInfo[]; - /** - * The base token info of the node. - */ - baseToken: INodeInfoBaseToken; -} diff --git a/bindings/nodejs/lib/types/models/storage-score.ts b/bindings/nodejs/lib/types/models/storage-score.ts index aa9b975136..89952f69b9 100644 --- a/bindings/nodejs/lib/types/models/storage-score.ts +++ b/bindings/nodejs/lib/types/models/storage-score.ts @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { u64 } from '../utils/type-aliases'; +import { NumericString } from '../utils'; /** * Defines the parameters of storage score calculations on objects which take node resources. @@ -10,7 +10,7 @@ export interface StorageScoreParameters { /** * Defines the number of IOTA tokens required per unit of storage score. */ - storageCost: u64; + storageCost: NumericString; /** * Defines the factor to be used for data only fields. */ @@ -18,17 +18,17 @@ export interface StorageScoreParameters { /** * Defines the offset to be applied to all outputs for the overhead of handling them in storage. */ - offsetOutputOverhead: u64; + offsetOutputOverhead: NumericString; /** * Defines the offset to be used for block issuer feature public keys. */ - offsetEd25519BlockIssuerKey: u64; + offsetEd25519BlockIssuerKey: NumericString; /** * Defines the offset to be used for staking feature. */ - offsetStakingFeature: u64; + offsetStakingFeature: NumericString; /** * Defines the offset to be used for delegation output. */ - offsetDelegation: u64; + offsetDelegation: NumericString; } diff --git a/bindings/nodejs/lib/types/secret_manager/bridge/secret-manager.ts b/bindings/nodejs/lib/types/secret_manager/bridge/secret-manager.ts index 2e1390d34b..e3e5d504c5 100644 --- a/bindings/nodejs/lib/types/secret_manager/bridge/secret-manager.ts +++ b/bindings/nodejs/lib/types/secret_manager/bridge/secret-manager.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { UnsignedBlock } from '../../block'; -import type { IGenerateAddressesOptions } from '../../client/generate-addresses-options'; +import type { GenerateAddressesOptions } from '../../client/generate-addresses-options'; import type { PreparedTransactionData } from '../../client/prepared-transaction-data'; import { HexEncodedString } from '../../utils'; import { Bip44 } from '../secret-manager'; @@ -10,14 +10,14 @@ import { Bip44 } from '../secret-manager'; export interface __GenerateEd25519AddressesMethod__ { name: 'generateEd25519Addresses'; data: { - options: IGenerateAddressesOptions; + options: GenerateAddressesOptions; }; } export interface __GenerateEvmAddressesMethod__ { name: 'generateEvmAddresses'; data: { - options: IGenerateAddressesOptions; + options: GenerateAddressesOptions; }; } diff --git a/bindings/nodejs/lib/types/utils/bridge/index.ts b/bindings/nodejs/lib/types/utils/bridge/index.ts index 31804d2b14..22de248ba6 100644 --- a/bindings/nodejs/lib/types/utils/bridge/index.ts +++ b/bindings/nodejs/lib/types/utils/bridge/index.ts @@ -1,20 +1,15 @@ import type { __GenerateMnemonicMethod__, __MnemonicToHexSeedMethod__, - __ComputeAccountIdMethod__, __ComputeOutputIdMethod__, __ComputeTokenIdMethod__, - __ComputeNftIdMethod__, __ComputeFoundryIdMethod__, __ComputeMinimumOutputAmountMethod__, - __ParseBech32AddressMethod__, + __Blake2b256HashMethod__, __BlockIdMethod__, __TransactionIdMethod__, - __Bech32ToHexMethod__, - __HexToBech32Method__, - __AccountIdToBech32Method__, - __NftIdToBech32Method__, - __HexPublicKeyToBech32AddressMethod__, + __AddressToBech32Method__, + __ParseBech32AddressMethod__, __IsAddressValidMethod__, __ProtocolParametersHashMethod__, __TransactionSigningHashMethod__, @@ -31,25 +26,22 @@ import type { __OutputManaWithDecay__, __VerifyTransactionSyntax__, __BlockBytes__, + __IotaMainnetProtocolParameters__, + __ShimmerMainnetProtocolParameters__, } from './utils'; export type __UtilsMethods__ = | __GenerateMnemonicMethod__ | __MnemonicToHexSeedMethod__ - | __ComputeAccountIdMethod__ - | __ComputeNftIdMethod__ | __ComputeFoundryIdMethod__ | __ComputeOutputIdMethod__ | __ComputeTokenIdMethod__ | __ComputeMinimumOutputAmountMethod__ | __ParseBech32AddressMethod__ + | __Blake2b256HashMethod__ | __BlockIdMethod__ | __TransactionIdMethod__ - | __Bech32ToHexMethod__ - | __HexToBech32Method__ - | __AccountIdToBech32Method__ - | __NftIdToBech32Method__ - | __HexPublicKeyToBech32AddressMethod__ + | __AddressToBech32Method__ | __IsAddressValidMethod__ | __ProtocolParametersHashMethod__ | __TransactionSigningHashMethod__ @@ -65,4 +57,6 @@ export type __UtilsMethods__ = | __GenerateManaWithDecay__ | __OutputManaWithDecay__ | __VerifyTransactionSyntax__ - | __BlockBytes__; + | __BlockBytes__ + | __IotaMainnetProtocolParameters__ + | __ShimmerMainnetProtocolParameters__; diff --git a/bindings/nodejs/lib/types/utils/bridge/utils.ts b/bindings/nodejs/lib/types/utils/bridge/utils.ts index 8360aab79c..b27576946e 100644 --- a/bindings/nodejs/lib/types/utils/bridge/utils.ts +++ b/bindings/nodejs/lib/types/utils/bridge/utils.ts @@ -10,9 +10,9 @@ import { Block, ProtocolParameters, OutputId, - NftId, Bech32Address, Unlock, + Address, } from '../../'; import { AccountId } from '../../block/id'; import { SlotCommitment } from '../../block/slot'; @@ -30,13 +30,6 @@ export interface __MnemonicToHexSeedMethod__ { }; } -export interface __ComputeAccountIdMethod__ { - name: 'computeAccountId'; - data: { - outputId: OutputId; - }; -} - export interface __ComputeFoundryIdMethod__ { name: 'computeFoundryId'; data: { @@ -46,10 +39,10 @@ export interface __ComputeFoundryIdMethod__ { }; } -export interface __ComputeNftIdMethod__ { - name: 'computeNftId'; +export interface __Blake2b256HashMethod__ { + name: 'blake2b256Hash'; data: { - outputId: OutputId; + bytes: HexEncodedString; }; } @@ -100,42 +93,11 @@ export interface __TransactionIdMethod__ { }; } -export interface __Bech32ToHexMethod__ { - name: 'bech32ToHex'; - data: { - bech32: Bech32Address; - }; -} - -export interface __HexToBech32Method__ { - name: 'hexToBech32'; +export interface __AddressToBech32Method__ { + name: 'addressToBech32'; data: { - hex: HexEncodedString; - bech32Hrp?: string; - }; -} - -export interface __AccountIdToBech32Method__ { - name: 'accountIdToBech32'; - data: { - accountId: AccountId; - bech32Hrp?: string; - }; -} - -export interface __NftIdToBech32Method__ { - name: 'nftIdToBech32'; - data: { - nftId: NftId; - bech32Hrp?: string; - }; -} - -export interface __HexPublicKeyToBech32AddressMethod__ { - name: 'hexPublicKeyToBech32Address'; - data: { - hex: HexEncodedString; - bech32Hrp?: string; + address: Address; + bech32Hrp: string; }; } @@ -272,3 +234,11 @@ export interface __BlockBytes__ { block: Block; }; } + +export interface __IotaMainnetProtocolParameters__ { + name: 'iotaMainnetProtocolParameters'; +} + +export interface __ShimmerMainnetProtocolParameters__ { + name: 'shimmerMainnetProtocolParameters'; +} diff --git a/bindings/nodejs/lib/types/wallet/address.ts b/bindings/nodejs/lib/types/wallet/address.ts index 18845f1d0d..8043afe535 100644 --- a/bindings/nodejs/lib/types/wallet/address.ts +++ b/bindings/nodejs/lib/types/wallet/address.ts @@ -4,16 +4,7 @@ import { SlotIndex } from '../block/slot'; import { Bech32Address, NftId, TokenId } from '../block'; import { NumericString, u256, u64 } from '../utils'; - -/** A Bip44 address */ -export interface Bip44Address { - /** The Bech32 address. */ - address: Bech32Address; - /** The address key index. */ - keyIndex: number; - /** Whether the address is a public or an internal (change) address. */ - internal: boolean; -} +import { ReturnStrategy } from './output-params'; /** Address with a base token amount */ export interface SendParams { @@ -26,7 +17,7 @@ export interface SendParams { * given the provided amount. If a storage deposit is needed and a return address is not provided, it will * default to the address of the wallet. */ - returnAddress?: string; + returnAddress?: Bech32Address; /** * Expiration in seconds, after which the output will be available for the sender again, if not spent by the * receiver already. The expiration will only be used if one is necessary given the provided amount. If an @@ -61,10 +52,12 @@ export interface SendNftParams { nftId: NftId; } -/** Options for address generation, useful with a Ledger Nano SecretManager */ -export interface GenerateAddressOptions { - /** Whether to generate a public or an internal (change) address. */ - internal: boolean; - /** Whether to display the generated address on Ledger Nano devices. */ - ledgerNanoPrompt: boolean; +/** Parameters for sending mana. */ +export interface SendManaParams { + /** Amount of mana to send, e.g. 1000000. */ + mana: u64 | NumericString; + /** Recipient address, e.g. rms1qztwng6cty8cfm42nzvq099ev7udhrnk0rw8jt8vttf9kpqnxhpsx869vr3. */ + address: Bech32Address; + /** Whether to gift the storage deposit or not. */ + return_strategy?: ReturnStrategy; } diff --git a/bindings/nodejs/lib/types/wallet/bridge/index.ts b/bindings/nodejs/lib/types/wallet/bridge/index.ts index cf21474064..96f0c82d2f 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/index.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/index.ts @@ -2,7 +2,6 @@ import type { __AnnounceCandidacyMethod__, __PrepareBurnMethod__, __PrepareClaimOutputsMethod__, - __ClaimOutputsMethod__, __PrepareConsolidateOutputsMethod__, __PrepareCreateAccountOutputMethod__, __DeregisterParticipationEventMethod__, @@ -25,17 +24,16 @@ import type { __PrepareMintNativeTokenMethod__, __PrepareMintNftsMethod__, __PrepareOutputMethod__, + __PrepareSendManaMethod__, __PrepareSendMethod__, __PrepareCreateDelegationMethod__, __PrepareDelayDelegationClaimingMethod__, __PrepareBeginStakingMethod__, __PrepareExtendStakingMethod__, __PrepareEndStakingMethod__, - __PrepareTransactionMethod__, + __PrepareSendOutputsMethod__, __RegisterParticipationEventsMethod__, __WaitForTransactionAcceptanceMethod__, - __SendMethod__, - __SendWithParamsMethod__, __PrepareSendNativeTokensMethod__, __PrepareSendNftMethod__, __SendOutputsMethod__, @@ -55,7 +53,7 @@ import type { __PrepareIncreaseVotingPowerMethod__, __PrepareDecreaseVotingPowerMethod__, __PrepareStopParticipatingMethod__, - __BackupMethod__, + __BackupToStrongholdSnapshotMethod__, __ChangeStrongholdPasswordMethod__, __ClearStrongholdPasswordMethod__, __ClearListenersMethod__, @@ -63,7 +61,7 @@ import type { __GenerateMnemonicMethod__, __GetLedgerNanoStatusMethod__, __IsStrongholdPasswordAvailableMethod__, - __RestoreBackupMethod__, + __RestoreFromStrongholdSnapshotMethod__, __SetClientOptionsMethod__, __SetStrongholdPasswordClearIntervalMethod__, __SetStrongholdPasswordMethod__, @@ -77,7 +75,6 @@ import type { export type __WalletMethod__ = | __AnnounceCandidacyMethod__ | __PrepareBurnMethod__ - | __ClaimOutputsMethod__ | __PrepareClaimOutputsMethod__ | __PrepareConsolidateOutputsMethod__ | __PrepareCreateAccountOutputMethod__ @@ -106,17 +103,16 @@ export type __WalletMethod__ = | __PrepareMintNativeTokenMethod__ | __PrepareMintNftsMethod__ | __PrepareOutputMethod__ + | __PrepareSendManaMethod__ | __PrepareSendMethod__ | __PrepareCreateDelegationMethod__ | __PrepareDelayDelegationClaimingMethod__ | __PrepareBeginStakingMethod__ | __PrepareExtendStakingMethod__ | __PrepareEndStakingMethod__ - | __PrepareTransactionMethod__ + | __PrepareSendOutputsMethod__ | __RegisterParticipationEventsMethod__ | __WaitForTransactionAcceptanceMethod__ - | __SendMethod__ - | __SendWithParamsMethod__ | __PrepareSendNativeTokensMethod__ | __PrepareSendNftMethod__ | __SendOutputsMethod__ @@ -131,7 +127,7 @@ export type __WalletMethod__ = | __GetParticipationOverviewMethod__ | __PrepareIncreaseVotingPowerMethod__ | __PrepareDecreaseVotingPowerMethod__ - | __BackupMethod__ + | __BackupToStrongholdSnapshotMethod__ | __ChangeStrongholdPasswordMethod__ | __ClearListenersMethod__ | __ClearStrongholdPasswordMethod__ @@ -139,7 +135,7 @@ export type __WalletMethod__ = | __GenerateMnemonicMethod__ | __GetLedgerNanoStatusMethod__ | __IsStrongholdPasswordAvailableMethod__ - | __RestoreBackupMethod__ + | __RestoreFromStrongholdSnapshotMethod__ | __SetClientOptionsMethod__ | __SetStrongholdPasswordClearIntervalMethod__ | __SetStrongholdPasswordMethod__ diff --git a/bindings/nodejs/lib/types/wallet/bridge/wallet.ts b/bindings/nodejs/lib/types/wallet/bridge/wallet.ts index 607b4f92f5..d6621c47b2 100644 --- a/bindings/nodejs/lib/types/wallet/bridge/wallet.ts +++ b/bindings/nodejs/lib/types/wallet/bridge/wallet.ts @@ -1,16 +1,17 @@ import type { SyncOptions, FilterOptions } from '../wallet'; import type { WalletEventType, WalletEvent } from '../event'; import type { - IAuth, - IClientOptions, + Auth, + ClientOptions, Burn, - INode, + Node, PreparedTransactionData, } from '../../client'; import type { SendParams, SendNativeTokenParams, SendNftParams, + SendManaParams, } from '../address'; import type { OutputParams } from '../output-params'; import type { OutputsToClaim } from '../output'; @@ -51,8 +52,8 @@ export type __AnnounceCandidacyMethod__ = { }; }; -export type __BackupMethod__ = { - name: 'backup'; +export type __BackupToStrongholdSnapshotMethod__ = { + name: 'backupToStrongholdSnapshot'; data: { destination: string; password: string; @@ -93,8 +94,8 @@ export type __IsStrongholdPasswordAvailableMethod__ = { name: 'isStrongholdPasswordAvailable'; }; -export type __RestoreBackupMethod__ = { - name: 'restoreBackup'; +export type __RestoreFromStrongholdSnapshotMethod__ = { + name: 'restoreFromStrongholdSnapshot'; data: { source: string; password: string; @@ -105,7 +106,7 @@ export type __RestoreBackupMethod__ = { export type __SetClientOptionsMethod__ = { name: 'setClientOptions'; - data: { clientOptions: IClientOptions }; + data: { clientOptions: ClientOptions }; }; export type __SetStrongholdPasswordMethod__ = { @@ -137,7 +138,7 @@ export type __StoreMnemonicMethod__ = { export type __UpdateNodeAuthMethod__ = { name: 'updateNodeAuth'; - data: { url: string; auth?: IAuth }; + data: { url: string; auth?: Auth }; }; export type __PrepareBurnMethod__ = { @@ -155,13 +156,6 @@ export type __PrepareClaimOutputsMethod__ = { }; }; -export type __ClaimOutputsMethod__ = { - name: 'claimOutputs'; - data: { - outputIdsToClaim: OutputId[]; - }; -}; - export type __PrepareConsolidateOutputsMethod__ = { name: 'prepareConsolidateOutputs'; data: { @@ -349,16 +343,8 @@ export type __PrepareOutputMethod__ = { }; }; -export type __PrepareSendMethod__ = { - name: 'prepareSend'; - data: { - params: SendParams[]; - options?: TransactionOptions; - }; -}; - -export type __PrepareTransactionMethod__ = { - name: 'prepareTransaction'; +export type __PrepareSendOutputsMethod__ = { + name: 'prepareSendOutputs'; data: { outputs: Output[]; options?: TransactionOptions; @@ -381,17 +367,8 @@ export type __WaitForTransactionAcceptanceMethod__ = { }; }; -export type __SendMethod__ = { - name: 'send'; - data: { - amount: NumericString; - address: string; - options?: TransactionOptions; - }; -}; - -export type __SendWithParamsMethod__ = { - name: 'sendWithParams'; +export type __PrepareSendMethod__ = { + name: 'prepareSend'; data: { params: SendParams[]; options?: TransactionOptions; @@ -422,6 +399,14 @@ export type __SendOutputsMethod__ = { }; }; +export type __PrepareSendManaMethod__ = { + name: 'prepareSendMana'; + data: { + params: SendManaParams; + options?: TransactionOptions; + }; +}; + export type __SetAliasMethod__ = { name: 'setAlias'; data: { @@ -503,7 +488,7 @@ export type __GetParticipationEventMethod__ = { export type __GetParticipationEventIdsMethod__ = { name: 'getParticipationEventIds'; data: { - node: INode; + node: Node; eventType?: ParticipationEventType; }; }; diff --git a/bindings/nodejs/lib/types/wallet/event.ts b/bindings/nodejs/lib/types/wallet/event.ts index 10b8884e79..a0d3d1d568 100644 --- a/bindings/nodejs/lib/types/wallet/event.ts +++ b/bindings/nodejs/lib/types/wallet/event.ts @@ -115,18 +115,20 @@ class TransactionInclusionWalletEvent extends WalletEvent { * All of the transaction progress types. */ enum TransactionProgressType { - /** Performing input selection. */ - SelectingInputs = 0, + /** Building a transaction. */ + BuildingTransaction = 0, /** Generating remainder value deposit address. */ GeneratingRemainderDepositAddress = 1, /** Prepared transaction. */ PreparedTransaction = 2, - /** Prepared transaction signing hash hex encoded, required for blindsigning with a Ledger Nano. */ - PreparedTransactionSigningHash = 3, /** Signing the transaction. */ - SigningTransaction = 4, + SigningTransaction = 3, + /** Prepared transaction signing hash hex encoded, required for blindsigning with a Ledger Nano. */ + PreparedTransactionSigningHash = 4, + /** Prepared block signing input, required for blindsigning with a Ledger Nano. */ + PreparedBlockSigningInput = 5, /** Broadcasting. */ - Broadcasting = 5, + Broadcasting = 6, } /** @@ -159,11 +161,11 @@ abstract class TransactionProgress { } /** - * A 'selecting inputs' progress. + * A 'building transaction' progress. */ -class SelectingInputsProgress extends TransactionProgress { +class BuildingTransactionProgress extends TransactionProgress { constructor() { - super(TransactionProgressType.SelectingInputs); + super(TransactionProgressType.BuildingTransaction); } } @@ -207,6 +209,15 @@ class PreparedTransactionProgress extends TransactionProgress { } } +/** + * A 'signing transaction' progress. + */ +class SigningTransactionProgress extends TransactionProgress { + constructor() { + super(TransactionProgressType.SigningTransaction); + } +} + /** * A 'prepared transaction hash' progress. */ @@ -223,11 +234,17 @@ class PreparedTransactionSigningHashProgress extends TransactionProgress { } /** - * A 'signing transaction' progress. + * A 'prepared block input' progress. */ -class SigningTransactionProgress extends TransactionProgress { - constructor() { - super(TransactionProgressType.SigningTransaction); +class PreparedBlockSigningInputProgress extends TransactionProgress { + blockSigningInput: HexEncodedString; + + /** + * @param signingHash The signing hash of the block. + */ + constructor(signingInput: HexEncodedString) { + super(TransactionProgressType.PreparedBlockSigningInput); + this.blockSigningInput = signingInput; } } @@ -249,11 +266,12 @@ export { TransactionInclusionWalletEvent, TransactionProgressWalletEvent, TransactionProgress, - SelectingInputsProgress, + BuildingTransactionProgress, GeneratingRemainderDepositAddressProgress, PreparedTransactionProgress, - PreparedTransactionSigningHashProgress, SigningTransactionProgress, + PreparedTransactionSigningHashProgress, + PreparedBlockSigningInputProgress, BroadcastingProgress, TransactionProgressType, }; diff --git a/bindings/nodejs/lib/types/wallet/output.ts b/bindings/nodejs/lib/types/wallet/output.ts index ba265d6ad4..7171cc57f6 100644 --- a/bindings/nodejs/lib/types/wallet/output.ts +++ b/bindings/nodejs/lib/types/wallet/output.ts @@ -4,7 +4,7 @@ import { Type } from 'class-transformer'; import { Address, AddressDiscriminator } from '../block/address'; import { Output, OutputDiscriminator, OutputId } from '../block/output'; -import { IOutputMetadataResponse } from '../models/api'; +import { OutputMetadataResponse } from '../models/api'; /** Output to claim */ export enum OutputsToClaim { @@ -20,7 +20,7 @@ export class OutputData { /** The identifier of an Output */ outputId!: OutputId; /** The metadata of the output */ - metadata!: IOutputMetadataResponse; + metadata!: OutputMetadataResponse; /** The actual Output */ @Type(() => Output, { discriminator: OutputDiscriminator, diff --git a/bindings/nodejs/lib/types/wallet/participation.ts b/bindings/nodejs/lib/types/wallet/participation.ts index 9b5c0c2f03..1bcd7775fc 100644 --- a/bindings/nodejs/lib/types/wallet/participation.ts +++ b/bindings/nodejs/lib/types/wallet/participation.ts @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { INode } from '../client'; +import type { Node } from '../client'; import { HexEncodedString } from '../utils'; /** @@ -51,7 +51,7 @@ export interface ParticipationEvent { */ export interface ParticipationEventRegistrationOptions { /** The node to register participation events. */ - node: INode; + node: Node; /** * The events to register. * If empty, then every event being tracked by the node will be registered. */ @@ -69,7 +69,7 @@ export interface ParticipationEventWithNodes { /** Information about a voting or staking event */ data: ParticipationEventData; /** Provided client nodes for this event. */ - nodes: INode[]; + nodes: Node[]; } /** diff --git a/bindings/nodejs/lib/types/wallet/transaction-options.ts b/bindings/nodejs/lib/types/wallet/transaction-options.ts index 5ce1e5b2a9..2004248bbf 100644 --- a/bindings/nodejs/lib/types/wallet/transaction-options.ts +++ b/bindings/nodejs/lib/types/wallet/transaction-options.ts @@ -4,14 +4,13 @@ import { AccountAddress, AccountId, + Address, Bech32Address, - ContextInput, OutputId, } from '../block'; import { TaggedDataPayload } from '../block/payload/tagged'; import { Burn } from '../client'; import { u256, HexEncodedString, NumericString, u64 } from '../utils'; -import { Bip44Address } from './address'; /** Options for creating a transaction. */ export interface TransactionOptions { @@ -19,11 +18,9 @@ export interface TransactionOptions { remainderValueStrategy?: RemainderValueStrategy; /** An optional tagged data payload. */ taggedDataPayload?: TaggedDataPayload; - /** Transaction context inputs to include. */ - contextInputs?: ContextInput[]; /** Inputs that must be used for the transaction. */ requiredInputs?: OutputId[]; - /** Specifies what needs to be burned during input selection. */ + /** Specifies what needs to be burned in the transaction. */ burn?: Burn; /** Optional note, that is only stored locally. */ note?: string; @@ -31,8 +28,6 @@ export interface TransactionOptions { allowMicroAmount?: boolean; /** Whether to allow the selection of additional inputs for this transaction. */ allowAdditionalInputSelection?: boolean; - /** Transaction capabilities. */ - capabilities?: HexEncodedString; /** Mana allotments for the transaction. */ manaAllotments?: { [account_id: AccountId]: u64 }; /** Optional block issuer to which the transaction will have required mana allotted. */ @@ -56,7 +51,7 @@ export type ReuseAddress = { export type CustomAddress = { /** The name of the strategy. */ strategy: 'CustomAddress'; - value: Bip44Address; + value: Address; }; /** Options for creating Native Tokens. */ diff --git a/bindings/nodejs/lib/types/wallet/transaction.ts b/bindings/nodejs/lib/types/wallet/transaction.ts index b5f7c3db75..60eb35692e 100644 --- a/bindings/nodejs/lib/types/wallet/transaction.ts +++ b/bindings/nodejs/lib/types/wallet/transaction.ts @@ -4,7 +4,7 @@ import { Type } from 'class-transformer'; import { BlockId, TransactionId } from '../block'; import { SignedTransactionPayload } from '../block/payload/signed_transaction'; -import { OutputResponse } from '../models/api'; +import { OutputWithMetadata } from '../models'; /** Possible InclusionStates of transactions sent with the wallet */ export enum InclusionState { @@ -45,6 +45,6 @@ export class TransactionWithMetadata { * Outputs that are used as input in the transaction. * May not be all, because some may have already been deleted from the node. */ - @Type(() => OutputResponse) - inputs!: OutputResponse[]; + @Type(() => OutputWithMetadata) + inputs!: OutputWithMetadata[]; } diff --git a/bindings/nodejs/lib/types/wallet/wallet.ts b/bindings/nodejs/lib/types/wallet/wallet.ts index 31de56761b..88e53d3b6e 100644 --- a/bindings/nodejs/lib/types/wallet/wallet.ts +++ b/bindings/nodejs/lib/types/wallet/wallet.ts @@ -9,7 +9,7 @@ import { TokenId, } from '../block/id'; import { DecayedMana, HexEncodedString, u256, u64 } from '../utils'; -import { IClientOptions } from '../client'; +import { ClientOptions } from '../client'; import { Bip44, SecretManagerType } from '../secret_manager/secret-manager'; import { Bech32Address } from '../block'; @@ -22,7 +22,7 @@ export interface WalletOptions { /** The the BIP44 path of the wallet. */ bipPath?: Bip44; /** The node client options. */ - clientOptions?: IClientOptions; + clientOptions?: ClientOptions; /** The secret manager to use. */ secretManager?: SecretManagerType; /** The path to the wallet database. */ @@ -131,6 +131,8 @@ export interface WalletSyncOptions { accountOutputs?: boolean; /** Whether to sync NFT outputs. */ nftOutputs?: boolean; + /** Whether to sync delegation outputs. */ + delegationOutputs?: boolean; } /** Specifies what outputs should be synced for the address of an account output. */ @@ -139,10 +141,12 @@ export interface AccountSyncOptions { basicOutputs?: boolean; /** Whether to sync Account outputs. */ accountOutputs?: boolean; - /** Whether to sync NFT outputs. */ - nftOutputs?: boolean; /** Whether to sync foundry outputs. */ foundryOutputs?: boolean; + /** Whether to sync NFT outputs. */ + nftOutputs?: boolean; + /** Whether to sync delegation outputs. */ + delegationOutputs?: boolean; } /** Specifies what outputs should be synced for the address of an nft output. */ @@ -153,6 +157,8 @@ export interface NftSyncOptions { accountOutputs?: boolean; /** Whether to sync NFT outputs. */ nftOutputs?: boolean; + /** Whether to sync delegation outputs. */ + delegationOutputs?: boolean; } /** Options to filter outputs */ diff --git a/bindings/nodejs/lib/utils/utils.ts b/bindings/nodejs/lib/utils/utils.ts index 881d4133b5..073be7a5bb 100644 --- a/bindings/nodejs/lib/utils/utils.ts +++ b/bindings/nodejs/lib/utils/utils.ts @@ -21,6 +21,7 @@ import { Unlock, DecayedMana, NumericString, + Ed25519Address, } from '../types'; import { AccountId, @@ -69,9 +70,9 @@ export class Utils { */ static computeAccountId(outputId: OutputId): AccountId { return callUtilsMethod({ - name: 'computeAccountId', + name: 'blake2b256Hash', data: { - outputId, + bytes: outputId, }, }); } @@ -107,9 +108,9 @@ export class Utils { */ static computeNftId(outputId: OutputId): NftId { return callUtilsMethod({ - name: 'computeNftId', + name: 'blake2b256Hash', data: { - outputId, + bytes: outputId, }, }); } @@ -259,95 +260,37 @@ export class Utils { } /** - * Convert a Bech32 address to a hex-encoded string. + * Converts an address to its bech32 representation. * - * @param bech32 A Bech32 address. - * @returns The hex-encoded string. - */ - static bech32ToHex(bech32: Bech32Address): HexEncodedString { - return callUtilsMethod({ - name: 'bech32ToHex', - data: { - bech32, - }, - }); - } - - /** - * Convert a hex-encoded address string to a Bech32-encoded address string. - * - * @param hex A hex-encoded address string. + * @param address An address. * @param bech32Hrp The Bech32 HRP (human readable part) to use. * @returns The Bech32-encoded address string. */ - static hexToBech32( - hex: HexEncodedString, - bech32Hrp: string, - ): Bech32Address { + static addressToBech32(address: Address, bech32Hrp: string): Bech32Address { return callUtilsMethod({ - name: 'hexToBech32', + name: 'addressToBech32', data: { - hex, - bech32Hrp, - }, - }); - } - - /** - * Transforms an account id to a bech32 encoded address. - * - * @param accountId An account ID. - * @param bech32Hrp The Bech32 HRP (human readable part) to use. - * @returns The Bech32-encoded address string. - */ - static accountIdToBech32( - accountId: AccountId, - bech32Hrp: string, - ): Bech32Address { - return callUtilsMethod({ - name: 'accountIdToBech32', - data: { - accountId, - bech32Hrp, - }, - }); - } - - /** - * Convert an NFT ID to a Bech32-encoded address string. - * - * @param nftId An NFT ID. - * @param bech32Hrp The Bech32 HRP (human readable part) to use. - * @returns The Bech32-encoded address string. - */ - static nftIdToBech32(nftId: NftId, bech32Hrp: string): Bech32Address { - return callUtilsMethod({ - name: 'nftIdToBech32', - data: { - nftId, + address, bech32Hrp, }, }); } /** - * Convert a hex-encoded public key to a Bech32-encoded address string. + * Hashes a hex encoded public key with Blake2b256. * - * @param hex A hex-encoded public key. - * @param bech32Hrp The Bech32 HRP (human readable part) to use. - * @returns The Bech32-encoded address string. + * @param hex The hexadecimal string representation of a public key. + * @returns The Ed25519 address with the hashed public key. */ - static hexPublicKeyToBech32Address( - hex: HexEncodedString, - bech32Hrp: string, - ): Bech32Address { - return callUtilsMethod({ - name: 'hexPublicKeyToBech32Address', - data: { - hex, - bech32Hrp, - }, - }); + static publicKeyHash(hex: HexEncodedString): Ed25519Address { + return new Ed25519Address( + callUtilsMethod({ + name: 'blake2b256Hash', + data: { + bytes: hex, + }, + }), + ); } /** @@ -521,7 +464,7 @@ export class Utils { * @param unlocks The unlocks. * @param manaRewards The total mana rewards claimed in the transaction. * - * @returns The conflict reason. + * @returns void. */ static verifyTransactionSemantic( transaction: SignedTransactionPayload, @@ -529,8 +472,8 @@ export class Utils { protocolParameters: ProtocolParameters, unlocks?: Unlock[], manaRewards?: { [outputId: HexEncodedString]: NumericString }, - ): string { - const conflictReason = callUtilsMethod({ + ): void { + return callUtilsMethod({ name: 'verifyTransactionSemantic', data: { transaction, @@ -540,7 +483,6 @@ export class Utils { manaRewards, }, }); - return conflictReason; } /** @@ -666,4 +608,18 @@ export class Utils { }); return new Uint8Array(blockBytes); } + + static iotaMainnetProtocolParameters(): ProtocolParameters { + const params = callUtilsMethod({ + name: 'iotaMainnetProtocolParameters', + }); + return params; + } + + static shimmerMainnetProtocolParameters(): ProtocolParameters { + const params = callUtilsMethod({ + name: 'shimmerMainnetProtocolParameters', + }); + return params; + } } diff --git a/bindings/nodejs/lib/wallet/wallet.ts b/bindings/nodejs/lib/wallet/wallet.ts index 35e66fe489..8f5f33e3dd 100644 --- a/bindings/nodejs/lib/wallet/wallet.ts +++ b/bindings/nodejs/lib/wallet/wallet.ts @@ -30,8 +30,9 @@ import { ConsolidationParams, CreateDelegationTransaction, BeginStakingParams, + SendManaParams, } from '../types/wallet'; -import { Client, INode, Burn, PreparedTransactionData } from '../client'; +import { Client, Node, Burn, PreparedTransactionData } from '../client'; import { Output, FoundryOutput, @@ -59,7 +60,7 @@ import { CreateDelegationParams, PreparedCreateDelegationTransactionData, } from '../types/wallet'; -import { IAuth, IClientOptions, LedgerNanoStatus } from '../types/client'; +import { Auth, ClientOptions, LedgerNanoStatus } from '../types/client'; import { SecretManager } from '../secret_manager'; import { PreparedCreateDelegationTransaction } from '../types/wallet/create-delegation-transaction'; @@ -84,9 +85,12 @@ export class Wallet { /** * Backup the data to a Stronghold snapshot. */ - async backup(destination: string, password: string): Promise { + async backupToStrongholdSnapshot( + destination: string, + password: string, + ): Promise { await this.methodHandler.callMethod({ - name: 'backup', + name: 'backupToStrongholdSnapshot', data: { destination, password, @@ -200,14 +204,14 @@ export class Wallet { * if ignore_if_coin_type_mismatch == true, client options coin type and accounts will not be restored if the cointype doesn't match * If a bech32 hrp is provided to ignore_if_bech32_hrp_mismatch, that doesn't match the one of the current address, the wallet will not be restored. */ - async restoreBackup( + async restoreFromStrongholdSnapshot( source: string, password: string, ignoreIfCoinTypeMismatch?: boolean, ignoreIfBech32Mismatch?: string, ): Promise { await this.methodHandler.callMethod({ - name: 'restoreBackup', + name: 'restoreFromStrongholdSnapshot', data: { source, password, @@ -220,7 +224,7 @@ export class Wallet { /** * Set ClientOptions. */ - async setClientOptions(clientOptions: IClientOptions): Promise { + async setClientOptions(clientOptions: ClientOptions): Promise { await this.methodHandler.callMethod({ name: 'setClientOptions', data: { clientOptions }, @@ -287,7 +291,7 @@ export class Wallet { /** * Update the authentication for the provided node. */ - async updateNodeAuth(url: string, auth?: IAuth): Promise { + async updateNodeAuth(url: string, auth?: Auth): Promise { await this.methodHandler.callMethod({ name: 'updateNodeAuth', data: { url, auth }, @@ -734,7 +738,7 @@ export class Wallet { * @param eventType The type of events to get. */ async getParticipationEventIds( - node: INode, + node: Node, eventType?: ParticipationEventType, ): Promise { const response = await this.methodHandler.callMethod({ @@ -1459,17 +1463,17 @@ export class Wallet { } /** - * Send a transaction. + * Send outputs in a transaction. * - * @param outputs Outputs to use in the transaction. - * @param options Additional transaction options. - * @returns The transaction data. + * @param outputs The outputs to send. + * @param transactionOptions Additional transaction options. + * @returns The sent transaction. */ - async sendTransaction( + async sendOutputs( outputs: Output[], options?: TransactionOptions, ): Promise { - return (await this.prepareTransaction(outputs, options)).send(); + return (await this.prepareSendOutputs(outputs, options)).send(); } /** @@ -1479,12 +1483,12 @@ export class Wallet { * @param options Additional transaction options. * @returns The prepared transaction data. */ - async prepareTransaction( + async prepareSendOutputs( outputs: Output[], options?: TransactionOptions, ): Promise { const response = await this.methodHandler.callMethod({ - name: 'prepareTransaction', + name: 'prepareSendOutputs', data: { outputs, options, @@ -1518,15 +1522,14 @@ export class Wallet { } /** - * Checks the transaction state for a provided transaction id until it's accepted. Interval in milliseconds. Returns the block id that - * contains this transaction. + * Checks the transaction state for a provided transaction id until it's accepted. Interval in milliseconds. */ async waitForTransactionAcceptance( transactionId: TransactionId, interval?: number, maxAttempts?: number, - ): Promise { - const response = await this.methodHandler.callMethod({ + ): Promise { + await this.methodHandler.callMethod({ name: 'waitForTransactionAcceptance', data: { transactionId, @@ -1534,7 +1537,6 @@ export class Wallet { maxAttempts, }, }); - return JSON.parse(response).payload; } /** @@ -1553,18 +1555,9 @@ export class Wallet { if (typeof amount === 'bigint') { amount = amount.toString(10); } - const response = await this.methodHandler.callMethod({ - name: 'send', - data: { - amount, - address, - options: transactionOptions, - }, - }); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); + return ( + await this.prepareSend([{ address, amount }], transactionOptions) + ).send(); } /** @@ -1578,22 +1571,7 @@ export class Wallet { params: SendParams[], transactionOptions?: TransactionOptions, ): Promise { - for (let i = 0; i < params.length; i++) { - if (typeof params[i].amount === 'bigint') { - params[i].amount = params[i].amount.toString(10); - } - } - const response = await this.methodHandler.callMethod({ - name: 'sendWithParams', - data: { - params, - options: transactionOptions, - }, - }); - const parsed = JSON.parse( - response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); + return (await this.prepareSend(params, transactionOptions)).send(); } /** @@ -1681,28 +1659,45 @@ export class Wallet { } /** - * Send outputs in a transaction. + * Send mana. * - * @param outputs The outputs to send. + * @param params Amount, Address, and Return Strategy. * @param transactionOptions Additional transaction options. * @returns The sent transaction. */ - async sendOutputs( - outputs: Output[], + async sendMana( + params: SendManaParams, transactionOptions?: TransactionOptions, ): Promise { + return (await this.prepareSendMana(params, transactionOptions)).send(); + } + + /** + * Prepare to send mana. + * + * @param params Amount, Address, and Return Strategy. + * @param transactionOptions Additional transaction options. + * @returns The prepared transaction. + */ + async prepareSendMana( + params: SendManaParams, + transactionOptions?: TransactionOptions, + ): Promise { const response = await this.methodHandler.callMethod({ - name: 'sendOutputs', + name: 'prepareSendMana', data: { - outputs, + params, options: transactionOptions, }, }); const parsed = JSON.parse( response, - ) as Response; - return plainToInstance(TransactionWithMetadata, parsed.payload); + ) as Response; + return new PreparedTransaction( + plainToInstance(PreparedTransactionData, parsed.payload), + this, + ); } /** diff --git a/bindings/nodejs/src/secret_manager.rs b/bindings/nodejs/src/secret_manager.rs index 37176953ff..8169d67203 100644 --- a/bindings/nodejs/src/secret_manager.rs +++ b/bindings/nodejs/src/secret_manager.rs @@ -65,7 +65,7 @@ pub fn migrate_stronghold_snapshot_v2_to_v3( new_path.as_ref(), new_password, ) - .map_err(iota_sdk_bindings_core::iota_sdk::client::Error::from) + .map_err(iota_sdk_bindings_core::iota_sdk::client::ClientError::from) .map_err(NodejsError::new)?; Ok(()) diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index 4d26911d2c..e7f7a0a5b1 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -121,7 +121,7 @@ pub async fn get_client(wallet: External) -> Result) -> Result> { if let Some(wallet) = &**wallet.as_ref().read().await { - Ok(External::new(wallet.get_secret_manager().clone())) + Ok(External::new(wallet.secret_manager().clone())) } else { Err(destroyed_err("Wallet")) } diff --git a/bindings/nodejs/tests/client/addresses.spec.ts b/bindings/nodejs/tests/client/addresses.spec.ts index ad0feddc42..acb71c87a0 100644 --- a/bindings/nodejs/tests/client/addresses.spec.ts +++ b/bindings/nodejs/tests/client/addresses.spec.ts @@ -20,7 +20,7 @@ describe('Address tests', () => { const secretManager = SecretManager.create({ mnemonic: test['mnemonic'] }); - + const generatedAddress = await secretManager.generateEd25519Addresses({ coinType: test['coin_type'], accountIndex: test['account_index'], @@ -31,13 +31,14 @@ describe('Address tests', () => { bech32Hrp: test['bech32_hrp'], options: { internal: test['internal'], + ledgerNanoPrompt: false, } }); - + if (test['bech32_address'] !== generatedAddress[0]) { - throw new Error('Test failed: Bech32 address does not match generated address.'); + throw new Error('Test failed: Bech32 address does not match generated address.'); } - } + } }); it('generates addresses', async () => { diff --git a/bindings/nodejs/tests/client/examples.spec.ts b/bindings/nodejs/tests/client/examples.spec.ts index 66de590254..454db35354 100644 --- a/bindings/nodejs/tests/client/examples.spec.ts +++ b/bindings/nodejs/tests/client/examples.spec.ts @@ -46,10 +46,10 @@ const chain = { describe.skip('Main examples', () => { it('gets info about the node', async () => { const client = await makeClient(); - const info = await client.getInfo(); + const info = (await client.getNodeInfo()).info; expect( - info.nodeInfo.protocolParameters[0].parameters.bech32Hrp, + info.protocolParameters[0].parameters.bech32Hrp, ).toBe('rms'); }); @@ -93,10 +93,6 @@ describe.skip('Main examples', () => { const addressOutputs = await client.getOutputs(outputIdsResponse.items); expect(addressOutputs).toBeDefined(); - - addressOutputs.forEach((output) => { - expect(output.metadata.blockId).toBeValidBlockId(); - }); }); it('gets the output of a known output ID', async () => { @@ -105,7 +101,7 @@ describe.skip('Main examples', () => { '0xc1d95ac9c8c0237c6929faf427556c3562055a7155c6d336ee7891691d5525c90100', ); - expect(output.metadata.blockId).toBeValidBlockId(); + expect(output).toBeDefined(); }); it('gets the balance of an address', async () => { @@ -170,7 +166,7 @@ describe.skip('Main examples', () => { it('gets block data', async () => { const client = await makeClient(); - const tips = await client.getTips(); + const tips = (await client.getIssuance()).strongParents; const params = await client.getProtocolParameters(); const blockData = await client.getBlock(tips[0]); @@ -228,7 +224,7 @@ describe.skip('Main examples', () => { await client.destroy(); try { - const _info = await client.getInfo(); + const _info = (await client.getNodeInfo()).info; throw 'Should return an error because the client was destroyed'; } catch (err: any) { expect(err.message).toEqual('Client was destroyed'); diff --git a/bindings/nodejs/tests/client/infoMethods.spec.ts b/bindings/nodejs/tests/client/infoMethods.spec.ts index 5ec63bba74..5987ac8bb8 100644 --- a/bindings/nodejs/tests/client/infoMethods.spec.ts +++ b/bindings/nodejs/tests/client/infoMethods.spec.ts @@ -7,6 +7,7 @@ import 'dotenv/config'; import { Client } from '../../lib/client'; import '../customMatchers'; +import protocolParametersFixture from '../../../../sdk/tests/types/fixtures/protocol_parameters.json'; async function makeClient(): Promise { return await Client.create({ @@ -31,9 +32,9 @@ describe.skip('Client info methods', () => { const client = await makeClient(); const nodeInfo = await client.getNode(); - const nodeInfoByUrl = await client.getNodeInfo(nodeInfo.url); + const infoByUrl = await client.getInfo(nodeInfo.url); - expect(nodeInfoByUrl).toBeDefined(); + expect(infoByUrl).toBeDefined(); }); it('gets health of node with input url', async () => { @@ -54,18 +55,11 @@ describe.skip('Client info methods', () => { it('gets tips', async () => { const client = await makeClient(); - const tips = await client.getTips(); + const tips = (await client.getIssuance()).strongParents; expect(tips.length).toBeGreaterThan(0); }); - it('gets networkInfo', async () => { - const client = await makeClient(); - const networkInfo = await client.getNetworkInfo(); - - expect(networkInfo.protocolParameters.bech32Hrp).toBe('rms'); - }); - it('gets networkId', async () => { const client = await makeClient(); const networkId = await client.getNetworkId(); @@ -80,3 +74,15 @@ describe.skip('Client info methods', () => { expect(bech32Hrp).toBeDefined(); }); }); + +describe('Offline client info methods', () => { + it('provided protocol parameters', async () => { + const protocolParameters = protocolParametersFixture.params; + const client = await Client.create({ + protocolParameters + }); + const params = await client.getProtocolParameters(); + + expect(params).toStrictEqual(protocolParameters); + }); +}) diff --git a/bindings/nodejs/tests/client/messageMethods.spec.ts b/bindings/nodejs/tests/client/messageMethods.spec.ts index 65420d5125..7ef75504c4 100644 --- a/bindings/nodejs/tests/client/messageMethods.spec.ts +++ b/bindings/nodejs/tests/client/messageMethods.spec.ts @@ -56,7 +56,7 @@ describe.skip('Block methods', () => { it('finds blocks by block IDs', async () => { const client = await makeClient(); - const blockIds = await client.getTips(); + const blockIds = (await client.getIssuance()).strongParents; const blocks = await client.findBlocks(blockIds); expect(blocks.length).toBe(blockIds.length); @@ -64,7 +64,7 @@ describe.skip('Block methods', () => { it('gets block as raw bytes', async () => { const client = await makeClient(); - const tips = await client.getTips(); + const tips = (await client.getIssuance()).strongParents; const blockRaw = await client.getBlockRaw(tips[0]); diff --git a/bindings/nodejs/tests/client/outputBuilders.spec.ts b/bindings/nodejs/tests/client/outputBuilders.spec.ts index 074b69700d..b5b1452933 100644 --- a/bindings/nodejs/tests/client/outputBuilders.spec.ts +++ b/bindings/nodejs/tests/client/outputBuilders.spec.ts @@ -33,7 +33,7 @@ describe.skip('Output builder methods', () => { }, }); - const hexAddress = Utils.bech32ToHex(addresses[0]); + const address = Utils.parseBech32Address(addresses[0]); const client = await makeClient(); // most simple basic output @@ -41,7 +41,7 @@ describe.skip('Output builder methods', () => { amount: BigInt(1000000), unlockConditions: [ new AddressUnlockCondition( - new Ed25519Address(hexAddress), + address, ), ], }); @@ -57,7 +57,7 @@ describe.skip('Output builder methods', () => { }, }); - const hexAddress = Utils.bech32ToHex(addresses[0]); + const address = Utils.parseBech32Address(addresses[0]); const client = await makeClient(); const accountId = @@ -67,7 +67,7 @@ describe.skip('Output builder methods', () => { accountId, unlockConditions: [ new AddressUnlockCondition( - new Ed25519Address(hexAddress), + address, ), ], }); @@ -110,14 +110,14 @@ describe.skip('Output builder methods', () => { }, }); - const hexAddress = Utils.bech32ToHex(addresses[0]); + const address = Utils.parseBech32Address(addresses[0]); // most simple nft output const nftOutput = await client.buildNftOutput({ nftId: '0x7ffec9e1233204d9c6dce6812b1539ee96af691ca2e4d9065daa85907d33e5d3', unlockConditions: [ new AddressUnlockCondition( - new Ed25519Address(hexAddress), + address ), ], }); diff --git a/bindings/nodejs/tests/client/utilityMethods.spec.ts b/bindings/nodejs/tests/client/utilityMethods.spec.ts index c1bc80b825..f238292604 100644 --- a/bindings/nodejs/tests/client/utilityMethods.spec.ts +++ b/bindings/nodejs/tests/client/utilityMethods.spec.ts @@ -7,7 +7,7 @@ import 'dotenv/config'; import { Client, SecretManager, Utils } from '../../'; import '../customMatchers'; -import { SlotCommitment } from '../../out/types/block/slot'; +import { AccountAddress, Ed25519Address } from '../../lib'; async function makeOfflineClient(): Promise { return await Client.create({}); @@ -30,19 +30,19 @@ describe('Client utility methods', () => { }); it('converts address to hex and bech32', async () => { - const address = + const bech32address = 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy'; - const hexAddress = Utils.bech32ToHex(address); + const address = Utils.parseBech32Address(bech32address) as Ed25519Address; - expect(hexAddress.slice(0, 2)).toBe('0x'); + expect(address.pubKeyHash.slice(0, 2)).toBe('0x'); let offlineClient = await makeOfflineClient(); - const bech32Address = await offlineClient.hexToBech32( - hexAddress, + const convertedBech32Address = await offlineClient.addressToBech32( + address, 'rms', ); - expect(bech32Address).toBe(address); + expect(convertedBech32Address).toBe(bech32address); }); it('account id to address', async () => { @@ -50,12 +50,12 @@ describe('Client utility methods', () => { '0xcf077d276686ba64c0404b9eb2d15556782113c5a1985f262b70f9964d3bbd7f'; const offlineClient = await makeOfflineClient(); - const accountAddress = await offlineClient.accountIdToBech32( - accountId, + const bech32AccountAddress = await offlineClient.addressToBech32( + new AccountAddress(accountId), 'rms', ); - expect(accountAddress).toBe( + expect(bech32AccountAddress).toBe( 'rms1pr8swlf8v6rt5exqgp9eavk324t8sggnckseshex9dc0n9jd8w7h7wcnhn7', ); }); diff --git a/bindings/nodejs/tests/secret_manager/secret_manager.spec.ts b/bindings/nodejs/tests/secret_manager/secret_manager.spec.ts new file mode 100644 index 0000000000..98a4a00336 --- /dev/null +++ b/bindings/nodejs/tests/secret_manager/secret_manager.spec.ts @@ -0,0 +1,48 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import 'reflect-metadata'; + +import { describe, it, expect } from '@jest/globals'; +import { + CoinType, + SecretManager, + Utils, +} from '../../lib/'; + +describe('SecretManager', () => { + it('generate IOTA Ed25519 address', async () => { + const mnemonicSecretManager = { + mnemonic: "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast" + }; + + let bech32_hrp = Utils.iotaMainnetProtocolParameters().bech32Hrp; + + const secretManager = SecretManager.create(mnemonicSecretManager); + const addresses = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + bech32Hrp: bech32_hrp, + }); + + expect(addresses[0]).toEqual('iota1qpg2xkj66wwgn8p2ggnp7p582gj8g6p79us5hve2tsudzpsr2ap4skprwjg'); + + }, 20000); + + it('generate Shimmer Ed25519 address', async () => { + const mnemonicSecretManager = { + mnemonic: "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast" + }; + + let bech32_hrp = Utils.shimmerMainnetProtocolParameters().bech32Hrp; + + const secretManager = SecretManager.create(mnemonicSecretManager); + const addresses = await secretManager.generateEd25519Addresses({ + coinType: CoinType.Shimmer, + bech32Hrp: bech32_hrp, + range: { start: 0, end: 1 }, + }); + + expect(addresses[0]).toEqual('smr1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6ckj80y'); + + }, 20000); +}); diff --git a/bindings/nodejs/tests/types/protocol_parameters.spec.ts b/bindings/nodejs/tests/types/protocol_parameters.spec.ts index cc63ccd212..955564beb7 100644 --- a/bindings/nodejs/tests/types/protocol_parameters.spec.ts +++ b/bindings/nodejs/tests/types/protocol_parameters.spec.ts @@ -5,7 +5,7 @@ import 'reflect-metadata'; import { expect, describe, it } from '@jest/globals'; import * as protocol_parameters from '../../../../sdk/tests/types/fixtures/protocol_parameters.json'; -import { ProtocolParameters } from '../../lib/types/models/info/node-info-protocol'; +import { ProtocolParameters } from '../../lib/types/models/api/info/node-info-protocol'; import { Utils } from '../../'; describe('ProtocolParameters tests', () => { @@ -15,7 +15,7 @@ describe('ProtocolParameters tests', () => { const params: ProtocolParameters = JSON.parse(JSON.stringify(protocol_parameters.params)); const hash = Utils.protocolParametersHash(params); const expected_hash = protocol_parameters.hash; - + expect(hash).toEqual(expected_hash); }); }); diff --git a/bindings/nodejs/tests/utils/utils.spec.ts b/bindings/nodejs/tests/utils/utils.spec.ts index 0ee18ec0bd..b7d89eef17 100644 --- a/bindings/nodejs/tests/utils/utils.spec.ts +++ b/bindings/nodejs/tests/utils/utils.spec.ts @@ -9,7 +9,7 @@ import { BasicOutput, BlockId, OutputId, TransactionId, Utils } from '../../out' import '../customMatchers'; import { SlotCommitment } from '../../out/types/block/slot'; import * as protocol_parameters from '../../../../sdk/tests/types/fixtures/protocol_parameters.json'; -import { ProtocolParameters } from '../../lib/types/models/info/node-info-protocol'; +import { ProtocolParameters } from '../../lib/types/models/api/info/node-info-protocol'; describe('Utils methods', () => { it('invalid mnemonic error', () => { @@ -25,9 +25,9 @@ describe('Utils methods', () => { const hexPublicKey = '0x2baaf3bca8ace9f862e60184bd3e79df25ff230f7eaaa4c7f03daa9833ba854a'; - const address = Utils.hexPublicKeyToBech32Address(hexPublicKey, 'rms'); + const address = Utils.publicKeyHash(hexPublicKey); - expect(address).toBeValidAddress(); + expect(address.pubKeyHash).toBe('0x96f9de0989e77d0e150e850a5a600e83045fa57419eaf3b20225b763d4e23813'); }); it('validates address', () => { diff --git a/bindings/nodejs/tests/wallet/wallet.spec.ts b/bindings/nodejs/tests/wallet/wallet.spec.ts index 43db94253b..57542e6cd3 100644 --- a/bindings/nodejs/tests/wallet/wallet.spec.ts +++ b/bindings/nodejs/tests/wallet/wallet.spec.ts @@ -4,7 +4,13 @@ import 'reflect-metadata'; import { describe, it, expect } from '@jest/globals'; -import { Wallet, CoinType, WalletOptions, SecretManager } from '../../lib/'; +import { + Wallet, + CoinType, + WalletOptions, + SecretManager, + Utils, +} from '../../lib/'; describe('Wallet', () => { it('create wallet', async () => { @@ -20,7 +26,9 @@ describe('Wallet', () => { const secretManager = SecretManager.create(strongholdSecretManager); - await secretManager.storeMnemonic('vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim',); + await secretManager.storeMnemonic( + 'vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim', + ); const wallet_address = await secretManager.generateEd25519Addresses({ coinType: CoinType.IOTA, @@ -37,6 +45,7 @@ describe('Wallet', () => { storagePath: './test-create-wallet', clientOptions: { nodes: ['https://api.testnet.shimmer.network'], + protocolParameters: Utils.iotaMainnetProtocolParameters(), }, bipPath: { coinType: CoinType.IOTA, @@ -44,14 +53,12 @@ describe('Wallet', () => { secretManager: strongholdSecretManager, }; - const wallet = await Wallet.create(walletOptions); await wallet.destroy(); removeDir(storagePath); }, 20000); - it('recreate wallet', async () => { let storagePath = 'test-recreate-wallet'; removeDir(storagePath); @@ -65,7 +72,9 @@ describe('Wallet', () => { const secretManager = SecretManager.create(strongholdSecretManager); - await secretManager.storeMnemonic('vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim',); + await secretManager.storeMnemonic( + 'vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim', + ); const wallet_address = await secretManager.generateEd25519Addresses({ coinType: CoinType.IOTA, @@ -82,6 +91,7 @@ describe('Wallet', () => { storagePath, clientOptions: { nodes: ['https://api.testnet.shimmer.network'], + protocolParameters: Utils.iotaMainnetProtocolParameters(), }, bipPath: { coinType: CoinType.IOTA, @@ -89,12 +99,11 @@ describe('Wallet', () => { secretManager: strongholdSecretManager, }; - const wallet = await Wallet.create(walletOptions); const client = await wallet.getClient(); const hrp = await client.getBech32Hrp(); - expect(hrp).toEqual("smr"); + expect(hrp).toEqual('smr'); await wallet.destroy(); @@ -104,7 +113,7 @@ describe('Wallet', () => { // expect(accounts.length).toStrictEqual(0); await recreatedWallet.destroy(); - removeDir(storagePath) + removeDir(storagePath); }, 20000); it('error after destroy', async () => { @@ -116,8 +125,10 @@ describe('Wallet', () => { snapshotPath: `./${storagePath}/wallet.stronghold`, password: `A12345678*`, }, - } - const secretManager = await SecretManager.create(strongholdSecretManager); + }; + const secretManager = await SecretManager.create( + strongholdSecretManager, + ); await secretManager.storeMnemonic( 'vital give early extra blind skin eight discover scissors there globe deal goat fat load robot return rate fragile recycle select live ordinary claim', ); @@ -137,6 +148,7 @@ describe('Wallet', () => { storagePath, clientOptions: { nodes: ['https://api.testnet.shimmer.network'], + protocolParameters: Utils.iotaMainnetProtocolParameters(), }, bipPath: { coinType: CoinType.IOTA, @@ -163,7 +175,7 @@ describe('Wallet', () => { } removeDir(storagePath); }, 35000); -}) +}); function removeDir(storagePath: string) { const fs = require('fs'); diff --git a/bindings/python/examples/client/04_get_output.py b/bindings/python/examples/client/04_get_output.py index 8e1edde5ec..35920226d8 100644 --- a/bindings/python/examples/client/04_get_output.py +++ b/bindings/python/examples/client/04_get_output.py @@ -2,7 +2,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Client load_dotenv() diff --git a/bindings/python/examples/client/05_get_address_balance.py b/bindings/python/examples/client/05_get_address_balance.py index d849fd69ea..2c9a31857a 100644 --- a/bindings/python/examples/client/05_get_address_balance.py +++ b/bindings/python/examples/client/05_get_address_balance.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Client, NodeIndexerAPI, FeatureType load_dotenv() diff --git a/bindings/python/examples/client/07_get_block.py b/bindings/python/examples/client/07_get_block.py index 8c87fcce24..f740b91195 100644 --- a/bindings/python/examples/client/07_get_block.py +++ b/bindings/python/examples/client/07_get_block.py @@ -3,7 +3,6 @@ from dataclasses import asdict from dotenv import load_dotenv - from iota_sdk import Client load_dotenv() diff --git a/bindings/python/examples/client/build_account.py b/bindings/python/examples/client/build_account.py index 95efa4f88d..bc7369c233 100644 --- a/bindings/python/examples/client/build_account.py +++ b/bindings/python/examples/client/build_account.py @@ -2,8 +2,7 @@ import os from dotenv import load_dotenv - -from iota_sdk import (Client, Ed25519Address, AddressUnlockCondition, +from iota_sdk import (Client, AddressUnlockCondition, IssuerFeature, MetadataFeature, SenderFeature, Utils, utf8_to_hex) @@ -15,20 +14,20 @@ # Create a Client instance client = Client(nodes=[node_url]) -hexAddress = Utils.bech32_to_hex( +address = Utils.parse_bech32_address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy') account_id = '0x0000000000000000000000000000000000000000000000000000000000000000' unlock_conditions = [ - AddressUnlockCondition(Ed25519Address(hexAddress)), + AddressUnlockCondition(address), ] features = [ - SenderFeature(Ed25519Address(hexAddress)), - MetadataFeature(utf8_to_hex('Hello, World!')) + SenderFeature(address), + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ] immutable_features = [ - IssuerFeature(Ed25519Address(hexAddress)), - MetadataFeature(utf8_to_hex('Hello, World!')) + IssuerFeature(address), + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ] # Build account output diff --git a/bindings/python/examples/client/build_basic.py b/bindings/python/examples/client/build_basic.py index 455ec49084..00839eab31 100644 --- a/bindings/python/examples/client/build_basic.py +++ b/bindings/python/examples/client/build_basic.py @@ -2,8 +2,7 @@ import os from dotenv import load_dotenv - -from iota_sdk import (AddressUnlockCondition, Client, Ed25519Address, +from iota_sdk import (AddressUnlockCondition, Client, ExpirationUnlockCondition, MetadataFeature, SenderFeature, StorageDepositReturnUnlockCondition, TagFeature, TimelockUnlockCondition, Utils, utf8_to_hex) @@ -15,12 +14,10 @@ # Create a Client instance client = Client(nodes=[node_url]) -hex_address = Utils.bech32_to_hex( +address = Utils.parse_bech32_address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy') -address_unlock_condition = AddressUnlockCondition( - Ed25519Address(hex_address) -) +address_unlock_condition = AddressUnlockCondition(address) # Build most basic output with amount and a single address unlock condition basic_output = client.build_basic_output( @@ -37,7 +34,7 @@ address_unlock_condition, ], features=[ - MetadataFeature(utf8_to_hex('Hello, World!')) + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ], amount=1000000, ) @@ -48,7 +45,7 @@ unlock_conditions=[ address_unlock_condition, StorageDepositReturnUnlockCondition( - return_address=Ed25519Address(hex_address), + return_address=address, amount=1000000 ) ], @@ -61,7 +58,7 @@ unlock_conditions=[ address_unlock_condition, ExpirationUnlockCondition( - return_address=Ed25519Address(hex_address), + return_address=address, slot_index=1 ) ], @@ -97,7 +94,7 @@ address_unlock_condition ], features=[ - SenderFeature(Ed25519Address(hex_address)) + SenderFeature(address) ], amount=1000000, ) diff --git a/bindings/python/examples/client/build_foundry.py b/bindings/python/examples/client/build_foundry.py index f43c2a2751..7dec64c5ea 100644 --- a/bindings/python/examples/client/build_foundry.py +++ b/bindings/python/examples/client/build_foundry.py @@ -2,7 +2,6 @@ import os from dotenv import load_dotenv - from iota_sdk import (AccountAddress, Client, ImmutableAccountAddressUnlockCondition, SimpleTokenScheme) diff --git a/bindings/python/examples/client/build_nft.py b/bindings/python/examples/client/build_nft.py index fd30b5cb6b..85c95677e3 100644 --- a/bindings/python/examples/client/build_nft.py +++ b/bindings/python/examples/client/build_nft.py @@ -2,11 +2,9 @@ import os from dotenv import load_dotenv - from iota_sdk import ( AddressUnlockCondition, Client, - Ed25519Address, IssuerFeature, MetadataFeature, SenderFeature, @@ -23,7 +21,7 @@ # Create a Client instance client = Client(nodes=[node_url]) -hexAddress = Utils.bech32_to_hex( +address = Utils.parse_bech32_address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy') tip_27_immutable_metadata = Irc27Metadata( @@ -35,16 +33,16 @@ # Build NFT output nft_output = client.build_nft_output( unlock_conditions=[ - AddressUnlockCondition(Ed25519Address(hexAddress)) + AddressUnlockCondition(address) ], # NftId needs to be null the first time nft_id='0x0000000000000000000000000000000000000000000000000000000000000000', immutable_features=[ - IssuerFeature(Ed25519Address(hexAddress)), + IssuerFeature(address), tip_27_immutable_metadata.as_feature() ], features=[ - SenderFeature(Ed25519Address(hexAddress)), + SenderFeature(address), MetadataFeature(utf8_to_hex('mutable metadata')), TagFeature(utf8_to_hex('my tag')) ] diff --git a/bindings/python/examples/client/get_raw_block.py b/bindings/python/examples/client/get_raw_block.py index ac9fd62fdb..43c3827e52 100644 --- a/bindings/python/examples/client/get_raw_block.py +++ b/bindings/python/examples/client/get_raw_block.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Client load_dotenv() diff --git a/bindings/python/examples/client/get_validators.py b/bindings/python/examples/client/get_validators.py new file mode 100644 index 0000000000..6a1a883197 --- /dev/null +++ b/bindings/python/examples/client/get_validators.py @@ -0,0 +1,24 @@ +import dataclasses +import json +import os +import sys + +from dotenv import load_dotenv +from iota_sdk import Client + +load_dotenv() + +node_url = os.environ.get('NODE_URL', 'https://api.testnet.shimmer.network') +page_size = 1 +cursor = "" + +if len(sys.argv) > 1: + page_size = int(sys.argv[1]) + if len(sys.argv) > 2: + cursor = sys.argv[2] + +# Create a Client instance +client = Client(nodes=[node_url]) + +validators = client.get_validators(page_size, cursor) +print(f'{json.dumps(dataclasses.asdict(validators), indent=4)}') diff --git a/bindings/python/examples/client/getting_started.py b/bindings/python/examples/client/getting_started.py index 94d56433a3..d02552eb1e 100644 --- a/bindings/python/examples/client/getting_started.py +++ b/bindings/python/examples/client/getting_started.py @@ -4,5 +4,5 @@ client = Client(nodes=['https://api.testnet.shimmer.network']) # Get the node info -node_info = client.get_info() +node_info = client.get_node_info() print(f'{node_info}') diff --git a/bindings/python/examples/client/logger.py b/bindings/python/examples/client/logger.py index 3d33d60ec8..84fc4e2dcc 100644 --- a/bindings/python/examples/client/logger.py +++ b/bindings/python/examples/client/logger.py @@ -25,5 +25,5 @@ client = Client(nodes=[node_url]) # Get the node info -node_info = client.get_info() +node_info = client.get_node_info() print(f'{node_info}') diff --git a/bindings/python/examples/client/mqtt.py b/bindings/python/examples/client/mqtt.py index c948af27cd..22dcb83b9b 100644 --- a/bindings/python/examples/client/mqtt.py +++ b/bindings/python/examples/client/mqtt.py @@ -8,7 +8,6 @@ import threading from dotenv import load_dotenv - from iota_sdk import Client load_dotenv() @@ -35,7 +34,7 @@ def callback(event): # Topics can be found here -# https://studio.asyncapi.com/?url=https://raw.githubusercontent.com/iotaledger/tips/main/tips/TIP-0028/event-api.yml +# https://studio.asyncapi.com/?url=https://raw.githubusercontent.com/iotaledger/tips/tip48/tips/TIP-0048/asyncapi3.yaml client.listen_mqtt(["blocks"], callback) # Exit after 10 received events diff --git a/bindings/python/examples/exchange/1_create_wallet.py b/bindings/python/examples/exchange/1_create_wallet.py index c013112e88..fb613dc453 100644 --- a/bindings/python/examples/exchange/1_create_wallet.py +++ b/bindings/python/examples/exchange/1_create_wallet.py @@ -6,7 +6,6 @@ import os from dotenv import load_dotenv - from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, SecretManager, SyncOptions, Wallet, WalletOptions, Bip44) diff --git a/bindings/python/examples/exchange/2_generate_address.py b/bindings/python/examples/exchange/2_generate_address.py index 4a329a0d52..fceccaa4ca 100644 --- a/bindings/python/examples/exchange/2_generate_address.py +++ b/bindings/python/examples/exchange/2_generate_address.py @@ -6,7 +6,6 @@ import os from dotenv import load_dotenv - from iota_sdk import StrongholdSecretManager, SecretManager # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/exchange/3_check_balance.py b/bindings/python/examples/exchange/3_check_balance.py index dadcf3ed17..85564ea781 100644 --- a/bindings/python/examples/exchange/3_check_balance.py +++ b/bindings/python/examples/exchange/3_check_balance.py @@ -6,7 +6,6 @@ import os from dotenv import load_dotenv - from iota_sdk import SyncOptions, Wallet, WalletOptions # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/exchange/4_listen_events.py b/bindings/python/examples/exchange/4_listen_events.py index 7de0246462..fdd180f64d 100644 --- a/bindings/python/examples/exchange/4_listen_events.py +++ b/bindings/python/examples/exchange/4_listen_events.py @@ -9,7 +9,6 @@ import time from dotenv import load_dotenv - from iota_sdk import SyncOptions, Wallet, WalletOptions, WalletEventType # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/exchange/5_send_amount.py b/bindings/python/examples/exchange/5_send_amount.py index 62465aa605..53bff0f90c 100644 --- a/bindings/python/examples/exchange/5_send_amount.py +++ b/bindings/python/examples/exchange/5_send_amount.py @@ -6,7 +6,6 @@ import os from dotenv import load_dotenv - from iota_sdk import SyncOptions, Wallet, WalletOptions # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/account_output/create.py b/bindings/python/examples/how_tos/account_output/create.py index 1a5194b42c..758972421f 100644 --- a/bindings/python/examples/how_tos/account_output/create.py +++ b/bindings/python/examples/how_tos/account_output/create.py @@ -2,7 +2,6 @@ import time from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # In this example we will create an account output. diff --git a/bindings/python/examples/how_tos/account_output/destroy.py b/bindings/python/examples/how_tos/account_output/destroy.py index 0ddec8450b..d1ed25fd12 100644 --- a/bindings/python/examples/how_tos/account_output/destroy.py +++ b/bindings/python/examples/how_tos/account_output/destroy.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # In this example we will destroy an account output. diff --git a/bindings/python/examples/how_tos/account_output/implicit_account_creation.py b/bindings/python/examples/how_tos/account_output/implicit_account_creation.py index abbdd53fa2..cb866ea50a 100644 --- a/bindings/python/examples/how_tos/account_output/implicit_account_creation.py +++ b/bindings/python/examples/how_tos/account_output/implicit_account_creation.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # In this example, we create an implicit account creation address. diff --git a/bindings/python/examples/how_tos/account_output/request_funds.py b/bindings/python/examples/how_tos/account_output/request_funds.py index f531e82803..63edd41911 100644 --- a/bindings/python/examples/how_tos/account_output/request_funds.py +++ b/bindings/python/examples/how_tos/account_output/request_funds.py @@ -2,8 +2,7 @@ import time from dotenv import load_dotenv - -from iota_sdk import Wallet, WalletOptions, Utils, SyncOptions, WalletSyncOptions +from iota_sdk import AccountAddress, Wallet, WalletOptions, Utils, SyncOptions, WalletSyncOptions # In this example we request funds to the wallet's first account output # address. @@ -28,8 +27,8 @@ print(f'Account Id: {account_id}') # Get Account address -account_address = Utils.account_id_to_bech32( - account_id, wallet.get_client().get_bech32_hrp()) +account_address = Utils.address_to_bech32( + AccountAddress(account_id), wallet.get_client().get_bech32_hrp()) faucet_response = wallet.get_client().request_funds_from_faucet( FAUCET_URL, account_address) print(faucet_response) diff --git a/bindings/python/examples/how_tos/account_output/send_amount.py b/bindings/python/examples/how_tos/account_output/send_amount.py index d41e4edee8..50a1730386 100644 --- a/bindings/python/examples/how_tos/account_output/send_amount.py +++ b/bindings/python/examples/how_tos/account_output/send_amount.py @@ -1,8 +1,7 @@ import os from dotenv import load_dotenv - -from iota_sdk import Wallet, WalletOptions, Utils, NodeIndexerAPI, SyncOptions, WalletSyncOptions, SendParams +from iota_sdk import AccountAddress, Wallet, WalletOptions, Utils, NodeIndexerAPI, SyncOptions, WalletSyncOptions, SendParams # In this example we send funds from an account output. @@ -27,8 +26,8 @@ print(f'Account Id: {account_id}') # Get account address -account_address = Utils.account_id_to_bech32( - account_id, wallet.get_client().get_bech32_hrp()) +account_address = Utils.address_to_bech32( + AccountAddress(account_id), wallet.get_client().get_bech32_hrp()) # Find first output unlockable by the account address query_parameters = NodeIndexerAPI.BasicOutputQueryParameters( diff --git a/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py index 4f98725259..f7151d8c20 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/advanced_transaction.py @@ -3,11 +3,9 @@ import time from dotenv import load_dotenv - from iota_sdk import ( AddressUnlockCondition, Client, - Ed25519Address, Wallet, WalletOptions, Utils, @@ -40,8 +38,8 @@ basic_output = Client().build_basic_output( unlock_conditions=[ AddressUnlockCondition( - Ed25519Address( - Utils.bech32_to_hex('rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy')) + Utils.parse_bech32_address( + 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy') ), TimelockUnlockCondition(in_an_hour), ], @@ -50,8 +48,8 @@ transaction = wallet.send_outputs([basic_output]) print(f'Transaction sent: {transaction.transaction_id}') -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) print( - f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}') + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') diff --git a/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py index ca228965d2..60c24d06a5 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/claim_transaction.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # This example uses secrets in environment variables for simplicity which @@ -32,6 +31,7 @@ transaction = wallet.claim_outputs(output_ids) print(f'Transaction sent: {transaction.transaction_id}') -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) -print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}') +print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') diff --git a/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py b/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py index 04184dcf56..9f680d04b5 100644 --- a/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py +++ b/bindings/python/examples/how_tos/advanced_transactions/send_micro_transaction.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions, SendParams # This example uses secrets in environment variables for simplicity which @@ -30,8 +29,8 @@ transaction = wallet.send_with_params(params, {"allowMicroAmount": True}) print(f'Transaction sent: {transaction.transaction_id}') -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) print( - f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}') + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') diff --git a/bindings/python/examples/how_tos/client/get_health.py b/bindings/python/examples/how_tos/client/get_health.py index b66fad5f44..b4dcae6824 100644 --- a/bindings/python/examples/how_tos/client/get_health.py +++ b/bindings/python/examples/how_tos/client/get_health.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Client load_dotenv() diff --git a/bindings/python/examples/how_tos/client/get_info.py b/bindings/python/examples/how_tos/client/get_info.py index ff7e6c75a0..5799b142bf 100644 --- a/bindings/python/examples/how_tos/client/get_info.py +++ b/bindings/python/examples/how_tos/client/get_info.py @@ -3,7 +3,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Client load_dotenv() @@ -14,5 +13,5 @@ client = Client(nodes=[node_url]) # Get the node info -node_info = client.get_info().node_info +node_info = client.get_node_info() print(f'{json.dumps(dataclasses.asdict(node_info), indent=4)}') diff --git a/bindings/python/examples/how_tos/client/get_outputs.py b/bindings/python/examples/how_tos/client/get_outputs.py index 9e03b62dc7..aa0738eb0f 100644 --- a/bindings/python/examples/how_tos/client/get_outputs.py +++ b/bindings/python/examples/how_tos/client/get_outputs.py @@ -2,7 +2,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Client, NodeIndexerAPI load_dotenv() diff --git a/bindings/python/examples/how_tos/native_tokens/burn.py b/bindings/python/examples/how_tos/native_tokens/burn.py index 96c0b83c46..4ce8f6bd57 100644 --- a/bindings/python/examples/how_tos/native_tokens/burn.py +++ b/bindings/python/examples/how_tos/native_tokens/burn.py @@ -28,10 +28,10 @@ token_id, burn_amount).send() print(f'Transaction sent: {transaction.transaction_id}') -# Wait for transaction to get accepted -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) -print(f'Tx accepted in block: {os.environ["EXPLORER_URL"]}/block/{block_id}') +print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') balance = wallet.sync() available_balance = balance.native_tokens[token_id].available diff --git a/bindings/python/examples/how_tos/native_tokens/create.py b/bindings/python/examples/how_tos/native_tokens/create.py index f3b0d045fd..e8abd0984c 100644 --- a/bindings/python/examples/how_tos/native_tokens/create.py +++ b/bindings/python/examples/how_tos/native_tokens/create.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import CreateNativeTokenParams, Wallet, WalletOptions, Irc30Metadata load_dotenv() @@ -26,11 +25,10 @@ transaction = wallet.create_account_output(None, None) print(f'Transaction sent: {transaction.transaction_id}') - # Wait for transaction to get accepted - block_id = wallet.wait_for_transaction_acceptance( + wallet.wait_for_transaction_acceptance( transaction.transaction_id) print( - f'Tx accepted in block: {os.environ["EXPLORER_URL"]}/block/{block_id}') + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') wallet.sync() print("Wallet synced") @@ -51,10 +49,10 @@ transaction = prepared_transaction.send() print(f'Transaction sent: {transaction.transaction_id}') -# Wait for transaction to get accepted -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) -print(f'Tx accepted in block: {os.environ["EXPLORER_URL"]}/block/{block_id}') +print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') print(f'Created token: {transaction.token_id}') diff --git a/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py b/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py index 65d0d69d75..2aba519a66 100644 --- a/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py +++ b/bindings/python/examples/how_tos/native_tokens/destroy_foundry.py @@ -24,10 +24,10 @@ transaction = wallet.prepare_destroy_foundry(foundry_id).send() print(f'Transaction sent: {transaction.transaction_id}') -# Wait for transaction to get accepted -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) -print(f'Tx accepted in block: {os.environ["EXPLORER_URL"]}/block/{block_id}') +print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') balance = wallet.sync() print(f'Foundries after destroying: {len(balance.foundries)}') diff --git a/bindings/python/examples/how_tos/native_tokens/melt.py b/bindings/python/examples/how_tos/native_tokens/melt.py index df24c44080..cb5ee6e4e4 100644 --- a/bindings/python/examples/how_tos/native_tokens/melt.py +++ b/bindings/python/examples/how_tos/native_tokens/melt.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions load_dotenv() @@ -30,10 +29,10 @@ transaction = wallet.melt_native_token(token_id, melt_amount) print(f'Transaction sent: {transaction.transaction_id}') -# Wait for transaction to get accepted -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) -print(f'Tx accepted in block: {os.environ["EXPLORER_URL"]}/block/{block_id}') +print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') balance = wallet.sync() available_balance = balance.native_tokens[token_id].available diff --git a/bindings/python/examples/how_tos/native_tokens/mint.py b/bindings/python/examples/how_tos/native_tokens/mint.py index f5989f88ce..283afa7d99 100644 --- a/bindings/python/examples/how_tos/native_tokens/mint.py +++ b/bindings/python/examples/how_tos/native_tokens/mint.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions load_dotenv() @@ -30,10 +29,10 @@ transaction = wallet.mint_native_token(token_id, mint_amount) print(f'Transaction sent: {transaction.transaction_id}') -# Wait for transaction to get accepted -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) -print(f'Tx accepted in block: {os.environ["EXPLORER_URL"]}/block/{block_id}') +print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') balance = wallet.sync() available_balance = balance.native_tokens[token_id].available diff --git a/bindings/python/examples/how_tos/native_tokens/send.py b/bindings/python/examples/how_tos/native_tokens/send.py index 8ddfc84e03..f00d172385 100644 --- a/bindings/python/examples/how_tos/native_tokens/send.py +++ b/bindings/python/examples/how_tos/native_tokens/send.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import SendNativeTokenParams, Wallet, WalletOptions load_dotenv() @@ -33,10 +32,10 @@ transaction = wallet.send_native_tokens(outputs, None) print(f'Transaction sent: {transaction.transaction_id}') -# Wait for transaction to get accepted -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) -print(f'Tx accepted in block: {os.environ["EXPLORER_URL"]}/block/{block_id}') +print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') balance = wallet.sync() available_balance = balance.native_tokens[token_id].available diff --git a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py index 1b06ac99e7..634d164449 100644 --- a/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/00_mint_issuer_nft.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import MintNftParams, Utils, Wallet, WalletOptions, utf8_to_hex load_dotenv() @@ -28,12 +27,11 @@ tx = wallet.mint_nfts([params]) -# Wait for transaction to get accepted -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( tx.transaction_id) print( - f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}') + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{tx.transaction_id}') transaction = tx.payload.transaction diff --git a/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py b/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py index 704744595e..53a05f3a50 100644 --- a/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py +++ b/bindings/python/examples/how_tos/nft_collection/01_mint_collection_nft.py @@ -2,8 +2,7 @@ import sys from dotenv import load_dotenv - -from iota_sdk import MintNftParams, Utils, Wallet, WalletOptions, Irc27Metadata +from iota_sdk import HexStr, MintNftParams, NFTAddress, Utils, Wallet, WalletOptions, Irc27Metadata load_dotenv() @@ -17,7 +16,7 @@ if len(sys.argv) < 2: raise Exception("missing example argument: ISSUER_NFT_ID") -issuer_nft_id = sys.argv[1] +issuer_nft_id = HexStr(sys.argv[1]) wallet = Wallet(WalletOptions(storage_path=os.environ.get('WALLET_DB_PATH'))) @@ -30,12 +29,12 @@ wallet.sync() bech32_hrp = wallet.get_client().get_bech32_hrp() -issuer = Utils.nft_id_to_bech32(issuer_nft_id, bech32_hrp) +issuer = Utils.address_to_bech32(NFTAddress(issuer_nft_id), bech32_hrp) def get_immutable_metadata(index: int) -> str: """Returns the immutable metadata for the NFT with the given index""" - Irc27Metadata( + return Irc27Metadata( "video/mp4", "https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT", "Shimmer OG NFT #" + str(index), @@ -65,11 +64,11 @@ def get_immutable_metadata(index: int) -> str: ) transaction = wallet.mint_nfts(chunk) - # Wait for transaction to get accepted - block_id = wallet.wait_for_transaction_acceptance( + wallet.wait_for_transaction_acceptance( transaction.transaction_id) - print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}') + print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') # Sync so the new outputs are available again for new transactions wallet.sync() diff --git a/bindings/python/examples/how_tos/nfts/burn_nft.py b/bindings/python/examples/how_tos/nfts/burn_nft.py index aa51158a18..d68e5cbb63 100644 --- a/bindings/python/examples/how_tos/nfts/burn_nft.py +++ b/bindings/python/examples/how_tos/nfts/burn_nft.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions load_dotenv() diff --git a/bindings/python/examples/how_tos/nfts/mint_nft.py b/bindings/python/examples/how_tos/nfts/mint_nft.py index ea675ab1fc..d0de2658e8 100644 --- a/bindings/python/examples/how_tos/nfts/mint_nft.py +++ b/bindings/python/examples/how_tos/nfts/mint_nft.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import MintNftParams, Wallet, WalletOptions, utf8_to_hex # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/nfts/send_nft.py b/bindings/python/examples/how_tos/nfts/send_nft.py index 9908e5ca6a..1c2f3c0eeb 100644 --- a/bindings/python/examples/how_tos/nfts/send_nft.py +++ b/bindings/python/examples/how_tos/nfts/send_nft.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import SendNftParams, Wallet, WalletOptions load_dotenv() diff --git a/bindings/python/examples/how_tos/outputs/features.py b/bindings/python/examples/how_tos/outputs/features.py index c8fc009baa..210e82623d 100644 --- a/bindings/python/examples/how_tos/outputs/features.py +++ b/bindings/python/examples/how_tos/outputs/features.py @@ -2,11 +2,9 @@ from dataclasses import asdict from dotenv import load_dotenv - from iota_sdk import ( AddressUnlockCondition, Client, - Ed25519Address, Utils, SenderFeature, IssuerFeature, @@ -19,12 +17,10 @@ client = Client() -hex_address = Utils.bech32_to_hex( +address = Utils.parse_bech32_address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy') -address_unlock_condition = AddressUnlockCondition( - Ed25519Address(hex_address) -) +address_unlock_condition = AddressUnlockCondition(address) # Output with sender feature nft_output = client.build_nft_output( @@ -33,7 +29,7 @@ address_unlock_condition ], features=[ - SenderFeature(Ed25519Address(hex_address)) + SenderFeature(address) ], ) outputs = [nft_output] @@ -45,7 +41,7 @@ address_unlock_condition, ], immutable_features=[ - IssuerFeature(Ed25519Address(hex_address)) + IssuerFeature(address) ], ) outputs.append(nft_output) @@ -57,7 +53,7 @@ address_unlock_condition, ], features=[ - MetadataFeature(utf8_to_hex('Hello, World!')) + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ], ) outputs.append(nft_output) @@ -69,7 +65,7 @@ address_unlock_condition, ], immutable_features=[ - MetadataFeature(utf8_to_hex('Hello, World!')) + MetadataFeature({'data': utf8_to_hex('Hello, World!')}) ], ) outputs.append(nft_output) diff --git a/bindings/python/examples/how_tos/outputs/unlock_conditions.py b/bindings/python/examples/how_tos/outputs/unlock_conditions.py index 01ee5db20b..e0d349cc8c 100644 --- a/bindings/python/examples/how_tos/outputs/unlock_conditions.py +++ b/bindings/python/examples/how_tos/outputs/unlock_conditions.py @@ -3,9 +3,7 @@ from dotenv import load_dotenv from iota_sdk import ( AddressUnlockCondition, - AccountAddress, Client, - Ed25519Address, Utils, ExpirationUnlockCondition, SimpleTokenScheme, @@ -18,15 +16,13 @@ client = Client() -hex_address = Utils.bech32_to_hex( +ed25519_address = Utils.parse_bech32_address( 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy') -account_hex_address = Utils.bech32_to_hex( +account_address = Utils.parse_bech32_address( 'rms1pr59qm43mjtvhcajfmupqf23x29llam88yecn6pyul80rx099krmv2fnnux') -address_unlock_condition = AddressUnlockCondition( - Ed25519Address(hex_address) -) +address_unlock_condition = AddressUnlockCondition(ed25519_address) token_scheme = SimpleTokenScheme(50, 0, 100) @@ -42,7 +38,7 @@ address_unlock_condition, StorageDepositReturnUnlockCondition( 1000000, - Ed25519Address(hex_address), + ed25519_address, ), ], ) @@ -63,7 +59,7 @@ address_unlock_condition, ExpirationUnlockCondition( 1, - Ed25519Address(hex_address), + ed25519_address, ), ], ) @@ -75,7 +71,7 @@ token_scheme=token_scheme, unlock_conditions=[ ImmutableAccountAddressUnlockCondition( - AccountAddress(account_hex_address), + account_address, ), ], ) diff --git a/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py b/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py index 95cb574604..a004efa417 100644 --- a/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py +++ b/bindings/python/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import (Bip44, CoinType, SecretManager, StrongholdSecretManager, Utils, utf8_to_hex) @@ -39,6 +38,7 @@ print( f'Public key: {ed25519_signature.public_key}\nSignature: {ed25519_signature.signature}') -bech32_address = Utils.hex_public_key_to_bech32_address( - ed25519_signature.public_key, "rms") +bech32_address = Utils.address_to_bech32( + Utils.public_key_hash(ed25519_signature.public_key), + "rms") print(f'Address: {bech32_address}') diff --git a/bindings/python/examples/how_tos/sign_evm/sign_evm.py b/bindings/python/examples/how_tos/sign_evm/sign_evm.py index 8e0f34f22d..c66f407ab1 100644 --- a/bindings/python/examples/how_tos/sign_evm/sign_evm.py +++ b/bindings/python/examples/how_tos/sign_evm/sign_evm.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import (Bip44, CoinType, SecretManager, StrongholdSecretManager, utf8_to_hex) diff --git a/bindings/python/examples/how_tos/simple_transaction/request_funds.py b/bindings/python/examples/how_tos/simple_transaction/request_funds.py index e97507b006..6080d8c331 100644 --- a/bindings/python/examples/how_tos/simple_transaction/request_funds.py +++ b/bindings/python/examples/how_tos/simple_transaction/request_funds.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # This example requests funds from the faucet diff --git a/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py b/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py index 8da4b986bd..1b8e6647df 100644 --- a/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py +++ b/bindings/python/examples/how_tos/simple_transaction/simple_transaction.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import SendParams, Wallet, WalletOptions # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/how_tos/wallet/check_balance.py b/bindings/python/examples/how_tos/wallet/check_balance.py index 4c85509ddc..31840767da 100644 --- a/bindings/python/examples/how_tos/wallet/check_balance.py +++ b/bindings/python/examples/how_tos/wallet/check_balance.py @@ -2,7 +2,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # This example checks the balance of a wallet. diff --git a/bindings/python/examples/how_tos/wallet/consolidate_outputs.py b/bindings/python/examples/how_tos/wallet/consolidate_outputs.py index feedaee4b7..1d623980dd 100644 --- a/bindings/python/examples/how_tos/wallet/consolidate_outputs.py +++ b/bindings/python/examples/how_tos/wallet/consolidate_outputs.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import ConsolidationParams, Utils, Wallet, WalletOptions, FeatureType # In this example we will consolidate basic outputs from a wallet with only an AddressUnlockCondition by sending @@ -33,7 +32,7 @@ for i, output_data in enumerate(outputs): print(f'OUTPUT #{i}') print( - f'- address: #{Utils.hex_to_bech32(output_data.address.pub_key_hash, "rms")}') + f'- address: #{Utils.address_to_bech32(output_data.address, "rms")}') print(f'- amount: #{output_data.output.amount}') native_tokens = [ @@ -50,12 +49,11 @@ print('Transaction sent: ', transaction.transaction_id) # Wait for the consolidation transaction to get accepted -block_id = wallet.wait_for_transaction_acceptance( +wallet.wait_for_transaction_acceptance( transaction.transaction_id) print( - f'Transaction accepted: {os.environ["EXPLORER_URL"]}/block/{block_id}' -) + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') # Sync wallet wallet.sync() @@ -67,7 +65,7 @@ for i, output_data in enumerate(outputs): print(f'OUTPUT #{i}') print( - f'- address: #{Utils.hex_to_bech32(output_data.address.pub_key_hash, "rms")}') + f'- address: #{Utils.address_to_bech32(output_data.address, "rms")}') print(f'- amount: #{output_data.output.amount}') native_tokens = [ diff --git a/bindings/python/examples/how_tos/wallet/create_address.py b/bindings/python/examples/how_tos/wallet/create_address.py index 929385a8d4..d1317a4587 100644 --- a/bindings/python/examples/how_tos/wallet/create_address.py +++ b/bindings/python/examples/how_tos/wallet/create_address.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import StrongholdSecretManager, SecretManager load_dotenv() diff --git a/bindings/python/examples/how_tos/wallet/create_wallet.py b/bindings/python/examples/how_tos/wallet/create_wallet.py index c956909b76..6950658d05 100644 --- a/bindings/python/examples/how_tos/wallet/create_wallet.py +++ b/bindings/python/examples/how_tos/wallet/create_wallet.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, SecretManager, Wallet, WalletOptions, Bip44 load_dotenv() diff --git a/bindings/python/examples/how_tos/wallet/list_outputs.py b/bindings/python/examples/how_tos/wallet/list_outputs.py index aaabc7ee45..a48a3eba99 100644 --- a/bindings/python/examples/how_tos/wallet/list_outputs.py +++ b/bindings/python/examples/how_tos/wallet/list_outputs.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # In this example we will get outputs stored in the wallet diff --git a/bindings/python/examples/how_tos/wallet/list_transactions.py b/bindings/python/examples/how_tos/wallet/list_transactions.py index af354a5d61..2b60b3692c 100644 --- a/bindings/python/examples/how_tos/wallet/list_transactions.py +++ b/bindings/python/examples/how_tos/wallet/list_transactions.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # In this example we will list transactions diff --git a/bindings/python/examples/how_tos/wallet/print_address.py b/bindings/python/examples/how_tos/wallet/print_address.py index bcd71f0974..5fc7ed36d0 100644 --- a/bindings/python/examples/how_tos/wallet/print_address.py +++ b/bindings/python/examples/how_tos/wallet/print_address.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions # This example uses secrets in environment variables for simplicity which diff --git a/bindings/python/examples/secret_manager/generate_addresses.py b/bindings/python/examples/secret_manager/generate_addresses.py index 899c8d12b7..8dac2b221d 100644 --- a/bindings/python/examples/secret_manager/generate_addresses.py +++ b/bindings/python/examples/secret_manager/generate_addresses.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import CoinType, MnemonicSecretManager, SecretManager load_dotenv() diff --git a/bindings/python/examples/secret_manager/stronghold.py b/bindings/python/examples/secret_manager/stronghold.py index 9756d0aba7..e72e056da2 100644 --- a/bindings/python/examples/secret_manager/stronghold.py +++ b/bindings/python/examples/secret_manager/stronghold.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import SecretManager, StrongholdSecretManager load_dotenv() diff --git a/bindings/python/examples/wallet/12-prepare_output.py b/bindings/python/examples/wallet/12-prepare_output.py index 138028d8c1..9e0a62d068 100644 --- a/bindings/python/examples/wallet/12-prepare_output.py +++ b/bindings/python/examples/wallet/12-prepare_output.py @@ -2,7 +2,6 @@ import os from dotenv import load_dotenv - from iota_sdk import OutputParams, Unlocks, Wallet, WalletOptions load_dotenv() diff --git a/bindings/python/examples/wallet/13-check-unlock-conditions.py b/bindings/python/examples/wallet/13-check-unlock-conditions.py index bb8bbf7a70..66538afcc6 100644 --- a/bindings/python/examples/wallet/13-check-unlock-conditions.py +++ b/bindings/python/examples/wallet/13-check-unlock-conditions.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import OutputParams, Utils, Wallet, WalletOptions load_dotenv() @@ -16,13 +15,13 @@ output = wallet.prepare_output(OutputParams( address, 1000000)) -hexEncodedWalletAddress = Utils.bech32_to_hex(address) +wallet_address = Utils.parse_bech32_address(address) controlled_by_wallet = False if len( output.unlock_conditions) == 1 and output.unlock_conditions[0].type == 0: - if output.unlock_conditions[0].address.pub_key_hash == hexEncodedWalletAddress: + if output.unlock_conditions[0].address.pub_key_hash == wallet_address.pub_key_hash: controlled_by_wallet = True print( diff --git a/bindings/python/examples/wallet/backup.py b/bindings/python/examples/wallet/backup.py index 9c8c9f6ba2..d72d65beb3 100644 --- a/bindings/python/examples/wallet/backup.py +++ b/bindings/python/examples/wallet/backup.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet, WalletOptions, Bip44 load_dotenv() @@ -35,5 +34,7 @@ # done once. wallet.store_mnemonic(os.environ['MNEMONIC']) -wallet.backup("backup.stronghold", os.environ['STRONGHOLD_PASSWORD']) +wallet.backup_to_stronghold_snapshot( + "backup.stronghold", + os.environ['STRONGHOLD_PASSWORD']) print('Created backup') diff --git a/bindings/python/examples/wallet/create_alias.py b/bindings/python/examples/wallet/create_alias.py index e9d6c4ff34..48957ba2e4 100644 --- a/bindings/python/examples/wallet/create_alias.py +++ b/bindings/python/examples/wallet/create_alias.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions load_dotenv() diff --git a/bindings/python/examples/wallet/get_client.py b/bindings/python/examples/wallet/get_client.py index 7b95c00691..689309c353 100644 --- a/bindings/python/examples/wallet/get_client.py +++ b/bindings/python/examples/wallet/get_client.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import Wallet, WalletOptions load_dotenv() @@ -12,5 +11,5 @@ client = wallet.get_client() -info = client.get_info() -print(f'{info}') +node_info = client.get_node_info() +print(f'{node_info}') diff --git a/bindings/python/examples/wallet/getting_started.py b/bindings/python/examples/wallet/getting_started.py index bf4e077f69..4d49406ef4 100644 --- a/bindings/python/examples/wallet/getting_started.py +++ b/bindings/python/examples/wallet/getting_started.py @@ -4,7 +4,6 @@ import os from dotenv import load_dotenv - from iota_sdk import (ClientOptions, CoinType, StrongholdSecretManager, Utils, Wallet, WalletOptions, Bip44, SecretManager) diff --git a/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py b/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py index 0c2adfbfcb..69b5b37144 100644 --- a/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py +++ b/bindings/python/examples/wallet/offline_signing/0_generate_addresses.py @@ -8,7 +8,6 @@ import os from dotenv import load_dotenv - from iota_sdk import ClientOptions, CoinType, StrongholdSecretManager, Wallet, WalletOptions, Bip44 load_dotenv() diff --git a/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py b/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py index db423e2042..423065c158 100644 --- a/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/1_prepare_transaction.py @@ -8,7 +8,6 @@ from dacite import from_dict from dotenv import load_dotenv - from iota_sdk import (AccountAddress, ClientOptions, CoinType, SendParams, Wallet, WalletOptions, Bip44) diff --git a/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py b/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py index 1a30f5c14d..243846a816 100644 --- a/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/2_sign_transaction.py @@ -8,7 +8,6 @@ from dacite import from_dict from dotenv import load_dotenv - from iota_sdk import PreparedTransactionData, Wallet, WalletOptions load_dotenv() diff --git a/bindings/python/examples/wallet/offline_signing/3_send_transaction.py b/bindings/python/examples/wallet/offline_signing/3_send_transaction.py index dd8b1498ba..91941f1c02 100644 --- a/bindings/python/examples/wallet/offline_signing/3_send_transaction.py +++ b/bindings/python/examples/wallet/offline_signing/3_send_transaction.py @@ -8,7 +8,6 @@ from dacite import from_dict from dotenv import load_dotenv - from iota_sdk import SignedTransactionData, Wallet, WalletOptions load_dotenv() @@ -29,7 +28,8 @@ # Sends offline signed transaction online. transaction = wallet.submit_and_store_transaction(signed_transaction_data) print( - f'Transaction sent: {os.environ["EXPLORER_URL"]}/transaction/{transaction.transaction_id}') -block_id = wallet.wait_for_transaction_acceptance( + f'Transaction sent: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') +wallet.wait_for_transaction_acceptance( transaction.transaction_id) -print(f'Tx accepted in block: {os.environ["EXPLORER_URL"]}/block/{block_id}') +print( + f'Tx accepted: {os.environ["EXPLORER_URL"]}/transactions/{transaction.transaction_id}') diff --git a/bindings/python/examples/wallet/restore_backup.py b/bindings/python/examples/wallet/restore_backup.py index 4e18db71b7..6812d35b2f 100644 --- a/bindings/python/examples/wallet/restore_backup.py +++ b/bindings/python/examples/wallet/restore_backup.py @@ -2,7 +2,6 @@ import os from dotenv import load_dotenv - from iota_sdk import ClientOptions, CoinType, Wallet, WalletOptions, Bip44 load_dotenv() @@ -28,6 +27,8 @@ if 'STRONGHOLD_PASSWORD' not in os.environ: raise Exception(".env STRONGHOLD_PASSWORD is undefined, see .env.example") -wallet.restore_backup("backup.stronghold", os.environ['STRONGHOLD_PASSWORD']) +wallet.restore_from_stronghold_snapshot( + "backup.stronghold", + os.environ['STRONGHOLD_PASSWORD']) print(f'Restored wallet: {json.dumps(wallet, indent=4)}') diff --git a/bindings/python/examples/wallet/transaction_options.py b/bindings/python/examples/wallet/transaction_options.py index 8055406e3a..600059a417 100644 --- a/bindings/python/examples/wallet/transaction_options.py +++ b/bindings/python/examples/wallet/transaction_options.py @@ -1,7 +1,6 @@ import os from dotenv import load_dotenv - from iota_sdk import (RemainderValueStrategy, TaggedDataPayload, SendParams, TransactionOptions, Wallet, WalletOptions, utf8_to_hex) diff --git a/bindings/python/iota_sdk/__init__.py b/bindings/python/iota_sdk/__init__.py index b00f9bc274..cc4c2d5d87 100644 --- a/bindings/python/iota_sdk/__init__.py +++ b/bindings/python/iota_sdk/__init__.py @@ -20,7 +20,6 @@ from .types.block.body.basic import * from .types.block.body.type import * from .types.block.body.validation import * -from .types.block.metadata import * from .types.block.id import * from .types.block_builder_options import * from .types.block_issuer_key import * @@ -36,7 +35,6 @@ from .types.irc_30 import * from .types.filter_options import * from .types.native_token import * -from .types.network_info import * from .types.node_info import * from .types.output import * from .types.output_data import * diff --git a/bindings/python/iota_sdk/client/_high_level_api.py b/bindings/python/iota_sdk/client/_high_level_api.py index 37bb3dcf26..a121cef234 100644 --- a/bindings/python/iota_sdk/client/_high_level_api.py +++ b/bindings/python/iota_sdk/client/_high_level_api.py @@ -4,12 +4,11 @@ from typing import List, Optional from dataclasses import dataclass from abc import ABCMeta, abstractmethod - +from iota_sdk.client.responses import OutputResponse from iota_sdk.types.block.block import Block from iota_sdk.types.block.id import BlockId from iota_sdk.types.common import CoinType, json from iota_sdk.types.output_id import OutputId -from iota_sdk.types.output_metadata import OutputWithMetadata @json @@ -82,40 +81,36 @@ def _call_method(self, name, data=None): no payload. """ - # TODO: this should return `List[OutputResponse]`, not `List[OutputWithMetadata]` - # https://github.com/iotaledger/iota-sdk/issues/1921 def get_outputs( - self, output_ids: List[OutputId]) -> List[OutputWithMetadata]: - """Fetch OutputWithMetadata from provided OutputIds (requests are sent in parallel). + self, output_ids: List[OutputId]) -> List[OutputResponse]: + """Fetch OutputResponse from provided OutputIds (requests are sent in parallel). Args: output_ids: A list of output ids. Returns: - A list of corresponding `OutputWithMetadata` objects. + A list of corresponding `OutputResponse` objects. """ outputs = self._call_method('getOutputs', { 'outputIds': list(map(lambda o: o.output_id, output_ids)) }) - return [OutputWithMetadata.from_dict(o) for o in outputs] + return [OutputResponse.from_dict(o) for o in outputs] - # TODO: this should return `List[OutputResponse]`, not `List[OutputWithMetadata]` - # https://github.com/iotaledger/iota-sdk/issues/1921 - def get_outputs_ignore_errors( - self, output_ids: List[OutputId]) -> List[OutputWithMetadata]: - """Try to get OutputWithMetadata from provided OutputIds. + def get_outputs_ignore_not_found( + self, output_ids: List[OutputId]) -> List[OutputResponse]: + """Try to get OutputResponse from provided OutputIds. Requests are sent in parallel and errors are ignored, can be useful for spent outputs. Args: output_ids: A list of output ids. Returns: - A list of corresponding `OutputWithMetadata` objects. + A list of corresponding `OutputResponse` objects. """ - outputs = self._call_method('getOutputsIgnoreErrors', { + outputs = self._call_method('getOutputsIgnoreNotFound', { 'outputIds': list(map(lambda o: o.output_id, output_ids)) }) - return [OutputWithMetadata.from_dict(o) for o in outputs] + return [OutputResponse.from_dict(o) for o in outputs] def find_blocks(self, block_ids: List[BlockId]) -> List[Block]: """Find all blocks by provided block IDs. diff --git a/bindings/python/iota_sdk/client/_node_core_api.py b/bindings/python/iota_sdk/client/_node_core_api.py index b09892ab6d..f62b32525d 100644 --- a/bindings/python/iota_sdk/client/_node_core_api.py +++ b/bindings/python/iota_sdk/client/_node_core_api.py @@ -3,8 +3,7 @@ from typing import List, Optional, Union from abc import ABCMeta, abstractmethod - -from iota_sdk.client.responses import NodeInfoWrapper, InfoResponse, RoutesResponse, CongestionResponse, ManaRewardsResponse, CommitteeResponse, ValidatorResponse, ValidatorsResponse, IssuanceBlockHeaderResponse, BlockMetadataResponse, BlockWithMetadataResponse, OutputWithMetadataResponse, TransactionMetadataResponse, UtxoChangesResponse, UtxoChangesFullResponse +from iota_sdk.client.responses import InfoResponse, NodeInfoResponse, NetworkMetricsResponse, RoutesResponse, CongestionResponse, ManaRewardsResponse, CommitteeResponse, ValidatorResponse, ValidatorsResponse, IssuanceBlockHeaderResponse, BlockMetadataResponse, BlockWithMetadataResponse, OutputResponse, OutputWithMetadataResponse, TransactionMetadataResponse, UtxoChangesResponse, UtxoChangesFullResponse from iota_sdk.types.block.block import Block from iota_sdk.types.block.id import BlockId from iota_sdk.types.common import HexStr, EpochIndex, SlotIndex @@ -52,17 +51,22 @@ def get_health(self, url: str) -> bool: 'url': url }) - # TODO: this is not strictly following the 2.0 Core API Spec (or maybe the TIP isn't updated yet) - # https://github.com/iotaledger/iota-sdk/issues/1921 - def get_info(self) -> NodeInfoWrapper: - """Returns general information about the node together with its URL. + def get_routes(self) -> RoutesResponse: + """Returns the available API route groups of the node. + GET /api/routes + """ + return RoutesResponse.from_dict(self._call_method('getRoutes')) + + def get_node_info(self) -> NodeInfoResponse: + """Returns general information about a node together with its URL. GET /api/core/v3/info + + Returns: + The node info with its URL. """ - return NodeInfoWrapper.from_dict(self._call_method('getInfo')) + return NodeInfoResponse.from_dict(self._call_method('getNodeInfo')) - # TODO: this is not strictly following the 2.0 Core API Spec (or maybe the TIP isn't updated yet) - # https://github.com/iotaledger/iota-sdk/issues/1921 - def get_node_info(self, url: str, auth=None) -> InfoResponse: + def get_info(self, url: str, auth=None) -> InfoResponse: """Returns general information about the node. GET /api/core/v3/info @@ -73,53 +77,37 @@ def get_node_info(self, url: str, auth=None) -> InfoResponse: Returns: The node info. """ - return InfoResponse.from_dict(self._call_method('getNodeInfo', { + return InfoResponse.from_dict(self._call_method('getInfo', { 'url': url, 'auth': auth })) - # TODO: this should made be available - # https://github.com/iotaledger/iota-sdk/issues/1921 - def get_routes(self) -> RoutesResponse: - """Returns the available API route groups of the node. - GET /api/routes - """ - - def call_plugin_route(self, base_plugin_path: str, method: str, - endpoint: str, query_params: Optional[List[str]] = None, request: Optional[str] = None): - """Extension method which provides request methods for plugins. + def get_network_metrics(self) -> NetworkMetricsResponse: + """Returns network metrics. + GET /api/core/v3/network/metrics - Args: - base_plugin_path: The base path of the routes provided by the plugin. - method: The HTTP method. - endpoint: The endpoint to query provided by the plugin. - query_params: The parameters of the query. - request: The request object sent to the endpoint of the plugin. + Returns: + Network metrics. """ - if query_params is None: - query_params = [] - return self._call_method('callPluginRoute', { - 'basePluginPath': base_plugin_path, - 'method': method, - 'endpoint': endpoint, - 'queryParams': query_params, - 'request': request, - }) + return NetworkMetricsResponse.from_dict( + self._call_method('getNetworkMetrics')) # Accounts routes. - def get_account_congestion(self, account_id: HexStr) -> CongestionResponse: + def get_account_congestion( + self, account_id: HexStr, work_score: Optional[int] = None) -> CongestionResponse: """Checks if the account is ready to issue a block. GET /api/core/v3/accounts/{bech32Address}/congestion """ return CongestionResponse.from_dict(self._call_method('getAccountCongestion', { - 'accountId': account_id + 'accountId': account_id, + 'workScore': work_score })) # Rewards routes. def get_output_mana_rewards( - self, output_id: OutputId, slot_index: SlotIndex) -> ManaRewardsResponse: + self, output_id: OutputId, slot_index: Optional[SlotIndex] = None) -> ManaRewardsResponse: """Returns the total available Mana rewards of an account or delegation output decayed up to `epochEnd` index provided in the response. Note that rewards for an epoch only become available at the beginning of the next epoch. If the end epoch of a @@ -133,22 +121,12 @@ def get_output_mana_rewards( 'slotIndex': slot_index })) - # Committee routes. - - def get_committee(self, epoch_index: EpochIndex) -> CommitteeResponse: - """Returns the information of committee members at the given epoch index. If epoch index is not provided, the - current committee members are returned. - GET /api/core/v3/committee/?epochIndex - """ - return CommitteeResponse.from_dict(self._call_method('getCommittee', { - 'epochIndex': epoch_index - })) - # Validators routes. - def get_validators(self, page_size, cursor) -> ValidatorsResponse: - """Returns information of all registered validators and if they are active. - GET JSON to /api/core/v3/validators + def get_validators( + self, page_size: Optional[int] = None, cursor: Optional[str] = None) -> ValidatorsResponse: + """Returns information of all stakers (registered validators) and if they are active, ordered by their holding stake. + GET /api/core/v3/validators """ return ValidatorsResponse.from_dict(self._call_method('getValidators', { 'pageSize': page_size, @@ -156,13 +134,25 @@ def get_validators(self, page_size, cursor) -> ValidatorsResponse: })) def get_validator(self, account_id: HexStr) -> ValidatorResponse: - """Return information about a validator. + """Return information about a staker (registered validator). GET /api/core/v3/validators/{bech32Address} """ return ValidatorResponse.from_dict(self._call_method('getValidator', { 'accountId': account_id })) + # Committee routes. + + def get_committee( + self, epoch_index: Optional[EpochIndex] = None) -> CommitteeResponse: + """Returns the information of committee members at the given epoch index. If epoch index is not provided, the + current committee members are returned. + GET /api/core/v3/committee/?epochIndex + """ + return CommitteeResponse.from_dict(self._call_method('getCommittee', { + 'epochIndex': epoch_index + })) + # Block routes. def get_issuance(self) -> IssuanceBlockHeaderResponse: @@ -174,7 +164,7 @@ def get_issuance(self) -> IssuanceBlockHeaderResponse: def post_block(self, block: Block) -> BlockId: """Returns the BlockId of the submitted block. - POST JSON to /api/core/v3/blocks + POST /api/core/v3/blocks Args: block: The block to post. @@ -186,7 +176,7 @@ def post_block(self, block: Block) -> BlockId: 'block': block }) - def post_block_raw(self, block: Block) -> BlockId: + def post_block_raw(self, block_bytes: bytes) -> BlockId: """Returns the BlockId of the submitted block. POST /api/core/v3/blocks @@ -194,7 +184,7 @@ def post_block_raw(self, block: Block) -> BlockId: The corresponding block id of the block. """ return self._call_method('postBlockRaw', { - 'block': block + 'blockBytes': block_bytes }) def get_block(self, block_id: BlockId) -> Block: @@ -244,10 +234,8 @@ def get_block_with_metadata( # UTXO routes. - # TODO: this should return `OutputResponse`, not OutputWithMetadataResponse - # https://github.com/iotaledger/iota-sdk/issues/1921 def get_output( - self, output_id: Union[OutputId, HexStr]) -> OutputWithMetadataResponse: + self, output_id: Union[OutputId, HexStr]) -> OutputResponse: """Finds an output by its ID and returns it as object. GET /api/core/v3/outputs/{outputId} @@ -256,12 +244,10 @@ def get_output( """ output_id_str = output_id.output_id if isinstance( output_id, OutputId) else output_id - return OutputWithMetadataResponse.from_dict(self._call_method('getOutput', { + return OutputResponse.from_dict(self._call_method('getOutput', { 'outputId': output_id_str })) - # TODO: this should be made available - # https://github.com/iotaledger/iota-sdk/issues/1921 def get_output_raw( self, output_id: Union[OutputId, HexStr]) -> List[int]: """Finds an output by its ID and returns it as raw bytes. @@ -296,7 +282,7 @@ def get_output_with_metadata( GET /api/core/v3/outputs/{outputId}/full Returns: - The corresponding output. + The corresponding output with its metadata. """ output_id_str = output_id.output_id if isinstance( output_id, OutputId) else output_id @@ -315,8 +301,6 @@ def get_included_block(self, transaction_id: TransactionId) -> Block: 'transactionId': transaction_id })) - # TODO: this should be made available - # https://github.com/iotaledger/iota-sdk/issues/1921 def get_included_block_raw( self, transaction_id: TransactionId) -> List[int]: """Returns the earliest confirmed block containing the transaction with the given ID, as raw bytes. @@ -367,8 +351,6 @@ def get_commitment( 'commitmentId': commitment_id })) - # TODO: this should be made available - # https://github.com/iotaledger/iota-sdk/issues/1921 def get_commitment_raw( self, commitment_id: SlotCommitmentId) -> List[int]: """Finds a slot commitment by its ID and returns it as raw bytes. @@ -405,9 +387,7 @@ def get_utxo_changes_full( 'commitmentId': commitment_id })) - # TODO: call method name needs to be changed to `getCommitmentBySlot` - # https://github.com/iotaledger/iota-sdk/issues/1921 - def get_slot_commitment_by_slot( + def get_commitment_by_slot( self, slot: SlotIndex) -> SlotCommitment: """Finds a slot commitment by slot index and returns it as object. GET /api/core/v3/commitments/by-slot/{slot} @@ -415,13 +395,11 @@ def get_slot_commitment_by_slot( Returns: The corresponding slot commitment. """ - return SlotCommitment.from_dict(self._call_method('getCommitmentByIndex', { + return SlotCommitment.from_dict(self._call_method('getCommitmentBySlot', { 'slot': slot })) - # TODO: this should be made available - # https://github.com/iotaledger/iota-sdk/issues/1921 - def get_slot_commitment_by_slot_raw( + def get_commitment_by_slot_raw( self, slot: SlotIndex) -> List[int]: """Finds a slot commitment by slot index and returns it as raw bytes. GET /api/core/v3/commitments/by-slot/{slot} @@ -433,8 +411,6 @@ def get_slot_commitment_by_slot_raw( 'slot': slot }) - # TODO: call method name needs to be changed to `getUxoChangesBySlot` - # https://github.com/iotaledger/iota-sdk/issues/1921 def get_utxo_changes_by_slot(self, slot: SlotIndex) -> UtxoChangesResponse: """Get all UTXO changes of a given slot by its index. GET /api/core/v3/commitments/by-slot/{slot}/utxo-changes @@ -442,12 +418,10 @@ def get_utxo_changes_by_slot(self, slot: SlotIndex) -> UtxoChangesResponse: Returns: The corresponding UTXO changes. """ - return UtxoChangesResponse.from_dict(self._call_method('getUtxoChangesByIndex', { + return UtxoChangesResponse.from_dict(self._call_method('getUtxoChangesBySlot', { 'slot': slot })) - # TODO: call method name needs to be changed to `getUxoChangesFullBySlot` - # https://github.com/iotaledger/iota-sdk/issues/1921 def get_utxo_changes_full_by_slot( self, slot: SlotIndex) -> UtxoChangesFullResponse: """Get all full UTXO changes of a given slot by its index. @@ -456,6 +430,29 @@ def get_utxo_changes_full_by_slot( Returns: The full UTXO changes. """ - return UtxoChangesFullResponse.from_dict(self._call_method('getUtxoChangesFullByIndex', { + return UtxoChangesFullResponse.from_dict(self._call_method('getUtxoChangesFullBySlot', { 'slot': slot })) + + # Plugin routes. + + def call_plugin_route(self, base_plugin_path: str, method: str, + endpoint: str, query_params: Optional[List[str]] = None, request: Optional[str] = None): + """Extension method which provides request methods for plugins. + + Args: + base_plugin_path: The base path of the routes provided by the plugin. + method: The HTTP method. + endpoint: The endpoint to query provided by the plugin. + query_params: The parameters of the query. + request: The request object sent to the endpoint of the plugin. + """ + if query_params is None: + query_params = [] + return self._call_method('callPluginRoute', { + 'basePluginPath': base_plugin_path, + 'method': method, + 'endpoint': endpoint, + 'queryParams': query_params, + 'request': request, + }) diff --git a/bindings/python/iota_sdk/client/_node_indexer_api.py b/bindings/python/iota_sdk/client/_node_indexer_api.py index ef93bfb1c8..e2b6f23a32 100644 --- a/bindings/python/iota_sdk/client/_node_indexer_api.py +++ b/bindings/python/iota_sdk/client/_node_indexer_api.py @@ -4,7 +4,6 @@ from dataclasses import dataclass from typing import Optional from abc import ABCMeta, abstractmethod - from iota_sdk.client.responses import OutputIdsResponse from iota_sdk.types.common import HexStr, json, SlotIndex from iota_sdk.types.output_id import OutputId @@ -279,7 +278,7 @@ def account_output_id(self, account_id: HexStr) -> OutputId: Returns: The output ID of the account output. """ - return OutputId.from_string(self._call_method('accountOutputId', { + return OutputId(self._call_method('accountOutputId', { 'accountId': account_id })) @@ -302,7 +301,7 @@ def anchor_output_id(self, anchor_id: HexStr) -> OutputId: Returns: The output ID of the anchor output. """ - return OutputId.from_string(self._call_method('anchorOutputId', { + return OutputId(self._call_method('anchorOutputId', { 'anchorId': anchor_id })) @@ -325,7 +324,7 @@ def delegation_output_id(self, delegation_id: HexStr) -> OutputId: Returns: The output ID of the delegation output. """ - return OutputId.from_string(self._call_method('delegationOutputId', { + return OutputId(self._call_method('delegationOutputId', { 'delegationId': delegation_id })) @@ -348,7 +347,7 @@ def foundry_output_id(self, foundry_id: HexStr) -> OutputId: Returns: The output ID of the foundry output. """ - return OutputId.from_string(self._call_method('foundryOutputId', { + return OutputId(self._call_method('foundryOutputId', { 'foundryId': foundry_id })) @@ -371,6 +370,6 @@ def nft_output_id(self, nft_id: HexStr) -> OutputId: Returns: The output ID of the NFT output. """ - return OutputId.from_string(self._call_method('nftOutputId', { + return OutputId(self._call_method('nftOutputId', { 'nftId': nft_id })) diff --git a/bindings/python/iota_sdk/client/_utils.py b/bindings/python/iota_sdk/client/_utils.py index b9a956d2a1..6a68fcc9c3 100644 --- a/bindings/python/iota_sdk/client/_utils.py +++ b/bindings/python/iota_sdk/client/_utils.py @@ -3,10 +3,9 @@ from typing import Optional from abc import ABCMeta, abstractmethod - +from iota_sdk.types.address import Address from iota_sdk.types.block.block import Block from iota_sdk.types.block.id import BlockId -from iota_sdk.types.common import HexStr from iota_sdk.types.output import Output @@ -35,38 +34,12 @@ def _call_method(self, name, data=None): no payload. """ - # pylint: disable=redefined-builtin - def hex_to_bech32(self, hex_str: HexStr, bech32_hrp: str) -> str: - """Transforms a hex encoded address to a bech32 encoded address. - """ - return self._call_method('hexToBech32', { - 'hex': hex_str, - 'bech32Hrp': bech32_hrp - }) - - def account_id_to_bech32(self, account_id: HexStr, bech32_hrp: str) -> str: - """Transforms an account id to a bech32 encoded address. - """ - return self._call_method('accountIdToBech32', { - 'accountId': account_id, - 'bech32Hrp': bech32_hrp - }) - - def nft_id_to_bech32(self, nft_id: HexStr, bech32_hrp: str) -> str: - """Transforms an nft id to a bech32 encoded address. - """ - return self._call_method('nftIdToBech32', { - 'nftId': nft_id, - 'bech32Hrp': bech32_hrp - }) - - # pylint: disable=redefined-builtin - def hex_public_key_to_bech32_address( - self, hex_str: HexStr, bech32_hrp: Optional[str] = None) -> str: - """Transforms a hex encoded public key to a bech32 encoded address. + def address_to_bech32(self, address: Address, + bech32_hrp: Optional[str] = None) -> str: + """Converts an address to its bech32 representation. """ - return self._call_method('hexPublicKeyToBech32Address', { - 'hex': hex_str, + return self._call_method('addressToBech32', { + 'address': address, 'bech32Hrp': bech32_hrp }) diff --git a/bindings/python/iota_sdk/client/client.py b/bindings/python/iota_sdk/client/client.py index db10174a36..6f0f6ef10a 100644 --- a/bindings/python/iota_sdk/client/client.py +++ b/bindings/python/iota_sdk/client/client.py @@ -5,7 +5,6 @@ from datetime import timedelta from typing import Any, Dict, List, Optional, Union import humps - from iota_sdk.external import create_client, listen_mqtt from iota_sdk.client._node_core_api import NodeCoreAPI from iota_sdk.client._node_indexer_api import NodeIndexerAPI @@ -13,9 +12,10 @@ from iota_sdk.client._utils import ClientUtils from iota_sdk.client.common import _call_client_method_routine from iota_sdk.types.block.block import UnsignedBlock +from iota_sdk.types.client_options import MqttBrokerOptions from iota_sdk.types.common import HexStr, Node from iota_sdk.types.feature import Feature -from iota_sdk.types.network_info import NetworkInfo +from iota_sdk.types.node_info import ProtocolParameters from iota_sdk.types.output import AccountOutput, BasicOutput, FoundryOutput, NftOutput, deserialize_output from iota_sdk.types.payload import Payload from iota_sdk.types.token_scheme import SimpleTokenScheme @@ -36,6 +36,7 @@ def __init__( List[Union[str, Node]]]] = None, nodes: Optional[Union[Union[str, Node], List[Union[str, Node]]]] = None, + protocol_parameters: Optional[ProtocolParameters] = None, ignore_node_health: Optional[bool] = None, api_timeout: Optional[timedelta] = None, node_sync_interval: Optional[timedelta] = None, @@ -43,6 +44,7 @@ def __init__( min_quorum_size: Optional[int] = None, quorum_threshold: Optional[int] = None, user_agent: Optional[str] = None, + broker_options: Optional[MqttBrokerOptions] = None, max_parallel_api_requests: Optional[int] = None, client_handle=None ): @@ -58,7 +60,7 @@ def __init__( api_timeout : Timeout for API requests. node_sync_interval : - Interval in which nodes will be checked for their sync status and the [NetworkInfo](crate::NetworkInfo) gets updated. + Interval in which nodes will be checked for their sync status and the network info gets updated. quorum : If node quorum is enabled. Will compare the responses from multiple nodes and only returns the response if 'quorum_threshold'% of the nodes return the same one. min_quorum_size : @@ -67,6 +69,8 @@ def __init__( % of nodes that have to return the same response so it gets accepted. user_agent : The User-Agent header for requests. + broker_options (MqttBrokerOptions): + Options for the MQTT broker. max_parallel_api_requests : Set maximum parallel API requests. client_handle : @@ -81,6 +85,10 @@ def __init__( client_config['primary_nodes'] = convert_nodes(primary_nodes) client_config['nodes'] = convert_nodes(nodes) + if broker_options is not None: + client_config['broker_options'] = broker_options.to_dict() + if protocol_parameters is not None: + client_config['protocol_parameters'] = protocol_parameters.to_dict() client_config = { k: v for k, @@ -247,10 +255,11 @@ def get_node(self) -> Dict[str, Any]: """ return self._call_method('getNode') - def get_network_info(self) -> NetworkInfo: - """Gets the network related information such as network_id. + def get_protocol_parameters(self) -> ProtocolParameters: + """Gets the protocol parameters. """ - return NetworkInfo.from_dict(self._call_method('getNetworkInfo')) + return ProtocolParameters.from_dict( + self._call_method('getProtocolParameters')) def get_network_id(self) -> int: """Gets the network id of the node we're connecting to. diff --git a/bindings/python/iota_sdk/client/responses.py b/bindings/python/iota_sdk/client/responses.py index 8f15a75010..27f5d3e173 100644 --- a/bindings/python/iota_sdk/client/responses.py +++ b/bindings/python/iota_sdk/client/responses.py @@ -1,15 +1,15 @@ # Copyright 2024 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations from typing import Dict, List, Optional +from enum import Enum, IntEnum from dataclasses import dataclass, field from dataclasses_json import config - from iota_sdk.types.block.block import Block from iota_sdk.types.block.id import BlockId -from iota_sdk.types.block.metadata import BlockFailureReason, BlockState from iota_sdk.types.common import HexStr, json, EpochIndex, SlotIndex -from iota_sdk.types.node_info import NodeInfoBaseToken, NodeInfoMetrics, NodeInfoStatus, ProtocolParameters +from iota_sdk.types.node_info import BaseTokenResponse, StatusResponse, ProtocolParameters from iota_sdk.types.output import Output, deserialize_output from iota_sdk.types.output_id import OutputId, OutputWithId from iota_sdk.types.output_id_proof import OutputIdProof @@ -19,71 +19,138 @@ from iota_sdk.types.transaction_metadata import TransactionFailureReason, TransactionState +# Node routes responses + @json @dataclass -class OutputIdsResponse: - """Response type for output IDs. +class RoutesResponse: + """API route groups of the node. + GET /api/routes. Attributes: - committed_slot: The committed slot at which these outputs were available at. - page_size: The maximum amount of items returned in one call. If there are more items, a cursor to the next page is returned too. - cursor: The cursor to the next page of results. - items: The query results. + routes: The available API route groups of the node. """ + routes: List[str] - def __init__(self, output_dict: Dict): - self.committed_slot = output_dict["committedSlot"] - self.page_size = output_dict["pageSize"] - self.cursor = output_dict["cursor"] - self.items = [OutputId.from_string( - output_id) for output_id in output_dict["items"]] + +@json +@dataclass +class ProtocolParametersResponse: + """Protocol Parameters with start epoch. + + Attributes: + parameters: The protocol parameters. + start_epoch: The start epoch of the set of protocol parameters. + """ + parameters: ProtocolParameters + start_epoch: EpochIndex @json @dataclass -class CommitteeMember: - """Information of a committee member. +class InfoResponse: + """General information about the node. + GET /api/core/v3/info. Attributes: - address: Account address of the validator. - pool_stake: The total stake of the pool, including delegators. - validator_stake: The stake of a validator. - fixed_cost: The fixed cost of the validator, which it receives as part of its Mana rewards. + name: The name of the node (e.g. Hornet). + version: The semantic version of the node. + status: The status of the node. + protocol_parameters: Supported protocol versions by the node. + base_token: Gives info about the base token the network uses. """ - address: str - pool_stake: int = field(metadata=config( + name: str + version: str + status: StatusResponse + protocol_parameters: List[ProtocolParametersResponse] + base_token: BaseTokenResponse + + +@json +@dataclass +class NodeInfoResponse: + """General information about the node and its URL. + GET /api/core/v3/info. + + Attributes: + info: A NodeInfo object. + url: The URL of the node. + """ + info: InfoResponse + url: str + + +@json +@dataclass +class NetworkMetricsResponse: + """Network metrics. + + Attributes: + blocks_per_second: The current rate of new blocks per second. + confirmed_blocks_per_second: The current rate of confirmed blocks per second. + confirmation_rate: The ratio of confirmed blocks to new blocks of the last confirmed slot. + """ + blocks_per_second: float = field(metadata=config( encoder=str )) - validator_stake: int = field(metadata=config( + confirmed_blocks_per_second: float = field(metadata=config( encoder=str )) - fixed_cost: int = field(metadata=config( + confirmation_rate: float = field(metadata=config( encoder=str )) +# Accounts routes responses + @json @dataclass -class CommitteeResponse: - """The validator information of the committee. - Response of GET /api/core/v3/committee +class CongestionResponse: + """Provides the cost and readiness to issue estimates. + Response of GET /api/core/v3/accounts/{accountId}/congestion. Attributes: - committee: The validators of the committee. - total_stake: The total amount of delegated and staked IOTA coins in the selected committee. - total_validator_stake: The total amount of staked IOTA coins in the selected committee. - epoch: The epoch index of the committee. + slot: Slot for which the estimate is provided. + ready: Indicates if a node is ready to schedule a block issued by the specified account, or if the issuer should wait. + reference_mana_cost: Mana cost a user needs to burn to issue a block in the slot. + block_issuance_credits: BIC of the account in the slot. This balance needs to be non-negative, otherwise account is locked. """ - committee: List[CommitteeMember] - total_stake: int = field(metadata=config( + slot: SlotIndex + ready: bool + reference_mana_cost: int = field(metadata=config( encoder=str )) - total_validator_stake: int = field(metadata=config( + block_issuance_credits: int = field(metadata=config( encoder=str )) - epoch: EpochIndex +# Rewards routes responses + +@json +@dataclass +class ManaRewardsResponse: + """The mana rewards of an account or delegation output. + Response of GET /api/core/v3/rewards/{outputId}. + + Attributes: + start_epoch: First epoch for which rewards can be claimed. This value is useful for checking if rewards have expired (by comparing against the staking or delegation start) or would expire soon (by checking its relation to the rewards retention period). + end_epoch: Last epoch for which rewards can be claimed. + rewards: Amount of totally available decayed rewards the requested output may claim. + latest_committed_epoch_pool_rewards: Rewards of the latest committed epoch of the staking pool to which this validator or delegator belongs. The ratio of this value and the maximally possible rewards for the latest committed epoch can be used to determine how well the validator of this staking pool performed in that epoch. Note that if the pool was not part of the committee in the latest committed epoch, this value is 0. + """ + start_epoch: EpochIndex + end_epoch: EpochIndex + rewards: int = field(metadata=config( + encoder=str + )) + latest_committed_epoch_pool_rewards: int = field(metadata=config( + encoder=str + )) + + +# Validators routes responses + @json @dataclass class ValidatorResponse: @@ -124,15 +191,64 @@ class ValidatorsResponse: Response of GET /api/core/v3/validators Attributes: - stakers: List of registered validators ready for the next epoch. + validators: List of registered validators ready for the next epoch. page_size: The number of validators returned per one API request with pagination. cursor: The cursor that needs to be provided as cursor query parameter to request the next page. If empty, this was the last page. """ - stakers: List[ValidatorResponse] + validators: List[ValidatorResponse] page_size: int cursor: Optional[str] = None +# Committee routes responses + +@json +@dataclass +class CommitteeMember: + """Information of a committee member. + + Attributes: + address: Account address of the validator. + pool_stake: The total stake of the pool, including delegators. + validator_stake: The stake of a validator. + fixed_cost: The fixed cost of the validator, which it receives as part of its Mana rewards. + """ + address: str + pool_stake: int = field(metadata=config( + encoder=str + )) + validator_stake: int = field(metadata=config( + encoder=str + )) + fixed_cost: int = field(metadata=config( + encoder=str + )) + + +@json +@dataclass +class CommitteeResponse: + """The validator information of the committee. + Response of GET /api/core/v3/committee + + Attributes: + epoch: The epoch index of the committee. + total_stake: The total amount of delegated and staked IOTA coins in the selected committee. + total_validator_stake: The total amount of staked IOTA coins in the selected committee. + committee: The validators of the committee. + """ + epoch: EpochIndex + total_stake: int = field(metadata=config( + encoder=str + )) + total_validator_stake: int = field(metadata=config( + encoder=str + )) + committee: List[CommitteeMember] + + +# Blocks routes responses + @json @dataclass class IssuanceBlockHeaderResponse: @@ -157,122 +273,135 @@ class IssuanceBlockHeaderResponse: shallow_like_parents: Optional[List[BlockId]] = None -@json -@dataclass -class CongestionResponse: - """Provides the cost and readiness to issue estimates. - Response of GET /api/core/v3/accounts/{accountId}/congestion. +class BlockState(str, Enum): + """Describes the state of a block. Attributes: - slot: Slot for which the estimate is provided. - ready: Indicates if a node is ready to schedule a block issued by the specified account, or if the issuer should wait. - reference_mana_cost: Mana cost a user needs to burn to issue a block in the slot. - block_issuance_credits: BIC of the account in the slot. This balance needs to be non-negative, otherwise account is locked. + Pending: Stored but not accepted/confirmed. + Accepted: Valid block referenced by some validators. + Confirmed: Valid block referenced by more than 2/3 of the validators. + Finalized: Accepted/confirmed block and the slot was finalized, can no longer be reverted. + Rejected: Rejected by the node, and user should reissue payload if it contains one. + Failed: Not successfully issued due to failure reason. """ - slot: SlotIndex - ready: bool - reference_mana_cost: int = field(metadata=config( - encoder=str - )) - block_issuance_credits: int = field(metadata=config( - encoder=str - )) + Pending = 'pending' + Accepted = 'accepted' + Confirmed = 'confirmed' + Finalized = 'finalized' + Rejected = 'rejected' + Failed = 'failed' -@json -@dataclass -class ManaRewardsResponse: - """The mana rewards of an account or delegation output. - Response of GET /api/core/v3/rewards/{outputId}. +class BlockFailureReason(IntEnum): + """Describes the reason of a block failure. Attributes: - start_epoch: First epoch for which rewards can be claimed. This value is useful for checking if rewards have expired (by comparing against the staking or delegation start) or would expire soon (by checking its relation to the rewards retention period). - end_epoch: Last epoch for which rewards can be claimed. - rewards: Amount of totally available decayed rewards the requested output may claim. - latest_committed_epoch_pool_rewards: Rewards of the latest committed epoch of the staking pool to which this validator or delegator belongs. The ratio of this value and the maximally possible rewards for the latest committed epoch can be used to determine how well the validator of this staking pool performed in that epoch. Note that if the pool was not part of the committee in the latest committed epoch, this value is 0. + TooOldToIssue (1): The block is too old to issue. + ParentTooOld (2): One of the block's parents is too old. + ParentDoesNotExist (3): One of the block's parents does not exist. + IssuerAccountNotFound (4): The block's issuer account could not be found. + ManaCostCalculationFailed (5): The mana cost could not be calculated. + BurnedInsufficientMana (6): The block's issuer account burned insufficient Mana for a block. + AccountLocked (7): The account is locked. + AccountExpired (8): The account is expired. + SignatureInvalid (9): The block's signature is invalid. + DroppedDueToCongestion (10): The block is dropped due to congestion. + PayloadInvalid (11): The block payload is invalid. + Invalid (255): The block is invalid. """ - start_epoch: EpochIndex - end_epoch: EpochIndex - rewards: int = field(metadata=config( - encoder=str - )) - latest_committed_epoch_pool_rewards: int = field(metadata=config( - encoder=str - )) + TooOldToIssue = 1 + ParentTooOld = 2 + ParentDoesNotExist = 3 + IssuerAccountNotFound = 4 + ManaCostCalculationFailed = 5 + BurnedInsufficientMana = 6 + AccountLocked = 7 + AccountExpired = 8 + SignatureInvalid = 9 + DroppedDueToCongestion = 10 + PayloadInvalid = 11 + Invalid = 255 + + def __str__(self): + return { + 1: "The block is too old to issue.", + 2: "One of the block's parents is too old.", + 3: "One of the block's parents does not exist.", + 4: "The block's issuer account could not be found.", + 5: "The mana cost could not be calculated.", + 6: "The block's issuer account burned insufficient Mana for a block.", + 7: "The account is locked.", + 8: "The account is expired.", + 9: "The block's signature is invalid.", + 10: "The block is dropped due to congestion.", + 11: "The block payload is invalid.", + 255: "The block is invalid." + }[self.value] @json @dataclass -class ProtocolParametersResponse: - """Protocol Parameters with start epoch. +class BlockMetadataResponse: + """The metadata of a block. + Response of GET /api/core/v3/blocks/{blockId}/metadata. Attributes: - start_epoch: The start epoch of the set of protocol parameters. - parameters: The protocol parameters. + block_id: The identifier of the block. Hex-encoded with 0x prefix. + block_state: If pending, the block is stored but not confirmed. If confirmed, the block is confirmed with the first level of knowledge. If finalized, the block is included and cannot be reverted anymore. If rejected, the block is rejected by the node, and user should reissue payload if it contains one. If failed, the block is not successfully issued due to failure reason. + block_failure_reason: The optional block failure reason. + transaction_metadata: The optional metadata of a given transaction. """ - start_epoch: EpochIndex - parameters: ProtocolParameters + block_id: BlockId + block_state: BlockState + block_failure_reason: Optional[BlockFailureReason] = None + transaction_metadata: Optional[TransactionMetadataResponse] = None -# TODO: rename sdk-wide to `NodeInfoResponse`? @json @dataclass -class InfoResponse: - """General information about the node. - GET /api/core/v3/info. +class BlockWithMetadataResponse: + """A block with its metadata. + Response of GET /api/core/v3/blocks/{blockId}/full. Attributes: - name: The name of the node (e.g. Hornet). - version: The semantic version of the node. - status: The status of the node. - metrics: Node metrics. - protocol_parameters: Supported protocol versions by the node. - base_token: Gives info about the base token the network uses. + block: The block. + metadata: The block metadata. """ - name: str - version: str - status: NodeInfoStatus - metrics: NodeInfoMetrics - protocol_parameters: List[ProtocolParametersResponse] - base_token: NodeInfoBaseToken + block: Block + metadata: BlockMetadataResponse -# TODO: rename sdk-wide to `NodeInfoResponseWithUrl`? -@json -@dataclass -class NodeInfoWrapper: - """General information about the node and its URL. - GET /api/core/v3/info. - - Attributes: - node_info: A NodeInfo object. - url: The URL of the node. - """ - node_info: InfoResponse - url: str - +# UTXO routes responses @json @dataclass -class RoutesResponse: - """API route groups of the node. - GET /api/routes. +class OutputIdsResponse: + """Response type for output IDs. Attributes: - routes: The available API route groups of the node. + committed_slot: The committed slot at which these outputs were available at. + page_size: The maximum amount of items returned in one call. If there are more items, a cursor to the next page is returned too. + cursor: The cursor to the next page of results. + items: The query results. """ - routes: List[str] + + def __init__(self, output_dict: Dict): + self.committed_slot = output_dict["committedSlot"] + self.page_size = output_dict["pageSize"] + self.cursor = output_dict["cursor"] + self.items = [OutputId( + output_id) for output_id in output_dict["items"]] @json @dataclass class OutputResponse: """An output with its output id proof. - Response of GET /api/core/v3/outputs/{output_id}. + Response of GET /api/core/v3/outputs/{outputId}. Attributes: - output: One of the possible outputs. - output_id_proof: The associated Output ID proof. + output: One of the possible output types. + output_id_proof: The proof of the output identifier. """ output: Output = field(metadata=config( decoder=deserialize_output @@ -284,12 +413,12 @@ class OutputResponse: @dataclass class OutputWithMetadataResponse: """An output with its output id proof and its metadata. - Response of GET /api/core/v3/outputs/{output_id}/full. + Response of GET /api/core/v3/outputs/{outputId}/full. Attributes: - output: One of the possible outputs. + output: One of the possible output types. output_id_proof: The associated Output ID proof. - metadata: The metadata of an output. + metadata: The metadata of the output. """ output: Output = field(metadata=config( decoder=deserialize_output @@ -313,6 +442,8 @@ class TransactionMetadataResponse: transaction_failure_reason: Optional[TransactionFailureReason] = None +# Commitment routes responses + @json @dataclass class UtxoChangesResponse: @@ -347,35 +478,3 @@ class UtxoChangesFullResponse: commitment_id: SlotCommitmentId created_outputs: List[OutputWithId] consumed_outputs: List[OutputWithId] - - -@json -@dataclass -class BlockMetadataResponse: - """The metadata of a block. - Response of GET /api/core/v3/blocks/{blockId}/metadata. - - Attributes: - block_id: The identifier of the block. Hex-encoded with 0x prefix. - block_state: If pending, the block is stored but not confirmed. If confirmed, the block is confirmed with the first level of knowledge. If finalized, the block is included and cannot be reverted anymore. If rejected, the block is rejected by the node, and user should reissue payload if it contains one. If failed, the block is not successfully issued due to failure reason. - block_failure_reason: The optional block failure reason. - transaction_metadata: The optional metadata of a given transaction. - """ - block_id: BlockId - block_state: BlockState - block_failure_reason: Optional[BlockFailureReason] = None - transaction_metadata: Optional[TransactionMetadataResponse] = None - - -@json -@dataclass -class BlockWithMetadataResponse: - """A block with its metadata. - Response of GET /api/core/v3/blocks/{blockId}/full. - - Attributes: - block: The block. - metadata: The block metadata. - """ - block: Block - metadata: BlockMetadataResponse diff --git a/bindings/python/iota_sdk/secret_manager/secret_manager.py b/bindings/python/iota_sdk/secret_manager/secret_manager.py index 041fee2780..36c0b5901b 100644 --- a/bindings/python/iota_sdk/secret_manager/secret_manager.py +++ b/bindings/python/iota_sdk/secret_manager/secret_manager.py @@ -4,7 +4,6 @@ from json import dumps, loads from typing import Optional, Union import humps - from iota_sdk.external import create_secret_manager, call_secret_manager_method from iota_sdk.types.block.block import Block, UnsignedBlock from iota_sdk.types.common import HexStr @@ -126,24 +125,69 @@ def _call_method(self, name, data=None): return json_response['payload'] return response + # TODO: Should we include `bech32` in the method name? + def generate_ed25519_address(self, + coin_type: int, + bech32_hrp: str, + account_index: Optional[int] = None, + address_index: Optional[int] = None, + internal: Optional[bool] = None, + legder_nano_prompt: Optional[bool] = None): + """Generate a single Ed25519 address. + + Args: + coin_type: The coin type to generate the address for. + bech32_hrp: The bech32 HRP (human readable part) to use. + account_index: An account index. + address_index: An address index. + internal: Whether the generated address should be internal. + ledger_nano_prompt: Whether to display the address on Ledger Nano devices. + + Returns: + The generated Ed25519 address. + """ + + options = {} + options['coinType'] = coin_type + options['bech32Hrp'] = bech32_hrp + if address_index is not None: + options['range'] = {} + options['range']['start'] = address_index + options['range']['end'] = address_index + 1 + if account_index is not None: + options['accountIndex'] = account_index + if internal is not None or legder_nano_prompt is not None: + options['options'] = {} + if internal is not None: + options['options']['internal'] = internal + if legder_nano_prompt is not None: + options['options']['ledgerNanoPrompot'] = legder_nano_prompt + + return self._call_method('generateEd25519Addresses', { + 'options': options + })[0] + # pylint: disable=unused-argument + + # TODO: Should `coin_type` and `bech32_hrp` be mandatory to provide? + # TODO: Should we include `bech32` in the method name? def generate_ed25519_addresses(self, + coin_type: Optional[int] = None, + bech32_hrp: Optional[str] = None, account_index: Optional[int] = None, start: Optional[int] = None, end: Optional[int] = None, internal: Optional[bool] = None, - coin_type: Optional[int] = None, - bech32_hrp: Optional[str] = None, ledger_nano_prompt: Optional[bool] = None): - """Generate Ed25519 addresses. + """Generate multiple Ed25519 addresses at once. Args: + coin_type: The coin type to generate addresses for. + bech32_hrp: The bech32 HRP (human readable part) to use. account_index: An account index. start: The start index of the addresses to generate. end: The end index of the addresses to generate. internal: Whether the generated addresses should be internal. - coin_type: The coin type to generate addresses for. - bech32_hrp: The bech32 HRP (human readable part) to use. ledger_nano_prompt: Whether to display the address on Ledger Nano devices. Returns: diff --git a/bindings/python/iota_sdk/types/address.py b/bindings/python/iota_sdk/types/address.py index 63ced66c56..1bb1eeb958 100644 --- a/bindings/python/iota_sdk/types/address.py +++ b/bindings/python/iota_sdk/types/address.py @@ -4,7 +4,6 @@ from enum import IntEnum from dataclasses import dataclass, field from typing import Any, Dict, List, Optional, TypeAlias, Union - from iota_sdk.types.common import HexStr, json diff --git a/bindings/python/iota_sdk/types/balance.py b/bindings/python/iota_sdk/types/balance.py index 1b9a3d4a35..baeae1d460 100644 --- a/bindings/python/iota_sdk/types/balance.py +++ b/bindings/python/iota_sdk/types/balance.py @@ -18,7 +18,7 @@ class BaseCoinBalance: Attributes: total: The total balance. available: The available amount of the total balance. - voting_power: The voting power of the wallet. + # voting_power: The voting power of the wallet. """ total: int = field(metadata=config( encoder=str @@ -26,9 +26,10 @@ class BaseCoinBalance: available: int = field(metadata=config( encoder=str )) - voting_power: int = field(metadata=config( - encoder=str - )) + # TODO https://github.com/iotaledger/iota-sdk/issues/1822 + # voting_power: int = field(metadata=config( + # encoder=str + # )) @json @@ -39,9 +40,13 @@ class ManaBalance: Attributes: total: The total balance. available: The available amount of the total balance. + rewards: Mana rewards of account and delegation outputs. """ total: DecayedMana available: DecayedMana + rewards: int = field(metadata=config( + encoder=str + )) @json diff --git a/bindings/python/iota_sdk/types/block/block.py b/bindings/python/iota_sdk/types/block/block.py index beb38d6deb..3938565ed0 100644 --- a/bindings/python/iota_sdk/types/block/block.py +++ b/bindings/python/iota_sdk/types/block/block.py @@ -5,7 +5,6 @@ from dataclasses import dataclass, field from typing import Any, Dict, TypeAlias, Union from dataclasses_json import config - from iota_sdk.utils import Utils from iota_sdk.types.common import HexStr, json, SlotIndex from iota_sdk.types.node_info import ProtocolParameters diff --git a/bindings/python/iota_sdk/types/block/metadata.py b/bindings/python/iota_sdk/types/block/metadata.py deleted file mode 100644 index dad9539935..0000000000 --- a/bindings/python/iota_sdk/types/block/metadata.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2023 IOTA Stiftung -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations -from enum import Enum, IntEnum -from dataclasses import dataclass -from typing import Optional - -from iota_sdk.types.common import json -from iota_sdk.types.block.id import BlockId -from iota_sdk.types.transaction_metadata import TransactionFailureReason, TransactionState - - -@json -@dataclass -class BlockMetadata: - """Represents the metadata of a block. - Response of GET /api/core/v3/blocks/{blockId}/metadata. - - Attributes: - block_state: The block state. - transaction_state: The transaction state. - block_failure_reason: The block failure reason. - transaction_failure_reason: The transaction failure reason. - """ - block_id: BlockId - block_state: BlockState - transaction_state: Optional[TransactionState] = None - block_failure_reason: Optional[BlockFailureReason] = None - transaction_failure_reason: Optional[TransactionFailureReason] = None - - -class BlockState(Enum): - """Describes the state of a block. - - Attributes: - Pending: Stored but not accepted/confirmed. - Accepted: Valid block referenced by some validators. - Confirmed: Valid block referenced by more than 2/3 of the validators. - Finalized: Accepted/confirmed block and the slot was finalized, can no longer be reverted. - Rejected: Rejected by the node, and user should reissue payload if it contains one. - Failed: Not successfully issued due to failure reason. - """ - Pending = 0 - Accepted = 1 - Confirmed = 2 - Finalized = 3 - Rejected = 4 - Failed = 5 - - -class BlockFailureReason(IntEnum): - """Describes the reason of a block failure. - - Attributes: - TooOldToIssue (1): The block is too old to issue. - ParentTooOld (2): One of the block's parents is too old. - ParentDoesNotExist (3): One of the block's parents does not exist. - IssuerAccountNotFound (4): The block's issuer account could not be found. - ManaCostCalculationFailed (5): The mana cost could not be calculated. - BurnedInsufficientMana (6): The block's issuer account burned insufficient Mana for a block. - AccountLocked (7): The account is locked. - AccountExpired (8): The account is expired. - SignatureInvalid (9): The block's signature is invalid. - DroppedDueToCongestion (10): The block is dropped due to congestion. - PayloadInvalid (11): The block payload is invalid. - Invalid (255): The block is invalid. - """ - TooOldToIssue = 1 - ParentTooOld = 2 - ParentDoesNotExist = 3 - IssuerAccountNotFound = 4 - ManaCostCalculationFailed = 5 - BurnedInsufficientMana = 6 - AccountLocked = 7 - AccountExpired = 8 - SignatureInvalid = 9 - DroppedDueToCongestion = 10 - PayloadInvalid = 11 - Invalid = 255 - - def __str__(self): - return { - 1: "The block is too old to issue.", - 2: "One of the block's parents is too old.", - 3: "One of the block's parents does not exist.", - 4: "The block's issuer account could not be found.", - 5: "The mana cost could not be calculated.", - 6: "The block's issuer account burned insufficient Mana for a block.", - 7: "The account is locked.", - 8: "The account is expired.", - 9: "The block's signature is invalid.", - 10: "The block is dropped due to congestion.", - 11: "The block payload is invalid.", - 255: "The block is invalid." - }[self.value] diff --git a/bindings/python/iota_sdk/types/burn.py b/bindings/python/iota_sdk/types/burn.py index c176bd0c93..4b1a6c137f 100644 --- a/bindings/python/iota_sdk/types/burn.py +++ b/bindings/python/iota_sdk/types/burn.py @@ -14,17 +14,35 @@ class Burn: """A DTO for `Burn`. Attributes: + mana: Whether initial excess mana should be burned (only from inputs/outputs that have been specified manually). + generated_mana: Whether generated mana should be burned. accounts: The accounts to burn. - nfts: The NFTs to burn. foundries: The foundries to burn. + nfts: The NFTs to burn. + delegations: The delegations to burn. native_tokens: The native tokens to burn. """ + mana: Optional[bool] = None + generated_mana: Optional[bool] = None accounts: Optional[List[HexStr]] = None - nfts: Optional[List[HexStr]] = None foundries: Optional[List[HexStr]] = None + nfts: Optional[List[HexStr]] = None + delegations: Optional[List[HexStr]] = None native_tokens: Optional[List[NativeToken]] = None + def set_mana(self, burn_mana: bool) -> Burn: + """Burn excess initial mana (only from inputs/outputs that have been specified manually). + """ + self.mana = burn_mana + return self + + def set_generated_mana(self, burn_generated_mana: bool) -> Burn: + """Burn generated mana. + """ + self.generated_mana = burn_generated_mana + return self + def add_account(self, account: HexStr) -> Burn: """Add an account to the burn. """ @@ -33,6 +51,14 @@ def add_account(self, account: HexStr) -> Burn: self.accounts.append(account) return self + def add_foundry(self, foundry: HexStr) -> Burn: + """Add a foundry to the burn. + """ + if self.foundries is None: + self.foundries = [] + self.foundries.append(foundry) + return self + def add_nft(self, nft: HexStr) -> Burn: """Add an NFT to the burn. """ @@ -41,12 +67,12 @@ def add_nft(self, nft: HexStr) -> Burn: self.nfts.append(nft) return self - def add_foundry(self, foundry: HexStr) -> Burn: - """Add a foundry to the burn. + def add_delegation(self, delegation: HexStr) -> Burn: + """Add a delegation to the burn. """ - if self.foundries is None: - self.foundries = [] - self.foundries.append(foundry) + if self.delegations is None: + self.delegations = [] + self.delegations.append(delegation) return self def add_native_token(self, native_token: NativeToken) -> Burn: diff --git a/bindings/python/iota_sdk/types/client_options.py b/bindings/python/iota_sdk/types/client_options.py index 34a1c6521a..ac38dd57f6 100644 --- a/bindings/python/iota_sdk/types/client_options.py +++ b/bindings/python/iota_sdk/types/client_options.py @@ -1,4 +1,4 @@ -# Copyright 2023 IOTA Stiftung +# Copyright 2024 IOTA Stiftung # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations @@ -54,7 +54,7 @@ class ClientOptions: ignore_node_health (bool): If the node health should be ignored. node_sync_interval (Duration): - Interval in which nodes will be checked for their sync status and the [NetworkInfo](crate::NetworkInfo) gets updated. + Interval in which nodes will be checked for their sync status and the network info gets updated. quorum (bool): If node quorum is enabled. Will compare the responses from multiple nodes and only returns the response if `quorum_threshold`% of the nodes return the same one. diff --git a/bindings/python/iota_sdk/types/consolidation_params.py b/bindings/python/iota_sdk/types/consolidation_params.py index b84e0338dc..9204adb9c1 100644 --- a/bindings/python/iota_sdk/types/consolidation_params.py +++ b/bindings/python/iota_sdk/types/consolidation_params.py @@ -3,7 +3,6 @@ from typing import Optional from dataclasses import dataclass - from iota_sdk.types.common import json diff --git a/bindings/python/iota_sdk/types/decayed_mana.py b/bindings/python/iota_sdk/types/decayed_mana.py index 884beabc77..5e080d7e83 100644 --- a/bindings/python/iota_sdk/types/decayed_mana.py +++ b/bindings/python/iota_sdk/types/decayed_mana.py @@ -2,7 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass - from iota_sdk.types.common import json diff --git a/bindings/python/iota_sdk/types/event.py b/bindings/python/iota_sdk/types/event.py index 75ed34886a..00a8c55475 100644 --- a/bindings/python/iota_sdk/types/event.py +++ b/bindings/python/iota_sdk/types/event.py @@ -19,3 +19,22 @@ class WalletEventType(IntEnum): SpentOutput = 2 TransactionInclusion = 3 TransactionProgress = 4 + + +class TransactionProgressEvent(IntEnum): + """Types of transaction progress events. + + Attributes: + BuildingTransaction (0): Building a transaction. + GeneratingRemainderDepositAddress (1): Generating remainder value deposit address. + PreparedTransaction (2): Prepared transaction. + PreparedTransactionSigningHash (3): Prepared transaction signing hash hex encoded, required for blindsigning with a Ledger Nano. + SigningTransaction (4): Signing the transaction. + Broadcasting (5): Broadcasting. + """ + BuildingTransaction = 0 + GeneratingRemainderDepositAddress = 1 + PreparedTransaction = 2 + PreparedTransactionSigningHash = 3 + SigningTransaction = 4 + Broadcasting = 5 diff --git a/bindings/python/iota_sdk/types/feature.py b/bindings/python/iota_sdk/types/feature.py index 5dc4cfb5f5..bb7f6cf78b 100644 --- a/bindings/python/iota_sdk/types/feature.py +++ b/bindings/python/iota_sdk/types/feature.py @@ -81,6 +81,20 @@ class MetadataFeature: entries: Dict[str, HexStr] +@json +@dataclass +class StateMetadataFeature: + """A Metadata Feature that can only be changed by the State Controller. + Attributes: + entries: A key-value map where the keys are graphic ASCII strings and the values hex-encoded binary data. + """ + type: int = field( + default_factory=lambda: int( + FeatureType.StateMetadata), + init=False) + entries: Dict[str, HexStr] + + @json @dataclass class TagFeature: @@ -149,7 +163,7 @@ class StakingFeature: Feature: TypeAlias = Union[SenderFeature, IssuerFeature, - MetadataFeature, TagFeature, NativeTokenFeature, BlockIssuerFeature, StakingFeature] + MetadataFeature, StateMetadataFeature, TagFeature, NativeTokenFeature, BlockIssuerFeature, StakingFeature] # pylint: disable=too-many-return-statements @@ -167,6 +181,8 @@ def deserialize_feature(d: Dict[str, Any]) -> Feature: return IssuerFeature.from_dict(d) if feature_type == FeatureType.Metadata: return MetadataFeature.from_dict(d) + if feature_type == FeatureType.StateMetadata: + return StateMetadataFeature.from_dict(d) if feature_type == FeatureType.Tag: return TagFeature.from_dict(d) if feature_type == FeatureType.NativeToken: diff --git a/bindings/python/iota_sdk/types/filter_options.py b/bindings/python/iota_sdk/types/filter_options.py index c4b13a485e..ae6ec2cf48 100644 --- a/bindings/python/iota_sdk/types/filter_options.py +++ b/bindings/python/iota_sdk/types/filter_options.py @@ -3,9 +3,7 @@ from __future__ import annotations from typing import List, Optional - from dataclasses import dataclass - from iota_sdk.types.common import json diff --git a/bindings/python/iota_sdk/types/irc_27.py b/bindings/python/iota_sdk/types/irc_27.py index e66de06bb1..3ebed849ed 100644 --- a/bindings/python/iota_sdk/types/irc_27.py +++ b/bindings/python/iota_sdk/types/irc_27.py @@ -70,4 +70,4 @@ def as_hex(self): def as_feature(self): """Turns this schema into a MetadataFeature type """ - MetadataFeature(self.as_hex()) + MetadataFeature({'irc-27': self.as_hex()}) diff --git a/bindings/python/iota_sdk/types/irc_30.py b/bindings/python/iota_sdk/types/irc_30.py index 3c3341f435..afa3166df6 100644 --- a/bindings/python/iota_sdk/types/irc_30.py +++ b/bindings/python/iota_sdk/types/irc_30.py @@ -47,4 +47,4 @@ def as_hex(self): def as_feature(self): """Turns this schema into a MetadataFeature type """ - MetadataFeature(self.as_hex()) + MetadataFeature({'irc-30': self.as_hex()}) diff --git a/bindings/python/iota_sdk/types/native_token.py b/bindings/python/iota_sdk/types/native_token.py index a44f83e23e..a38e2eb3ff 100644 --- a/bindings/python/iota_sdk/types/native_token.py +++ b/bindings/python/iota_sdk/types/native_token.py @@ -3,7 +3,6 @@ from dataclasses import dataclass, field from dataclasses_json import config - from iota_sdk.types.common import hex_str_decoder, HexStr, json diff --git a/bindings/python/iota_sdk/types/network_info.py b/bindings/python/iota_sdk/types/network_info.py deleted file mode 100644 index a3e615f0b1..0000000000 --- a/bindings/python/iota_sdk/types/network_info.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2023 IOTA Stiftung -# SPDX-License-Identifier: Apache-2.0 - -from __future__ import annotations -from dataclasses import dataclass -from iota_sdk.types.common import json -from iota_sdk.types.node_info import ProtocolParameters - - -@json -@dataclass -class NetworkInfo: - """Network related information. - """ - protocol_parameters: ProtocolParameters diff --git a/bindings/python/iota_sdk/types/node_info.py b/bindings/python/iota_sdk/types/node_info.py index 4875cf80a3..3f7ef621dd 100644 --- a/bindings/python/iota_sdk/types/node_info.py +++ b/bindings/python/iota_sdk/types/node_info.py @@ -11,7 +11,7 @@ @json @dataclass -class NodeInfoStatus: +class StatusResponse: """Node status. Attributes: @@ -46,27 +46,6 @@ class NodeInfoStatus: pruning_epoch: EpochIndex -@json -@dataclass -class NodeInfoMetrics: - """Node metrics. - - Attributes: - blocks_per_second: The current rate of new blocks per second. - confirmed_blocks_per_second: The current rate of confirmed blocks per second. - confirmation_rate: The ratio of confirmed blocks to new blocks of the last confirmed slot. - """ - blocks_per_second: float = field(metadata=config( - encoder=str - )) - confirmed_blocks_per_second: float = field(metadata=config( - encoder=str - )) - confirmation_rate: float = field(metadata=config( - encoder=str - )) - - @json @dataclass class StorageScoreParameters: @@ -279,6 +258,7 @@ class ProtocolParameters: bech32_hrp: str storage_score_parameters: StorageScoreParameters work_score_parameters: WorkScoreParameters + mana_parameters: ManaParameters token_supply: int = field(metadata=config( encoder=str )) @@ -288,7 +268,6 @@ class ProtocolParameters: )) slot_duration_in_seconds: int slots_per_epoch_exponent: int - mana_parameters: ManaParameters staking_unbonding_period: int validation_blocks_per_slot: int punishment_epochs: int @@ -306,7 +285,7 @@ class ProtocolParameters: @json @dataclass -class NodeInfoBaseToken: +class BaseTokenResponse: """The base coin info. Attributes: diff --git a/bindings/python/iota_sdk/types/output.py b/bindings/python/iota_sdk/types/output.py index 1c2a24632f..84522e03b2 100644 --- a/bindings/python/iota_sdk/types/output.py +++ b/bindings/python/iota_sdk/types/output.py @@ -10,6 +10,7 @@ from iota_sdk.types.feature import deserialize_features, SenderFeature, IssuerFeature, MetadataFeature, TagFeature, NativeTokenFeature from iota_sdk.types.token_scheme import SimpleTokenScheme from iota_sdk.types.unlock_condition import deserialize_unlock_conditions, AddressUnlockCondition, StateControllerAddressUnlockCondition, GovernorAddressUnlockCondition, StorageDepositReturnUnlockCondition, TimelockUnlockCondition, ExpirationUnlockCondition, ImmutableAccountAddressUnlockCondition +from iota_sdk.types.address import AccountAddress class OutputType(IntEnum): @@ -284,7 +285,7 @@ class DelegationOutput: encoder=str )) delegation_id: HexStr - validator_address: HexStr + validator_address: AccountAddress start_epoch: EpochIndex end_epoch: EpochIndex unlock_conditions: List[AddressUnlockCondition] = field(metadata=config( diff --git a/bindings/python/iota_sdk/types/output_id.py b/bindings/python/iota_sdk/types/output_id.py index 81a49cf512..1dd72d0e30 100644 --- a/bindings/python/iota_sdk/types/output_id.py +++ b/bindings/python/iota_sdk/types/output_id.py @@ -2,64 +2,69 @@ # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass - -from iota_sdk.types.common import HexStr, json +from iota_sdk.types.common import json from iota_sdk.types.output import Output from iota_sdk.types.transaction_id import TransactionId -class OutputId(dict): +class OutputId(str): """Represents an output ID. Attributes: output_id: The unique id of an output. - transaction_id: The transaction id associated with the output. - output_index: The index of the output within a transaction. - """ - def __init__(self, transaction_id: TransactionId, output_index: int): + def __new__(cls, output_id: str): """Initialize OutputId """ - if len(transaction_id) != 74: + if len(output_id) != 78: raise ValueError( - 'transaction_id length must be 74 characters with 0x prefix') - if not transaction_id.startswith('0x'): - raise ValueError('transaction_id must start with 0x') + 'output_id length must be 78 characters with 0x prefix') + if not output_id.startswith('0x'): + raise ValueError('output_id must start with 0x') # Validate that it has only valid hex characters - int(transaction_id[2:], 16) - output_index_hex = (output_index).to_bytes(2, "little").hex() - self.output_id = transaction_id + output_index_hex - self.transaction_id = transaction_id - self.output_index = output_index + int(output_id[2:], 16) + instance = super().__new__(cls, output_id) + return instance @classmethod - def from_string(cls, output_id: HexStr): - """Creates an `OutputId` instance from a `HexStr`. + def from_transaction_id_and_output_index( + cls, transaction_id: TransactionId, output_index: int): + """Creates an `OutputId` instance from its transaction id and output index. Args: - output_id: The unique id of an output as a hex string. + transaction_id: The transaction id associated with the output. + output_index: The index of the output within a transaction. Returns: OutputId: The unique id of an output. """ - obj = cls.__new__(cls) - super(OutputId, obj).__init__() - if len(output_id) != 78: + if len(transaction_id) != 74: raise ValueError( - 'output_id length must be 78 characters with 0x prefix') - if not output_id.startswith('0x'): + 'transaction_id length must be 74 characters with 0x prefix') + if not transaction_id.startswith('0x'): raise ValueError('transaction_id must start with 0x') # Validate that it has only valid hex characters - int(output_id[2:], 16) - obj.output_id = output_id - obj.transaction_id = TransactionId(output_id[:74]) - obj.output_index = int.from_bytes( - bytes.fromhex(output_id[74:]), 'little') - return obj + int(transaction_id[2:], 16) + output_index_hex = (output_index).to_bytes(2, "little").hex() + return OutputId(transaction_id + output_index_hex) - def __repr__(self): - return self.output_id + def transaction_id(self) -> TransactionId: + """Returns the TransactionId of an OutputId. + """ + return TransactionId(self[:74]) + + def output_index(self) -> int: + """Returns the output index of an OutputId. + """ + return int.from_bytes( + bytes.fromhex(self[74:]), 'little') + + @classmethod + def from_dict(cls, output_id_dict: dict): + """Init an OutputId from a dict. + """ + return OutputId(output_id_dict) @json diff --git a/bindings/python/iota_sdk/types/output_id_proof.py b/bindings/python/iota_sdk/types/output_id_proof.py index c9d85d3f92..d4ee905f56 100644 --- a/bindings/python/iota_sdk/types/output_id_proof.py +++ b/bindings/python/iota_sdk/types/output_id_proof.py @@ -9,13 +9,13 @@ from iota_sdk.types.common import HexStr, json, SlotIndex -class TreeNodeType(IntEnum): - """Tree node types. +class OutputCommitmentProofType(IntEnum): + """OutputCommitmentProof types. Attributes: HashableNode (0): Denotes a HashableNode. LeafHash (1): Denotes a LeafHash. - Valuehash (2): Denotes a Valuehash. + ValueHash (2): Denotes a ValueHash. """ HashableNode = 0 LeafHash = 1 @@ -29,23 +29,23 @@ def deserialize_proof(d: Dict[str, Any]) -> OutputCommitmentProof: Arguments: * `d`: A dictionary that is expected to have a key called 'type' which specifies the type of the returned value. """ - node_type = d['type'] - if node_type == TreeNodeType.HashableNode: + proof_type = d['type'] + if proof_type == OutputCommitmentProofType.HashableNode: return HashableNode.from_dict(d) - if node_type == TreeNodeType.LeafHash: + if proof_type == OutputCommitmentProofType.LeafHash: return LeafHash.from_dict(d) - if node_type == TreeNodeType.ValueHash: + if proof_type == OutputCommitmentProofType.ValueHash: return ValueHash.from_dict(d) - raise Exception(f'invalid node type: {node_type}') + raise Exception(f'invalid proof type: {proof_type}') @json @dataclass class HashableNode: - """Node contains the hashes of the left and right children of a node in the tree. + """Contains the hashes of the left and right children of a node in the OutputCommitmentProof tree. """ type: int = field(default_factory=lambda: int( - TreeNodeType.HashableNode), init=False) + OutputCommitmentProofType.HashableNode), init=False) l: OutputCommitmentProof = field(metadata=config( decoder=deserialize_proof )) @@ -57,20 +57,20 @@ class HashableNode: @json @dataclass class LeafHash: - """Leaf Hash contains the hash of a leaf in the tree. + """Contains the hash of a leaf in the OutputCommitmentProof tree. """ type: int = field(default_factory=lambda: int( - TreeNodeType.LeafHash), init=False) + OutputCommitmentProofType.LeafHash), init=False) hash: HexStr @json @dataclass class ValueHash: - """Value Hash contains the hash of the value for which the proof is being computed. + """Contains the hash of the value for which the OutputCommitmentProof is being computed. """ type: int = field(default_factory=lambda: int( - TreeNodeType.ValueHash), init=False) + OutputCommitmentProofType.ValueHash), init=False) hash: HexStr @@ -83,12 +83,14 @@ class OutputIdProof: slot: The slot index of the output. output_index: The index of the output within the corresponding transaction. transaction_commitment: The commitment of the transaction that created the output. Hex-encoded with 0x prefix. - output_commitment_proof: The proof of the output commitment. Hex-encoded with 0x prefix. + output_commitment_proof: The proof of the output commitment. """ slot: SlotIndex output_index: int transaction_commitment: HexStr - output_commitment_proof: OutputCommitmentProof + output_commitment_proof: OutputCommitmentProof = field(metadata=config( + decoder=deserialize_proof + )) OutputCommitmentProof: TypeAlias = Union[HashableNode, LeafHash, ValueHash] diff --git a/bindings/python/iota_sdk/types/output_metadata.py b/bindings/python/iota_sdk/types/output_metadata.py index 381ef075c0..d55c67379c 100644 --- a/bindings/python/iota_sdk/types/output_metadata.py +++ b/bindings/python/iota_sdk/types/output_metadata.py @@ -17,7 +17,7 @@ @dataclass class OutputMetadata: """Metadata about an output. - Response of GET /api/core/v3/outputs/{output_id}/metadata. + Response of GET /api/core/v3/outputs/{outputId}/metadata. Attributes: output_id: The ID of the output. @@ -36,11 +36,11 @@ class OutputMetadata: @json @dataclass class OutputWithMetadata: - """An output with its metadata. + """An output and its metadata. Attributes: - metadata: The `OutputMetadata` object that belongs to `output`. output: An `Output` object. + metadata: The `OutputMetadata` object that belongs to `output`. """ output: Output = field(metadata=config( decoder=deserialize_output @@ -62,8 +62,8 @@ def as_dict(self): """ d = {} - d['metadata'] = self.metadata.to_dict() d['output'] = self.output.as_dict() + d['metadata'] = self.metadata.to_dict() return d diff --git a/bindings/python/iota_sdk/types/payload.py b/bindings/python/iota_sdk/types/payload.py index 581f17bc45..0cefb602cd 100644 --- a/bindings/python/iota_sdk/types/payload.py +++ b/bindings/python/iota_sdk/types/payload.py @@ -11,8 +11,6 @@ from iota_sdk.types.input import UtxoInput, deserialize_inputs from iota_sdk.types.context_input import ContextInput, deserialize_context_inputs from iota_sdk.types.output import Output, deserialize_outputs - - from iota_sdk.types.unlock import Unlock, deserialize_unlocks diff --git a/bindings/python/iota_sdk/types/send_params.py b/bindings/python/iota_sdk/types/send_params.py index 1439021ca4..5eb42425bd 100644 --- a/bindings/python/iota_sdk/types/send_params.py +++ b/bindings/python/iota_sdk/types/send_params.py @@ -8,6 +8,7 @@ from iota_sdk.types.address import AccountAddress from iota_sdk.types.common import hex_str_decoder, HexStr, json from iota_sdk.types.native_token import NativeToken +from iota_sdk.types.output_params import ReturnStrategy @json @@ -152,3 +153,20 @@ class CreateAccountOutputParams: address: str immutable_metadata: Optional[str] = None metadata: Optional[str] = None + + +@json +@dataclass +class SendManaParams: + """Parameters for sending mana. + + Attributes: + mana: Amount of mana to send, e.g. 1000000. + address: Recipient address, e.g. rms1qztwng6cty8cfm42nzvq099ev7udhrnk0rw8jt8vttf9kpqnxhpsx869vr3. + return_strategy: Whether to gift the storage deposit or not. + """ + mana: int = field(metadata=config( + encoder=str + )) + address: str + return_strategy: Optional[ReturnStrategy] = None diff --git a/bindings/python/iota_sdk/types/slot.py b/bindings/python/iota_sdk/types/slot.py index 89767a27bc..64482239fa 100644 --- a/bindings/python/iota_sdk/types/slot.py +++ b/bindings/python/iota_sdk/types/slot.py @@ -3,7 +3,6 @@ from dataclasses import dataclass, field from dataclasses_json import config - from iota_sdk.types.common import HexStr, IdWithSlotIndex, json diff --git a/bindings/python/iota_sdk/types/transaction_metadata.py b/bindings/python/iota_sdk/types/transaction_metadata.py index 0e178b7eba..d7928d0e98 100644 --- a/bindings/python/iota_sdk/types/transaction_metadata.py +++ b/bindings/python/iota_sdk/types/transaction_metadata.py @@ -4,7 +4,7 @@ from enum import Enum -class TransactionState(Enum): +class TransactionState(str, Enum): """Describes the state of a transaction. Attributes: @@ -14,11 +14,11 @@ class TransactionState(Enum): Finalized: Included, its included block is finalized and cannot be reverted anymore. Failed: Not successfully issued due to failure reason. """ - Pending = 0 - Accepted = 1 - Confirmed = 2 - Finalized = 3 - Failed = 4 + Pending = 'pending' + Accepted = 'accepted' + Confirmed = 'confirmed' + Finalized = 'finalized' + Failed = 'failed' class TransactionFailureReason(Enum): @@ -52,48 +52,47 @@ class TransactionFailureReason(Enum): SenderFeatureNotUnlocked = 25 IssuerFeatureNotUnlocked = 26 StakingRewardInputMissing = 27 - StakingBlockIssuerFeatureMissing = 28 - StakingCommitmentInputMissing = 29 - StakingRewardClaimingInvalid = 30 - StakingFeatureRemovedBeforeUnbonding = 31 - StakingFeatureModifiedBeforeUnbonding = 32 - StakingStartEpochInvalid = 33 - StakingEndEpochTooEarly = 34 - BlockIssuerCommitmentInputMissing = 35 - BlockIssuanceCreditInputMissing = 36 - BlockIssuerNotExpired = 37 - BlockIssuerExpiryTooEarly = 38 - ManaMovedOffBlockIssuerAccount = 39 - AccountLocked = 40 - TimelockCommitmentInputMissing = 41 - TimelockNotExpired = 42 - ExpirationCommitmentInputMissing = 43 - ExpirationNotUnlockable = 44 - ReturnAmountNotFulFilled = 45 - NewChainOutputHasNonZeroedId = 46 - ChainOutputImmutableFeaturesChanged = 47 - ImplicitAccountDestructionDisallowed = 48 - MultipleImplicitAccountCreationAddresses = 49 - AccountInvalidFoundryCounter = 50 - AnchorInvalidStateTransition = 51 - AnchorInvalidGovernanceTransition = 52 - FoundryTransitionWithoutAccount = 53 - FoundrySerialInvalid = 54 - DelegationCommitmentInputMissing = 55 - DelegationRewardInputMissing = 56 - DelegationRewardsClaimingInvalid = 57 - DelegationOutputTransitionedTwice = 58 - DelegationModified = 59 - DelegationStartEpochInvalid = 60 - DelegationAmountMismatch = 61 - DelegationEndEpochNotZero = 62 - DelegationEndEpochInvalid = 63 - CapabilitiesNativeTokenBurningNotAllowed = 64 - CapabilitiesManaBurningNotAllowed = 65 - CapabilitiesAccountDestructionNotAllowed = 66 - CapabilitiesAnchorDestructionNotAllowed = 67 - CapabilitiesFoundryDestructionNotAllowed = 68 - CapabilitiesNftDestructionNotAllowed = 69 + StakingCommitmentInputMissing = 28 + StakingRewardClaimingInvalid = 29 + StakingFeatureRemovedBeforeUnbonding = 30 + StakingFeatureModifiedBeforeUnbonding = 31 + StakingStartEpochInvalid = 32 + StakingEndEpochTooEarly = 33 + BlockIssuerCommitmentInputMissing = 34 + BlockIssuanceCreditInputMissing = 35 + BlockIssuerNotExpired = 36 + BlockIssuerExpiryTooEarly = 37 + ManaMovedOffBlockIssuerAccount = 38 + AccountLocked = 39 + TimelockCommitmentInputMissing = 40 + TimelockNotExpired = 41 + ExpirationCommitmentInputMissing = 42 + ExpirationNotUnlockable = 43 + ReturnAmountNotFulFilled = 44 + NewChainOutputHasNonZeroedId = 45 + ChainOutputImmutableFeaturesChanged = 46 + ImplicitAccountDestructionDisallowed = 47 + MultipleImplicitAccountCreationAddresses = 48 + AccountInvalidFoundryCounter = 49 + AnchorInvalidStateTransition = 50 + AnchorInvalidGovernanceTransition = 51 + FoundryTransitionWithoutAccount = 52 + FoundrySerialInvalid = 53 + DelegationCommitmentInputMissing = 54 + DelegationRewardInputMissing = 54 + DelegationRewardsClaimingInvalid = 56 + DelegationOutputTransitionedTwice = 57 + DelegationModified = 58 + DelegationStartEpochInvalid = 59 + DelegationAmountMismatch = 60 + DelegationEndEpochNotZero = 61 + DelegationEndEpochInvalid = 62 + CapabilitiesNativeTokenBurningNotAllowed = 63 + CapabilitiesManaBurningNotAllowed = 64 + CapabilitiesAccountDestructionNotAllowed = 65 + CapabilitiesAnchorDestructionNotAllowed = 66 + CapabilitiesFoundryDestructionNotAllowed = 67 + CapabilitiesNftDestructionNotAllowed = 68 SemanticValidationFailed = 255 def __str__(self): @@ -106,7 +105,7 @@ def __str__(self): 5: "Invalid unlock for chain address.", 6: "Invalid unlock for direct unlockable address.", 7: "Invalid unlock for multi address.", - 8: "Commitment input required with reward or BIC input.", + 8: "Commitment input references an invalid or non-existent commitment.", 9: "BIC input reference cannot be loaded.", 10: "Reward input does not reference a staking account or a delegation output.", 11: "Staking rewards could not be calculated due to storage issues or overflow.", @@ -117,56 +116,55 @@ def __str__(self): 16: "Mana decay creation slot/epoch index exceeds target slot/epoch index.", 17: "Native token sums are unbalanced.", 18: "Simple token scheme minted/melted value decreased.", - 19: "Simple token scheme minting invalid.", - 20: "Simple token scheme melting invalid.", - 21: "Simple token scheme maximum supply changed.", - 22: "Simple token scheme genesis invalid.", + 19: "Simple token scheme's minted tokens did not increase by the minted amount or melted tokens changed.", + 20: "Simple token scheme's melted tokens did not increase by the melted amount or minted tokens changed.", + 21: "Simple token scheme's maximum supply cannot change during transition.", + 22: "Newly created simple token scheme's melted tokens are not zero or minted tokens do not equal native token amount in transaction.", 23: "Multi address length and multi unlock length do not match.", 24: "Multi address unlock threshold not reached.", 25: "Sender feature is not unlocked.", 26: "Issuer feature is not unlocked.", 27: "Staking feature removal or resetting requires a reward input.", - 28: "Block issuer feature missing for account with staking feature.", - 29: "Staking feature validation requires a commitment input.", - 30: "Staking feature must be removed or reset in order to claim rewards.", - 31: "Staking feature can only be removed after the unbonding period.", - 32: "Staking start epoch, fixed cost and staked amount cannot be modified while bonded.", - 33: "Staking start epoch must be the epoch of the transaction.", - 34: "Staking end epoch must be set to the transaction epoch plus the unbonding period.", - 35: "Commitment input missing for block issuer feature.", - 36: "Block issuance credit input missing for account with block issuer feature.", - 37: "Block issuer feature has not expired.", - 38: "Block issuer feature expiry set too early.", - 39: "Mana cannot be moved off block issuer accounts except with manalocks.", - 40: "Account is locked due to negative block issuance credits.", - 41: "Transaction's containing a timelock condition require a commitment input.", - 42: "Timelock not expired.", - 43: "Transaction's containing an expiration condition require a commitment input.", - 44: "Expiration unlock condition cannot be unlocked.", - 45: "Return amount not fulfilled.", - 46: "New chain output has non-zeroed ID.", - 47: "Immutable features in chain output modified during transition.", - 48: "Cannot destroy implicit account; must be transitioned to account.", - 49: "Multiple implicit account creation addresses on the input side.", - 50: "Foundry counter in account decreased or did not increase by the number of new foundries.", - 51: "Anchor has an invalid state transition.", - 52: "Anchor has an invalid governance transition.", - 53: "Foundry output transitioned without accompanying account on input or output side.", - 54: "Foundry output serial number is invalid.", - 55: "Delegation output validation requires a commitment input.", - 56: "Delegation output cannot be destroyed without a reward input.", - 57: "Invalid delegation mana rewards claiming.", - 58: "Delegation output attempted to be transitioned twice.", - 59: "Delegated amount, validator ID and start epoch cannot be modified.", - 60: "Invalid start epoch.", - 61: "Delegated amount does not match amount.", - 62: "End epoch must be set to zero at output genesis.", - 63: "Delegation end epoch does not match current epoch.", - 64: "Native token burning is not allowed by the transaction capabilities.", - 65: "Mana burning is not allowed by the transaction capabilities.", - 66: "Account destruction is not allowed by the transaction capabilities.", - 67: "Anchor destruction is not allowed by the transaction capabilities.", - 68: "Foundry destruction is not allowed by the transaction capabilities.", - 69: "NFT destruction is not allowed by the transaction capabilities.", + 28: "Staking feature validation requires a commitment input.", + 29: "Staking feature must be removed or reset in order to claim rewards.", + 30: "Staking feature can only be removed after the unbonding period.", + 31: "Staking start epoch, fixed cost and staked amount cannot be modified while bonded.", + 32: "Staking start epoch must be the epoch of the transaction.", + 33: "Staking end epoch must be set to the transaction epoch plus the unbonding period.", + 34: "Commitment input missing for block issuer feature.", + 35: "Block issuance credit input missing for account with block issuer feature.", + 36: "Block issuer feature has not expired.", + 37: "Block issuer feature expiry set too early.", + 38: "Mana cannot be moved off block issuer accounts except with manalocks.", + 39: "Account is locked due to negative block issuance credits.", + 40: "Transaction's containing a timelock condition require a commitment input.", + 41: "Timelock not expired.", + 42: "Transaction's containing an expiration condition require a commitment input.", + 43: "Expiration unlock condition cannot be unlocked.", + 44: "Return amount not fulfilled.", + 45: "New chain output has non-zeroed ID.", + 46: "Immutable features in chain output modified during transition.", + 47: "Cannot destroy implicit account; must be transitioned to account.", + 48: "Multiple implicit account creation addresses on the input side.", + 49: "Foundry counter in account decreased or did not increase by the number of new foundries.", + 50: "Anchor has an invalid state transition.", + 51: "Anchor has an invalid governance transition.", + 52: "Foundry output transitioned without accompanying account on input or output side.", + 53: "Foundry output serial number is invalid.", + 54: "Delegation output validation requires a commitment input.", + 55: "Delegation output cannot be destroyed without a reward input.", + 56: "Invalid delegation mana rewards claiming.", + 57: "Delegation output attempted to be transitioned twice.", + 58: "Delegated amount, validator ID and start epoch cannot be modified.", + 59: "Invalid start epoch.", + 60: "Delegated amount does not match amount.", + 61: "End epoch must be set to zero at output genesis.", + 62: "Delegation end epoch does not match current epoch.", + 63: "Native token burning is not allowed by the transaction capabilities.", + 64: "Mana burning is not allowed by the transaction capabilities.", + 65: "Account destruction is not allowed by the transaction capabilities.", + 66: "Anchor destruction is not allowed by the transaction capabilities.", + 67: "Foundry destruction is not allowed by the transaction capabilities.", + 68: "NFT destruction is not allowed by the transaction capabilities.", 255: "Semantic validation failed.", }[self.value] diff --git a/bindings/python/iota_sdk/types/transaction_options.py b/bindings/python/iota_sdk/types/transaction_options.py index 9ae1002a2a..aa6eee238d 100644 --- a/bindings/python/iota_sdk/types/transaction_options.py +++ b/bindings/python/iota_sdk/types/transaction_options.py @@ -3,10 +3,10 @@ from enum import Enum from typing import Optional, List, Union -from dataclasses import dataclass +from dataclasses import dataclass, field +from iota_sdk.types.address import Address from iota_sdk.types.burn import Burn from iota_sdk.types.common import HexStr, json -from iota_sdk.types.context_input import ContextInput from iota_sdk.types.output_id import OutputId from iota_sdk.types.payload import TaggedDataPayload @@ -17,30 +17,10 @@ class RemainderValueStrategyCustomAddress: """Remainder value strategy for custom addresses. Attributes: - address: An address to move the remainder value to. - key_index: The address key index. - internal: Determines if an address is a public or an internal (change) address. - used: Indicates whether an address has been used already. + value: An address to move the remainder value to. """ - - address: str - key_index: int - internal: bool - used: bool - - def to_dict(self) -> dict: - """Custom dict conversion. - """ - - return { - 'strategy': 'CustomAddress', - 'value': { - 'address': self.address, - 'keyIndex': self.key_index, - 'internal': self.internal, - 'used': self.used - } - } + strategy: str = field(default_factory=lambda: 'CustomAddress', init=False) + value: Address class RemainderValueStrategy(Enum): @@ -57,7 +37,6 @@ def to_dict(self) -> dict: return { 'strategy': self.name, - 'value': self.value[0] } @@ -69,38 +48,21 @@ class TransactionOptions: Attributes: remainder_value_strategy: The strategy applied for base coin remainders. tagged_data_payload: An optional tagged data payload. - context_inputs: Transaction context inputs to include. required_inputs: Inputs that must be used for the transaction. - burn: Specifies what needs to be burned during input selection. + burn: Specifies what needs to be burned in the transaction. note: A string attached to the transaction. allow_micro_amount: Whether to allow sending a micro amount. allow_additional_input_selection: Whether to allow the selection of additional inputs for this transaction. - capabilities: Transaction capabilities. mana_allotments: Mana allotments for the transaction. issuer_id: Optional block issuer to which the transaction will have required mana allotted. """ - - def __init__(self, remainder_value_strategy: Optional[Union[RemainderValueStrategy, RemainderValueStrategyCustomAddress]] = None, - tagged_data_payload: Optional[TaggedDataPayload] = None, - context_inputs: Optional[List[ContextInput]] = None, - required_inputs: Optional[List[OutputId]] = None, - burn: Optional[Burn] = None, - note: Optional[str] = None, - allow_micro_amount: Optional[bool] = None, - allow_additional_input_selection: Optional[bool] = None, - capabilities: Optional[HexStr] = None, - mana_allotments: Optional[dict[HexStr, int]] = None, - issuer_id: Optional[HexStr] = None): - """Initialize transaction options. - """ - self.remainder_value_strategy = remainder_value_strategy - self.tagged_data_payload = tagged_data_payload - self.context_inputs = context_inputs - self.required_inputs = required_inputs - self.burn = burn - self.note = note - self.allow_micro_amount = allow_micro_amount - self.allow_additional_input_selection = allow_additional_input_selection - self.capabilities = capabilities - self.mana_allotments = mana_allotments - self.issuer_id = issuer_id + remainder_value_strategy: Optional[Union[RemainderValueStrategy, + RemainderValueStrategyCustomAddress]] = None + tagged_data_payload: Optional[TaggedDataPayload] = None + required_inputs: Optional[List[OutputId]] = None + burn: Optional[Burn] = None + note: Optional[str] = None + allow_micro_amount: Optional[bool] = None + allow_additional_input_selection: Optional[bool] = None + mana_allotments: Optional[dict[HexStr, int]] = None + issuer_id: Optional[HexStr] = None diff --git a/bindings/python/iota_sdk/utils.py b/bindings/python/iota_sdk/utils.py index 4c54fa24bb..02f27c60fe 100644 --- a/bindings/python/iota_sdk/utils.py +++ b/bindings/python/iota_sdk/utils.py @@ -5,10 +5,9 @@ import json from typing import TYPE_CHECKING, List, Optional from iota_sdk.common import custom_encoder - from iota_sdk.types.block.id import BlockId from iota_sdk.types.signature import Ed25519Signature -from iota_sdk.types.address import Address, deserialize_address +from iota_sdk.types.address import Address, Ed25519Address, deserialize_address from iota_sdk.types.common import HexStr from iota_sdk.types.decayed_mana import DecayedMana from iota_sdk.types.payload import Transaction, SignedTransactionPayload @@ -30,53 +29,24 @@ class Utils: """Utility functions. """ - - @staticmethod - def bech32_to_hex(bech32: str) -> HexStr: - """Convert a Bech32 string to a hex string. - """ - return _call_method('bech32ToHex', { - 'bech32': bech32 - }) - - # pylint: disable=redefined-builtin @staticmethod - def hex_to_bech32(hex_str: HexStr, bech32_hrp: str) -> str: - """Convert a hex encoded address to a Bech32 encoded address. + def address_to_bech32(address: Address, bech32_hrp: str) -> str: + """Convert an address to its bech32 representation. """ - return _call_method('hexToBech32', { - 'hex': hex_str, - 'bech32Hrp': bech32_hrp - }) - - @staticmethod - def account_id_to_bech32(account_id: HexStr, bech32_hrp: str) -> str: - """Convert an account id to a Bech32 encoded address. - """ - return _call_method('accountIdToBech32', { - 'accountId': account_id, - 'bech32Hrp': bech32_hrp - }) - - @staticmethod - def nft_id_to_bech32(nft_id: HexStr, bech32_hrp: str) -> str: - """Convert an NFT ID to a Bech32 encoded address. - """ - return _call_method('nftIdToBech32', { - 'nftId': nft_id, + return _call_method('addressToBech32', { + 'address': address, 'bech32Hrp': bech32_hrp }) # pylint: disable=redefined-builtin @staticmethod - def hex_public_key_to_bech32_address( - hex_str: HexStr, bech32_hrp: str) -> str: - """Convert a hex encoded public key to a Bech32 encoded address. + def public_key_hash( + hex_str: HexStr) -> Ed25519Address: + """Hashes a hex encoded public key with Blake2b256. """ - return _call_method('hexPublicKeyToBech32Address', { - 'hex': hex_str, - 'bech32Hrp': bech32_hrp - }) + return Ed25519Address(_call_method('blake2b256Hash', { + 'bytes': hex_str, + })) @staticmethod def parse_bech32_address(address: str) -> Address: @@ -114,8 +84,8 @@ def mnemonic_to_hex_seed(mnemonic: str) -> HexStr: def compute_account_id(output_id: OutputId) -> HexStr: """Compute the account id for the given account output id. """ - return _call_method('computeAccountId', { - 'outputId': repr(output_id) + return _call_method('blake2b256Hash', { + 'bytes': repr(output_id) }) @staticmethod @@ -142,8 +112,8 @@ def compute_minimum_output_amount(output, storage_score_parameters) -> int: def compute_nft_id(output_id: OutputId) -> HexStr: """Compute the NFT id for the given NFT output id. """ - return _call_method('computeNftId', { - 'outputId': repr(output_id) + return _call_method('blake2b256Hash', { + 'bytes': repr(output_id) }) @staticmethod @@ -151,7 +121,7 @@ def compute_output_id(transaction_id: TransactionId, index: int) -> OutputId: """Compute the output id from transaction id and output index. """ - return OutputId.from_string(_call_method('computeOutputId', { + return OutputId(_call_method('computeOutputId', { 'id': transaction_id, 'index': index, })) @@ -223,10 +193,10 @@ def verify_secp256k1_ecdsa_signature( @staticmethod def verify_transaction_semantic( - transaction: Transaction, inputs: List[InputSigningData], protocol_parameters: ProtocolParameters, unlocks: Optional[List[Unlock]] = None, mana_rewards: Optional[dict[OutputId, int]] = None) -> str: + transaction: Transaction, inputs: List[InputSigningData], protocol_parameters: ProtocolParameters, unlocks: Optional[List[Unlock]] = None, mana_rewards: Optional[dict[OutputId, int]] = None): """Verifies the semantic of a transaction. """ - return _call_method('verifyTransactionSemantic', { + _call_method('verifyTransactionSemantic', { 'transaction': transaction, 'inputs': inputs, 'unlocks': unlocks, @@ -294,6 +264,20 @@ def block_bytes( 'block': block.as_dict(), })) + @staticmethod + def iota_mainnet_protocol_parameters() -> ProtocolParameters: + """Returns sample protocol parameters for IOTA mainnet. + """ + return ProtocolParameters.from_dict( + _call_method('iotaMainnetProtocolParameters')) + + @staticmethod + def shimmer_mainnet_protocol_parameters() -> ProtocolParameters: + """Returns sample protocol parameters for Shimmer mainnet. + """ + return ProtocolParameters.from_dict( + _call_method('shimmerMainnetProtocolParameters')) + class UtilsError(Exception): """A utils error.""" diff --git a/bindings/python/iota_sdk/wallet/prepared_transaction.py b/bindings/python/iota_sdk/wallet/prepared_transaction.py index 724eea14c7..b5349986a2 100644 --- a/bindings/python/iota_sdk/wallet/prepared_transaction.py +++ b/bindings/python/iota_sdk/wallet/prepared_transaction.py @@ -2,10 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations - from typing import TYPE_CHECKING from dataclasses import dataclass - from iota_sdk.types.common import HexStr, json from iota_sdk.types.transaction_with_metadata import CreateDelegationTransaction, CreateNativeTokenTransaction, TransactionWithMetadata from iota_sdk.types.transaction_data import PreparedTransactionData diff --git a/bindings/python/iota_sdk/wallet/sync_options.py b/bindings/python/iota_sdk/wallet/sync_options.py index 1365ccb811..7a2b702c1b 100644 --- a/bindings/python/iota_sdk/wallet/sync_options.py +++ b/bindings/python/iota_sdk/wallet/sync_options.py @@ -13,13 +13,15 @@ class WalletSyncOptions: Attributes: basic_outputs: Whether to sync basic outputs. - nft_outputs: Whether to sync NFT outputs. account_outputs: whether to sync account outputs. + nft_outputs: Whether to sync NFT outputs. + delegation_outputs: Whether to sync delegation outputs. """ basic_outputs: Optional[bool] = None - nft_outputs: Optional[bool] = None account_outputs: Optional[bool] = None + nft_outputs: Optional[bool] = None + delegation_outputs: Optional[bool] = None @json @@ -29,15 +31,17 @@ class AccountSyncOptions: Attributes: basic_outputs: Whether to sync basic outputs. - nft_outputs: Whether to sync NFT outputs. account_outputs: Whether to sync account outputs. foundry_outputs: Whether to sync foundry outputs. + nft_outputs: Whether to sync NFT outputs. + delegation_outputs: Whether to sync delegation outputs. """ basic_outputs: Optional[bool] = None - nft_outputs: Optional[bool] = None account_outputs: Optional[bool] = None foundry_outputs: Optional[bool] = None + nft_outputs: Optional[bool] = None + delegation_outputs: Optional[bool] = None @json @@ -47,13 +51,15 @@ class NftSyncOptions: Attributes: basic_outputs: Whether to sync basic outputs. - nft_outputs: Whether to sync NFT outputs. account_outputs: Whether to sync account outputs. + nft_outputs: Whether to sync NFT outputs. + delegation_outputs: Whether to sync delegation outputs. """ basic_outputs: Optional[bool] = None - nft_outputs: Optional[bool] = None account_outputs: Optional[bool] = None + nft_outputs: Optional[bool] = None + delegation_outputs: Optional[bool] = None @json diff --git a/bindings/python/iota_sdk/wallet/wallet.py b/bindings/python/iota_sdk/wallet/wallet.py index 8df3991aa8..f9e666197c 100644 --- a/bindings/python/iota_sdk/wallet/wallet.py +++ b/bindings/python/iota_sdk/wallet/wallet.py @@ -4,7 +4,6 @@ from json import dumps from typing import List, Optional, Union from dataclasses import dataclass - from iota_sdk import destroy_wallet, create_wallet, listen_wallet, get_client_from_wallet, get_secret_manager_from_wallet, Client from iota_sdk.secret_manager.secret_manager import LedgerNanoSecretManager, MnemonicSecretManager, StrongholdSecretManager, SeedSecretManager, SecretManager from iota_sdk.wallet.common import _call_wallet_method_routine @@ -23,7 +22,7 @@ from iota_sdk.types.output_params import OutputParams from iota_sdk.types.transaction_data import PreparedTransactionData, SignedTransactionData from iota_sdk.types.transaction_id import TransactionId -from iota_sdk.types.send_params import BeginStakingParams, CreateAccountOutputParams, CreateDelegationParams, CreateNativeTokenParams, MintNftParams, SendNativeTokenParams, SendNftParams, SendParams +from iota_sdk.types.send_params import BeginStakingParams, CreateAccountOutputParams, CreateDelegationParams, CreateNativeTokenParams, MintNftParams, SendManaParams, SendNativeTokenParams, SendNftParams, SendParams from iota_sdk.types.signature import Bip44 from iota_sdk.types.transaction_with_metadata import CreateDelegationTransaction, CreateNativeTokenTransaction, TransactionWithMetadata from iota_sdk.types.transaction_options import TransactionOptions @@ -73,7 +72,7 @@ def _call_method(self, name: str, data=None): message['data'] = data return message - def backup(self, destination: str, password: str): + def backup_to_stronghold_snapshot(self, destination: str, password: str): """Backup storage. """ return self._call_method( @@ -150,7 +149,7 @@ def clear_listeners(self, events: Optional[List[int]] = None): } ) - def restore_backup(self, source: str, password: str): + def restore_from_stronghold_snapshot(self, source: str, password: str): """Restore a backup from a Stronghold file. Replaces `client_options`, `coin_type`, `secret_manager` and wallet. Returns an error if the wallet was already created. If Stronghold is used @@ -592,18 +591,6 @@ def prepare_output(self, params: OutputParams, }) ) - def prepare_send(self, params: List[SendParams], - options: Optional[TransactionOptions] = None) -> PreparedTransaction: - """Prepare to send base coins. - """ - prepared = PreparedTransactionData.from_dict(self._call_method( - 'prepareSend', { - 'params': params, - 'options': options - } - )) - return PreparedTransaction(self, prepared) - def create_delegation(self, params: CreateDelegationParams, options: Optional[TransactionOptions] = None) -> CreateDelegationTransaction: """Create a delegation. @@ -702,18 +689,18 @@ def announce_candidacy(self, account_id: HexStr) -> BlockId: } )) - def send_transaction( + def send_outputs( self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: - """Send a transaction. + """Send outputs. """ - return self.prepare_transaction(outputs, options).send() + return self.prepare_send_outputs(outputs, options).send() - def prepare_transaction( + def prepare_send_outputs( self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> PreparedTransaction: - """Prepare transaction. + """Prepare to send outputs. """ prepared = PreparedTransactionData.from_dict(self._call_method( - 'prepareTransaction', { + 'prepareSendOutputs', { 'outputs': outputs, 'options': options } @@ -721,40 +708,40 @@ def prepare_transaction( return PreparedTransaction(self, prepared) def wait_for_transaction_acceptance( - self, transaction_id: TransactionId, interval=None, max_attempts=None) -> BlockId: - """Checks the transaction state for a provided transaction id until it's accepted. Interval in milliseconds. Returns the block id that - contains this transaction. + self, transaction_id: TransactionId, interval=None, max_attempts=None): + """Checks the transaction state for a provided transaction id until it's accepted. Interval in milliseconds. """ - return BlockId(self._call_method( + return self._call_method( 'waitForTransactionAcceptance', { 'transactionId': transaction_id, 'interval': interval, 'maxAttempts': max_attempts } - )) + ) def send(self, amount: int, address: str, options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Send base coins. """ - return TransactionWithMetadata.from_dict(self._call_method( - 'send', { - 'amount': str(amount), - 'address': address, - 'options': options - } - )) + return self.prepare_send([SendParams(address, amount)], options).send() def send_with_params( self, params: List[SendParams], options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: """Send base coins to multiple addresses or with additional parameters. """ - return TransactionWithMetadata.from_dict(self._call_method( - 'sendWithParams', { - 'params': [param.to_dict() for param in params], + return self.prepare_send(params, options).send() + + def prepare_send(self, params: List[SendParams], + options: Optional[TransactionOptions] = None) -> PreparedTransaction: + """Prepare to send with params. + """ + prepared = PreparedTransactionData.from_dict(self._call_method( + 'prepareSend', { + 'params': params, 'options': options } )) + return PreparedTransaction(self, prepared) def send_native_tokens( self, params: List[SendNativeTokenParams], options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: @@ -794,16 +781,23 @@ def prepare_send_nft(self, params: List[SendNftParams], )) return PreparedTransaction(self, prepared) - def send_outputs( - self, outputs: List[Output], options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: - """Send outputs in a transaction. + def send_mana( + self, params: SendManaParams, options: Optional[TransactionOptions] = None) -> TransactionWithMetadata: + """Send mana. """ - return TransactionWithMetadata.from_dict(self._call_method( - 'sendOutputs', { - 'outputs': outputs, - 'options': options, + return self.prepare_send_mana(params, options).send() + + def prepare_send_mana(self, params: SendManaParams, + options: Optional[TransactionOptions] = None) -> PreparedTransaction: + """Prepare to send mana. + """ + prepared = PreparedTransactionData.from_dict(self._call_method( + 'prepareSendMana', { + 'params': params, + 'options': options } )) + return PreparedTransaction(self, prepared) def set_alias(self, alias: str): """Set alias. diff --git a/bindings/python/src/client.rs b/bindings/python/src/client.rs index 3eac9d478e..ccc1c0bd13 100644 --- a/bindings/python/src/client.rs +++ b/bindings/python/src/client.rs @@ -11,7 +11,7 @@ use iota_sdk_bindings_core::{ }; use pyo3::{prelude::*, types::PyTuple}; -use crate::error::Result; +use crate::Error; #[pyclass] pub struct Client { @@ -20,10 +20,10 @@ pub struct Client { /// Create client for python-side usage. #[pyfunction] -pub fn create_client(options: Option) -> Result { +pub fn create_client(options: Option) -> Result { let runtime = tokio::runtime::Runtime::new()?; let client = runtime.block_on(async move { - Result::Ok(match options { + Result::<_, Error>::Ok(match options { Some(options) => ClientBuilder::new().from_json(&options)?.finish().await?, None => ClientBuilder::new().finish().await?, }) @@ -33,7 +33,7 @@ pub fn create_client(options: Option) -> Result { } #[pyfunction] -pub fn call_client_method(client: &Client, method: String) -> Result { +pub fn call_client_method(client: &Client, method: String) -> Result { let method = serde_json::from_str::(&method)?; let response = crate::block_on(async { rust_call_client_method(&client.client, method).await }); @@ -41,7 +41,7 @@ pub fn call_client_method(client: &Client, method: String) -> Result { } #[pyfunction] -pub fn listen_mqtt(client: &Client, topics: Vec, handler: PyObject) -> Result<()> { +pub fn listen_mqtt(client: &Client, topics: Vec, handler: PyObject) -> Result<(), Error> { let topics = topics .iter() .map(Topic::new) diff --git a/bindings/python/src/error.rs b/bindings/python/src/error.rs index 83e35b6685..f668fc5078 100644 --- a/bindings/python/src/error.rs +++ b/bindings/python/src/error.rs @@ -5,9 +5,6 @@ use core::convert::{From, Infallible}; use pyo3::{exceptions, prelude::*}; -/// The `Result` structure to wrap the error type for python binding. -pub(crate) type Result = std::result::Result; - /// The Error type. #[derive(Debug)] pub struct Error { @@ -37,14 +34,6 @@ impl From for Error { } } -impl From for Error { - fn from(err: iota_sdk_bindings_core::iota_sdk::types::block::Error) -> Self { - Self { - error: PyErr::new::(err.to_string()), - } - } -} - impl From for Error { fn from(err: iota_sdk_bindings_core::Error) -> Self { Self { @@ -53,8 +42,8 @@ impl From for Error { } } -impl From for Error { - fn from(err: iota_sdk_bindings_core::iota_sdk::client::Error) -> Self { +impl From for Error { + fn from(err: iota_sdk_bindings_core::iota_sdk::client::ClientError) -> Self { Self { error: PyErr::new::(err.to_string()), } @@ -69,8 +58,8 @@ impl From for Error { } } -impl From for Error { - fn from(err: iota_sdk_bindings_core::iota_sdk::wallet::Error) -> Self { +impl From for Error { + fn from(err: iota_sdk_bindings_core::iota_sdk::wallet::WalletError) -> Self { Self { error: PyErr::new::(err.to_string()), } diff --git a/bindings/python/src/lib.rs b/bindings/python/src/lib.rs index 594e6e7ac8..5e9f68a723 100644 --- a/bindings/python/src/lib.rs +++ b/bindings/python/src/lib.rs @@ -18,12 +18,7 @@ use once_cell::sync::OnceCell; use pyo3::{prelude::*, wrap_pyfunction}; use tokio::runtime::Runtime; -use self::{ - client::*, - error::{Error, Result}, - secret_manager::*, - wallet::*, -}; +use self::{client::*, error::Error, secret_manager::*, wallet::*}; /// Use one runtime. pub(crate) fn block_on(cb: C) -> C::Output { @@ -34,13 +29,13 @@ pub(crate) fn block_on(cb: C) -> C::Output { /// Init the Rust logger. #[pyfunction] -pub fn init_logger(config: String) -> Result<()> { +pub fn init_logger(config: String) -> Result<(), Error> { rust_init_logger(config).map_err(|err| Error::from(format!("{:?}", err)))?; Ok(()) } #[pyfunction] -pub fn call_utils_method(method: String) -> Result { +pub fn call_utils_method(method: String) -> Result { let method = serde_json::from_str::(&method)?; let response = rust_call_utils_method(method); Ok(serde_json::to_string(&response)?) @@ -55,7 +50,7 @@ pub fn migrate_stronghold_snapshot_v2_to_v3( rounds: u32, new_path: Option, new_password: Option, -) -> Result<()> { +) -> Result<(), Error> { Ok(StrongholdAdapter::migrate_snapshot_v2_to_v3( ¤t_path, current_password.into(), @@ -64,7 +59,7 @@ pub fn migrate_stronghold_snapshot_v2_to_v3( new_path.as_ref(), new_password.map(Into::into), ) - .map_err(iota_sdk_bindings_core::iota_sdk::client::Error::Stronghold)?) + .map_err(iota_sdk_bindings_core::iota_sdk::client::ClientError::Stronghold)?) } /// IOTA SDK implemented in Rust for Python binding. diff --git a/bindings/python/src/secret_manager.rs b/bindings/python/src/secret_manager.rs index 18af2f17f0..bbb75691e9 100644 --- a/bindings/python/src/secret_manager.rs +++ b/bindings/python/src/secret_manager.rs @@ -11,7 +11,7 @@ use iota_sdk_bindings_core::{ use pyo3::prelude::*; use tokio::sync::RwLock; -use crate::error::Result; +use crate::Error; #[pyclass] pub struct SecretManager { @@ -20,7 +20,7 @@ pub struct SecretManager { /// Create secret_manager for python-side usage. #[pyfunction] -pub fn create_secret_manager(options: String) -> Result { +pub fn create_secret_manager(options: String) -> Result { let secret_manager_dto = serde_json::from_str::(&options)?; let secret_manager = RustSecretManager::try_from(secret_manager_dto)?; Ok(SecretManager { @@ -29,7 +29,7 @@ pub fn create_secret_manager(options: String) -> Result { } #[pyfunction] -pub fn call_secret_manager_method(secret_manager: &SecretManager, method: String) -> Result { +pub fn call_secret_manager_method(secret_manager: &SecretManager, method: String) -> Result { let method = serde_json::from_str::(&method)?; let response = crate::block_on(async { let secret_manager = secret_manager.secret_manager.read().await; diff --git a/bindings/python/src/wallet.rs b/bindings/python/src/wallet.rs index 2dd2f39733..8a977c1e70 100644 --- a/bindings/python/src/wallet.rs +++ b/bindings/python/src/wallet.rs @@ -11,11 +11,7 @@ use iota_sdk_bindings_core::{ use pyo3::{prelude::*, types::PyTuple}; use tokio::sync::RwLock; -use crate::{ - client::Client, - error::{Error, Result}, - SecretManager, -}; +use crate::{client::Client, error::Error, SecretManager}; #[pyclass] pub struct Wallet { @@ -33,7 +29,7 @@ pub fn destroy_wallet(wallet: &Wallet) -> PyResult<()> { /// Create wallet handler for python-side usage. #[pyfunction] -pub fn create_wallet(options: String) -> Result { +pub fn create_wallet(options: String) -> Result { let wallet_options = serde_json::from_str::(&options)?; let wallet = crate::block_on(async { wallet_options.build().await })?; @@ -44,7 +40,7 @@ pub fn create_wallet(options: String) -> Result { /// Call a wallet method. #[pyfunction] -pub fn call_wallet_method(wallet: &Wallet, method: String) -> Result { +pub fn call_wallet_method(wallet: &Wallet, method: String) -> Result { let method = serde_json::from_str::(&method)?; let response = crate::block_on(async { match wallet.wallet.read().await.as_ref() { @@ -91,7 +87,7 @@ pub fn listen_wallet(wallet: &Wallet, events: Vec, handler: PyObject) { /// Get the client from the wallet. #[pyfunction] -pub fn get_client_from_wallet(wallet: &Wallet) -> Result { +pub fn get_client_from_wallet(wallet: &Wallet) -> Result { let client = crate::block_on(async { wallet .wallet @@ -113,14 +109,14 @@ pub fn get_client_from_wallet(wallet: &Wallet) -> Result { /// Get the secret manager from the wallet. #[pyfunction] -pub fn get_secret_manager_from_wallet(wallet: &Wallet) -> Result { +pub fn get_secret_manager_from_wallet(wallet: &Wallet) -> Result { let secret_manager = crate::block_on(async { wallet .wallet .read() .await .as_ref() - .map(|w| w.get_secret_manager().clone()) + .map(|w| w.secret_manager().clone()) .ok_or_else(|| { Error::from( serde_json::to_string(&Response::Panic("wallet was destroyed".into())) diff --git a/bindings/python/tests/test_api_responses.py b/bindings/python/tests/test_api_responses.py new file mode 100644 index 0000000000..785f777667 --- /dev/null +++ b/bindings/python/tests/test_api_responses.py @@ -0,0 +1,87 @@ +# Copyright 2024 IOTA Stiftung +# SPDX-License-Identifier: Apache-2.0 + +from typing import Generic, TypeVar +from json import load, loads, dumps +from iota_sdk import RoutesResponse, CongestionResponse, OutputWithMetadataResponse, ManaRewardsResponse, ValidatorsResponse, ValidatorResponse, CommitteeResponse, IssuanceBlockHeaderResponse, Block, BlockMetadataResponse, BlockWithMetadataResponse, OutputMetadata, OutputResponse, TransactionMetadataResponse, SlotCommitment, UtxoChangesResponse, UtxoChangesFullResponse + + +base_path = '../../sdk/tests/types/api/fixtures/' +T = TypeVar("T") + + +def test_api_responses(): + def test_api_response(cls_type: Generic[T], path: str): + fixture_str = '' + with open(base_path + path, "r", encoding="utf-8") as payload: + fixture = load(payload) + cls = cls_type.from_dict(fixture) + + # We need to sort the keys because optional fields in classes must be + # last in Python + fixture_str = dumps(fixture, sort_keys=True) + recreated = dumps( + loads(cls.to_json()), sort_keys=True) + assert fixture_str == recreated + + # GET /api/routes + test_api_response(RoutesResponse, "get-routes-response-example.json") + # GET /api/core/v3/info + # TODO reenable when Metrics are split out of Info + # test_api_response(InfoResponse, "get-info-response-example.json") + # GET /api/core/v3/accounts/{bech32Address}/congestion + test_api_response(CongestionResponse, + "get-congestion-estimate-response-example.json") + # GET /api/core/v3/rewards/{outputId} + test_api_response(ManaRewardsResponse, "get-mana-rewards-example.json") + # GET /api/core/v3/validators + test_api_response(ValidatorsResponse, "get-validators-example.json") + # GET /api/core/v3/validators/{bech32Address} + test_api_response(ValidatorResponse, "get-validator-example.json") + # GET /api/core/v3/committee + test_api_response(CommitteeResponse, "get-committee-example.json") + # GET /api/core/v3/blocks/issuance + test_api_response(IssuanceBlockHeaderResponse, + "get-buildingBlock-response-example.json") + # GET /api/core/v3/blocks/{blockId} + test_api_response(Block, "get-block-by-id-empty-response-example.json") + test_api_response(Block, "tagged-data-block-example.json") + test_api_response(Block, "transaction-block-example.json") + test_api_response( + Block, "get-block-by-id-validation-response-example.json") + # GET /api/core/v3/blocks/{blockId}/metadata + test_api_response(BlockMetadataResponse, + "get-block-by-id-response-example-new-transaction.json") + test_api_response(BlockMetadataResponse, + "get-block-by-id-response-example-new.json") + test_api_response(BlockMetadataResponse, + "get-block-by-id-response-example-confirmed-transaction.json") + test_api_response(BlockMetadataResponse, + "get-block-by-id-response-example-confirmed.json") + test_api_response(BlockMetadataResponse, + "get-block-by-id-response-example-conflicting-transaction.json") + # GET /api/core/v3/blocks/{blockId}/full + test_api_response(BlockWithMetadataResponse, + "get-full-block-by-id-tagged-data-response-example.json") + # GET /api/core/v3/outputs/{outputId} + test_api_response( + OutputResponse, "get-outputs-by-id-response-example.json") + # GET /api/core/v3/outputs/{outputId}/metadata + test_api_response( + OutputMetadata, "get-output-metadata-by-id-response-unspent-example.json") + test_api_response( + OutputMetadata, "get-output-metadata-by-id-response-spent-example.json") + # GET /api/core/v3/outputs/{outputId}/full + test_api_response(OutputWithMetadataResponse, + "get-full-output-metadata-example.json") + # GET /api/core/v3/transactions/{transactionId}/metadata + test_api_response(TransactionMetadataResponse, + "get-transaction-metadata-by-id-response-example.json") + # GET /api/core/v3/commitments/{commitmentId} + test_api_response(SlotCommitment, "get-commitment-response-example.json") + # GET /api/core/v3/commitments/{commitmentId}/utxo-changes + test_api_response(UtxoChangesResponse, + "get-utxo-changes-response-example.json") + # GET /api/core/v3/commitments/{commitmentId}/utxo-changes/full + test_api_response(UtxoChangesFullResponse, + "get-utxo-changes-full-response-example.json") diff --git a/bindings/python/tests/test_offline.py b/bindings/python/tests/test_offline.py index 536ee757da..b1c393f08e 100644 --- a/bindings/python/tests/test_offline.py +++ b/bindings/python/tests/test_offline.py @@ -11,7 +11,7 @@ with open('../../sdk/tests/client/fixtures/test_vectors.json', "r", encoding="utf-8") as json_file: tv = json.load(json_file) -client = Client() +client = Client(protocol_parameters=Utils.iota_mainnet_protocol_parameters()) def test_mnemonic_address_generation(): @@ -60,35 +60,43 @@ def test_output_id(self): transaction_id = TransactionId( '0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64900000000') output_index = 42 - output_id = OutputId(transaction_id, output_index) - assert repr( - output_id) == '0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649000000002a00' + output_id = OutputId.from_transaction_id_and_output_index( + transaction_id, output_index) + assert str(output_id + ) == "0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649000000002a00" - new_output_id = OutputId.from_string( + new_output_id = OutputId( '0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649000000002a00') - assert repr( - new_output_id) == '0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649000000002a00' - assert new_output_id.transaction_id == transaction_id - assert new_output_id.output_index == output_index + assert str(new_output_id + ) == '0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649000000002a00' + assert new_output_id.transaction_id() == transaction_id + assert new_output_id.output_index() == output_index + + output_id_invalid_hex_char = '0xz2fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649000000002a00' + with self.assertRaises(ValueError): + OutputId(output_id_invalid_hex_char) transaction_id_missing_0x_prefix = '52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64900000000' with self.assertRaises(ValueError): - OutputId(transaction_id_missing_0x_prefix, output_index) + OutputId.from_transaction_id_and_output_index( + transaction_id_missing_0x_prefix, output_index) transaction_id__invalid_hex_prefix = '0052fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64900000000' with self.assertRaises(ValueError): - OutputId(transaction_id__invalid_hex_prefix, output_index) + OutputId.from_transaction_id_and_output_index( + transaction_id__invalid_hex_prefix, output_index) transaction_id_invalid_hex_char = '0xz2fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64900000000' with self.assertRaises(ValueError): - OutputId(transaction_id_invalid_hex_char, output_index) + OutputId.from_transaction_id_and_output_index( + transaction_id_invalid_hex_char, output_index) output_id_missing_0x_prefix = '52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c6492a000000000000' with self.assertRaises(ValueError): - OutputId.from_string(output_id_missing_0x_prefix) + OutputId(output_id_missing_0x_prefix) output_id_invalid_hex_char = '0xz2fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c6492a000000000000' with self.assertRaises(ValueError): - OutputId.from_string(output_id_invalid_hex_char) + OutputId(output_id_invalid_hex_char) output_id_invalid_hex_prefix = '0052fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c6492a000000000000' with self.assertRaises(ValueError): - OutputId.from_string(output_id_invalid_hex_prefix) + OutputId(output_id_invalid_hex_prefix) def test_hex_utf8(): diff --git a/bindings/python/tests/test_protocol_parameters.py b/bindings/python/tests/test_protocol_parameters.py index b18e6f2e67..2450b86cf5 100644 --- a/bindings/python/tests/test_protocol_parameters.py +++ b/bindings/python/tests/test_protocol_parameters.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import json -# from iota_sdk import ProtocolParameters, Utils +from iota_sdk import ProtocolParameters, Utils protocol_params_json = {} @@ -10,10 +10,10 @@ protocol_params_json = json.load(json_file) -# def test_protocol_parameters(): -# protocol_params_dict = protocol_params_json['params'] -# protocol_params = ProtocolParameters.from_dict(protocol_params_dict) -# assert protocol_params.to_dict() == protocol_params_dict +def test_protocol_parameters(): + protocol_params_dict = protocol_params_json['params'] + protocol_params = ProtocolParameters.from_dict(protocol_params_dict) + assert protocol_params.to_dict() == protocol_params_dict -# expected_hash = protocol_params_json['hash'] -# assert Utils.protocol_parameters_hash(protocol_params) == expected_hash + expected_hash = protocol_params_json['hash'] + assert Utils.protocol_parameters_hash(protocol_params) == expected_hash diff --git a/bindings/python/tests/test_secret_manager.py b/bindings/python/tests/test_secret_manager.py new file mode 100644 index 0000000000..624430275e --- /dev/null +++ b/bindings/python/tests/test_secret_manager.py @@ -0,0 +1,24 @@ +# Copyright 2023 IOTA Stiftung +# SPDX-License-Identifier: Apache-2.0 + +from iota_sdk import MnemonicSecretManager, SecretManager, CoinType, Utils + + +def test_secret_manager_address_generation_iota(): + secret_manager = SecretManager(MnemonicSecretManager( + "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast")) + + bech32_hrp = Utils.iota_mainnet_protocol_parameters().bech32_hrp + address = secret_manager.generate_ed25519_address(CoinType.IOTA, bech32_hrp) + + assert 'iota1qpg2xkj66wwgn8p2ggnp7p582gj8g6p79us5hve2tsudzpsr2ap4skprwjg' == address + + +def test_secret_manager_address_generation_shimmer(): + secret_manager = SecretManager(MnemonicSecretManager( + "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast")) + + bech32_hrp = Utils.shimmer_mainnet_protocol_parameters().bech32_hrp + address = secret_manager.generate_ed25519_address(CoinType.SHIMMER, bech32_hrp) + + assert 'smr1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6ckj80y' == address diff --git a/bindings/python/tests/address_generation_test.py b/bindings/python/tests/test_wallet_address.py similarity index 73% rename from bindings/python/tests/address_generation_test.py rename to bindings/python/tests/test_wallet_address.py index 6c6f6fcbc7..847e4c1055 100644 --- a/bindings/python/tests/address_generation_test.py +++ b/bindings/python/tests/test_wallet_address.py @@ -2,14 +2,15 @@ # SPDX-License-Identifier: Apache-2.0 import shutil -from iota_sdk import Wallet, MnemonicSecretManager, CoinType, ClientOptions, WalletOptions, Bip44 +from iota_sdk import Wallet, MnemonicSecretManager, CoinType, ClientOptions, WalletOptions, Bip44, Utils -def test_address_generation_iota(): - db_path = './test_address_generation_iota' +def test_wallet_address_iota(): + db_path = './test_wallet_address_iota' shutil.rmtree(db_path, ignore_errors=True) - client_options = ClientOptions(nodes=[]) + client_options = ClientOptions( + nodes=[], protocol_parameters=Utils.iota_mainnet_protocol_parameters()) secret_manager = MnemonicSecretManager( "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast") @@ -28,15 +29,16 @@ def test_address_generation_iota(): address = wallet.address() - assert 'smr1qpg2xkj66wwgn8p2ggnp7p582gj8g6p79us5hve2tsudzpsr2ap4sp36wye' == address + assert 'iota1qpg2xkj66wwgn8p2ggnp7p582gj8g6p79us5hve2tsudzpsr2ap4skprwjg' == address shutil.rmtree(db_path, ignore_errors=True) -def test_address_generation_shimmer(): - db_path = './test_address_generation_shimmer' +def test_wallet_address_shimmer(): + db_path = './test_wallet_address_shimmer' shutil.rmtree(db_path, ignore_errors=True) - client_options = ClientOptions(nodes=[]) + client_options = ClientOptions( + nodes=[], protocol_parameters=Utils.shimmer_mainnet_protocol_parameters()) secret_manager = MnemonicSecretManager( "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast") diff --git a/bindings/python/tests/test_wallet_destroy.py b/bindings/python/tests/test_wallet_destroy.py index bd9e33d555..796b880b6c 100644 --- a/bindings/python/tests/test_wallet_destroy.py +++ b/bindings/python/tests/test_wallet_destroy.py @@ -3,7 +3,7 @@ import shutil import unittest -from iota_sdk import Wallet, MnemonicSecretManager, CoinType, ClientOptions, WalletOptions, WalletError, Bip44 +from iota_sdk import Wallet, MnemonicSecretManager, CoinType, ClientOptions, WalletOptions, WalletError, Bip44, Utils class WalletDestroy(unittest.TestCase): @@ -11,13 +11,14 @@ def test_wallet_destroy(self): db_path = './test_wallet_destroy' shutil.rmtree(db_path, ignore_errors=True) - client_options = ClientOptions(nodes=[]) + client_options = ClientOptions( + nodes=[], protocol_parameters=Utils.iota_mainnet_protocol_parameters()) secret_manager = MnemonicSecretManager( "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast") bip_path = Bip44( - coin_type=CoinType.SHIMMER + coin_type=CoinType.IOTA ) wallet_options = WalletOptions( None, @@ -29,7 +30,7 @@ def test_wallet_destroy(self): wallet = Wallet(wallet_options) address = wallet.address() - assert 'smr1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6ckj80y' == address + assert 'iota1qpg2xkj66wwgn8p2ggnp7p582gj8g6p79us5hve2tsudzpsr2ap4skprwjg' == address # Destroy the wallet wallet.destroy() @@ -38,14 +39,15 @@ def test_wallet_destroy(self): wallet = Wallet(wallet_options) address = wallet.address() - assert 'smr1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6ckj80y' == address + assert 'iota1qpg2xkj66wwgn8p2ggnp7p582gj8g6p79us5hve2tsudzpsr2ap4skprwjg' == address shutil.rmtree(db_path, ignore_errors=True) def test_wallet_destroy_error(self): db_path = './test_wallet_destroy_error' shutil.rmtree(db_path, ignore_errors=True) - client_options = ClientOptions(nodes=[]) + client_options = ClientOptions( + nodes=[], protocol_parameters=Utils.iota_mainnet_protocol_parameters()) secret_manager = MnemonicSecretManager( "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast") diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 0b5a97dee5..8a28e59389 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -28,7 +28,7 @@ js-sys = { version = "0.3.67", default-features = false, features = [] } log = { version = "0.4.20", default-features = false } serde_json = { version = "1.0.113", default-features = false } tokio = { version = "1.35.1", default-features = false, features = ["sync"] } -wasm-bindgen = { version = "0.2.90", default-features = false, features = [ +wasm-bindgen = { version = "0.2.92", default-features = false, features = [ "spans", "std", "serde-serialize", diff --git a/bindings/wasm/src/wallet.rs b/bindings/wasm/src/wallet.rs index 4643dd07c9..b23b1ef609 100644 --- a/bindings/wasm/src/wallet.rs +++ b/bindings/wasm/src/wallet.rs @@ -52,7 +52,7 @@ pub async fn get_client(method_handler: &WalletMethodHandler) -> Result Result { if let Some(wallet) = &*method_handler.0.read().await { - Ok(SecretManagerMethodHandler::new(wallet.get_secret_manager().clone())) + Ok(SecretManagerMethodHandler::new(wallet.secret_manager().clone())) } else { // Notify that the wallet was destroyed Err(destroyed_err("Wallet")) diff --git a/bindings/wasm/tests/utilityMethods.spec.ts b/bindings/wasm/tests/utilityMethods.spec.ts index 5c56aa27ce..fbcf791456 100644 --- a/bindings/wasm/tests/utilityMethods.spec.ts +++ b/bindings/wasm/tests/utilityMethods.spec.ts @@ -1,4 +1,4 @@ -import { Utils } from '../node/lib'; +import { AccountAddress, Ed25519Address, Utils } from '../node/lib'; describe('Utils methods', () => { it('generates and validates mnemonic', async () => { @@ -17,22 +17,22 @@ describe('Utils methods', () => { }); it('converts address to hex and bech32', async () => { - const address = + const bech32Address = 'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy'; - const hexAddress = Utils.bech32ToHex(address); + const address = Utils.parseBech32Address(bech32Address) as Ed25519Address; - expect(hexAddress.slice(0, 2)).toBe('0x'); + expect(address.pubKeyHash.slice(0, 2)).toBe('0x'); - const bech32Address = Utils.hexToBech32(hexAddress, 'rms'); + const convertedBech32Address = Utils.addressToBech32(address, 'rms'); - expect(bech32Address).toBe(address); + expect(convertedBech32Address).toBe(bech32Address); }); it('converts hex public key to bech32 address', async () => { const hexPublicKey = '0x2baaf3bca8ace9f862e60184bd3e79df25ff230f7eaaa4c7f03daa9833ba854a'; - const address = Utils.hexPublicKeyToBech32Address(hexPublicKey, 'rms'); + const address = Utils.addressToBech32(Utils.publicKeyHash(hexPublicKey), 'rms'); expect(address).toBe( 'rms1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx4aaacx', @@ -63,7 +63,7 @@ describe('Utils methods', () => { const accountId = '0x0ebc2867a240719a70faacdfc3840e857fa450b37d95297ac4f166c2f70c3345'; - const accountAddress = Utils.accountIdToBech32(accountId, 'rms'); + const accountAddress = Utils.addressToBech32(new AccountAddress(accountId), 'rms'); expect(accountAddress).toBe( 'rms1pq8tc2r85fq8rxnsl2kdlsuyp6zhlfzskd7e22t6cnckdshhpse52a27mlc', diff --git a/bindings/wasm/tests/wallet.spec.ts b/bindings/wasm/tests/wallet.spec.ts index 26fa8454b2..45eae39f1c 100644 --- a/bindings/wasm/tests/wallet.spec.ts +++ b/bindings/wasm/tests/wallet.spec.ts @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Wallet, CoinType, SecretManager } from '../node/lib'; +import { Wallet, CoinType, SecretManager, Utils } from '../node/lib'; describe('wallet tests', () => { jest.setTimeout(100000); @@ -30,6 +30,7 @@ describe('wallet tests', () => { }, clientOptions: { nodes: ['http://localhost:8050'], + protocolParameters: Utils.iotaMainnetProtocolParameters(), }, secretManager: mnemonicSecretManager, }); diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9714463c52..9d96b580fe 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -41,12 +41,13 @@ dialoguer = { version = "0.11.0", default-features = false, features = [ "password", ] } dotenvy = { version = "0.15.7", default-features = false } +eyre = { version = "0.6.12", default-features = false, features = ["auto-install"] } fern-logger = { version = "0.5.0", default-features = false } log = { version = "0.4.20", default-features = false } prefix-hex = { version = "0.7.1", default-features = false, features = ["std"] } rustyline = { version = "13.0.0", features = ["derive"] } serde_json = { version = "1.0.113", default-features = false } -strum = { version = "0.25.0", default-features = false, features = ["derive"] } +strum = { version = "0.26.1", default-features = false, features = ["derive"] } thiserror = { version = "1.0.56", default-features = false } tokio = { version = "1.35.1", default-features = false, features = ["fs"] } zeroize = { version = "1.7.0", default-features = false } diff --git a/cli/src/cli.rs b/cli/src/cli.rs index c4a5f6c8c2..d6f247346c 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -4,6 +4,7 @@ use std::{path::Path, str::FromStr}; use clap::{builder::BoolishValueParser, Args, CommandFactory, Parser, Subcommand}; +use eyre::{bail, Error}; use iota_sdk::{ client::{ constants::SHIMMER_COIN_TYPE, @@ -18,7 +19,6 @@ use iota_sdk::{ use log::LevelFilter; use crate::{ - error::Error, helper::{ check_file_exists, enter_or_generate_mnemonic, generate_mnemonic, get_alias, get_decision, get_password, import_mnemonic, select_secret_manager, SecretManagerChoice, @@ -115,7 +115,7 @@ fn parse_bip_path(arg: &str) -> Result { #[derive(Debug, Clone, Subcommand)] pub enum CliCommand { - /// Create a stronghold backup file. + /// Create a backup file. Currently only Stronghold backup is supported. Backup { /// Path of the created stronghold backup file. backup_path: String, @@ -140,7 +140,7 @@ pub enum CliCommand { }, /// Get information about currently set node. NodeInfo, - /// Restore a stronghold backup file. + /// Restore a backup file. Currently only Stronghold backup is supported. Restore { /// Path of the to be restored stronghold backup file. backup_path: String, @@ -169,7 +169,7 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { if storage_path.is_dir() { match Wallet::builder().with_storage_path(storage_path).finish().await { Ok(wallet) => { - let linked_secret_manager = match &mut *wallet.get_secret_manager().write().await { + let linked_secret_manager = match &mut *wallet.secret_manager().write().await { SecretManager::Stronghold(stronghold) => { let snapshot_path = stronghold.snapshot_path().to_path_buf(); let snapshot_exists = snapshot_path.exists(); @@ -202,14 +202,11 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { snapshot_exists: true, .. } => { let password = get_password("Stronghold password", false)?; - backup_command_stronghold(&wallet, &password, Path::new(&backup_path)).await?; + backup_to_stronghold_snapshot_command(&wallet, &password, Path::new(&backup_path)).await?; return Ok(None); } LinkedSecretManager::Stronghold { snapshot_path, .. } => { - return Err(Error::Miscellaneous(format!( - "Stronghold snapshot does not exist at '{}'", - snapshot_path.display() - ))); + bail!("Stronghold snapshot does not exist at '{}'", snapshot_path.display()); } _ => { println_log_info!("only Stronghold backup supported"); @@ -217,10 +214,7 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { } } } else { - return Err(Error::Miscellaneous(format!( - "wallet db does not exist at '{}'", - storage_path.display() - ))); + bail!("wallet db does not exist at '{}'", storage_path.display()); } } CliCommand::ChangePassword => { @@ -234,10 +228,7 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { Some(wallet) } LinkedSecretManager::Stronghold { snapshot_path, .. } => { - return Err(Error::Miscellaneous(format!( - "Stronghold snapshot does not exist at '{}'", - snapshot_path.display() - ))); + bail!("Stronghold snapshot does not exist at '{}'", snapshot_path.display()); } _ => { println_log_info!("only Stronghold password change supported"); @@ -245,18 +236,15 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { } } } else { - return Err(Error::Miscellaneous(format!( - "wallet db does not exist at '{}'", - storage_path.display() - ))); + bail!("wallet db does not exist at '{}'", storage_path.display()); } } CliCommand::Init(init_parameters) => { if wallet_and_secret_manager.is_some() { - return Err(Error::Miscellaneous(format!( + bail!( "cannot initialize: wallet db at '{}' already exists", storage_path.display() - ))); + ); } let secret_manager = create_secret_manager(&init_parameters).await?; let secret_manager_variant = secret_manager.to_string(); @@ -278,13 +266,10 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { } CliCommand::NodeInfo => { if let Some((wallet, _)) = wallet_and_secret_manager { - node_info_command(&wallet).await?; + crate::wallet_cli::node_info_command(&wallet).await?; return Ok(None); } else { - return Err(Error::Miscellaneous(format!( - "wallet db does not exist at '{}'", - storage_path.display() - ))); + bail!("wallet db does not exist at '{}'", storage_path.display()); } } CliCommand::Restore { backup_path } => { @@ -294,7 +279,7 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { // we need to explicitly drop the current wallet here to prevent: // "error accessing storage: IO error: lock hold by current process" drop(wallet); - let wallet = restore_command_stronghold( + let wallet = restore_from_stronghold_snapshot_command( storage_path, snapshot_path.as_path(), Path::new(&backup_path), @@ -312,7 +297,8 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { let init_params = InitParameters::default(); let snapshot_path = Path::new(&init_params.stronghold_snapshot_path); let wallet = - restore_command_stronghold(storage_path, snapshot_path, Path::new(&backup_path)).await?; + restore_from_stronghold_snapshot_command(storage_path, snapshot_path, Path::new(&backup_path)) + .await?; Some(wallet) } } @@ -321,10 +307,7 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { set_node_url_command(&wallet, url).await?; Some(wallet) } else { - return Err(Error::Miscellaneous(format!( - "wallet db does not exist at '{}'", - storage_path.display() - ))); + bail!("wallet db does not exist at '{}'", storage_path.display()); } } CliCommand::Sync => { @@ -332,10 +315,7 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { sync_command(&wallet).await?; Some(wallet) } else { - return Err(Error::Miscellaneous(format!( - "wallet db does not exist at '{}'", - storage_path.display() - ))); + bail!("wallet db does not exist at '{}'", storage_path.display()); } } } @@ -382,8 +362,14 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { }) } -pub async fn backup_command_stronghold(wallet: &Wallet, password: &Password, backup_path: &Path) -> Result<(), Error> { - wallet.backup(backup_path.into(), password.clone()).await?; +pub async fn backup_to_stronghold_snapshot_command( + wallet: &Wallet, + password: &Password, + backup_path: &Path, +) -> Result<(), Error> { + wallet + .backup_to_stronghold_snapshot(backup_path.into(), password.clone()) + .await?; println_log_info!("Wallet has been backed up to \"{}\".", backup_path.display()); @@ -411,17 +397,18 @@ pub async fn init_command( } else { None }; - let address = init_params - .address - .map(|addr| Bech32Address::from_str(&addr)) - .transpose()?; Ok(Wallet::builder() .with_secret_manager(secret_manager) .with_client_options(ClientOptions::new().with_node(init_params.node_url.as_str())?) .with_storage_path(storage_path.to_str().expect("invalid unicode")) + .with_address( + init_params + .address + .map(|addr| Bech32Address::from_str(&addr)) + .transpose()?, + ) .with_bip_path(init_params.bip_path) - .with_address(address) .with_alias(alias) .finish() .await?) @@ -444,15 +431,7 @@ pub async fn mnemonic_command(output_file_name: Option, output_stdout: O Ok(()) } -pub async fn node_info_command(wallet: &Wallet) -> Result<(), Error> { - let node_info = serde_json::to_string_pretty(&wallet.client().get_info().await?)?; - - println_log_info!("Current node info: {node_info}"); - - Ok(()) -} - -pub async fn restore_command_stronghold( +pub async fn restore_from_stronghold_snapshot_command( storage_path: &Path, snapshot_path: &Path, backup_path: &Path, @@ -494,10 +473,13 @@ pub async fn restore_command_stronghold( .await?; let password = get_password("Stronghold backup password", false)?; - if let Err(e) = wallet.restore_backup(backup_path.into(), password, None, None).await { + if let Err(e) = wallet + .restore_from_stronghold_snapshot(backup_path.into(), password, None, None) + .await + { // Clean up the file system after a failed restore (typically produces a wallet without a secret manager). // TODO: a better way would be to not create any files/dirs in the first place when it's not clear yet whether - // the restore will be successful. + // the restore will be successful. https://github.com/iotaledger/iota-sdk/issues/2018 if storage_path.is_dir() && !restore_into_existing_wallet { std::fs::remove_dir_all(storage_path)?; } @@ -537,10 +519,7 @@ async fn create_secret_manager(init_params: &InitParameters) -> Result), - #[error("dialoguer error: {0}")] - Dialoguer(#[from] DialoguerError), - #[error("io error: {0}")] - Io(#[from] std::io::Error), - #[error("logger error: {0}")] - Logger(#[from] LoggerError), - #[error("{0}")] - Miscellaneous(String), - #[error("prompt error: {0}")] - Prompt(#[from] ReadlineError), - #[error("serde_json error: {0}")] - SerdeJson(#[from] SerdeJsonError), - #[error("wallet error: {0}")] - Wallet(#[from] WalletError), -} - -impl From for Error { - fn from(error: ClientError) -> Self { - Self::Client(Box::new(error)) - } -} - -impl From for Error { - fn from(error: iota_sdk::client::stronghold::Error) -> Self { - Self::Client(Box::new(iota_sdk::client::Error::Stronghold(error))) - } -} diff --git a/cli/src/helper.rs b/cli/src/helper.rs index c7a321d66d..75f179f46f 100644 --- a/cli/src/helper.rs +++ b/cli/src/helper.rs @@ -6,6 +6,7 @@ use std::path::Path; use chrono::{DateTime, NaiveDateTime, Utc}; use dialoguer::{console::Term, theme::ColorfulTheme, Input, Select}; +use eyre::{bail, eyre, Error}; use iota_sdk::{ client::{utils::Password, verify_mnemonic}, crypto::keys::bip39::Mnemonic, @@ -16,7 +17,7 @@ use tokio::{ }; use zeroize::Zeroize; -use crate::{error::Error, println_log_error, println_log_info}; +use crate::{println_log_error, println_log_info}; const DEFAULT_MNEMONIC_FILE_PATH: &str = "./mnemonic.txt"; @@ -62,7 +63,7 @@ pub async fn get_alias(prompt: &str) -> Result { pub async fn bytes_from_hex_or_file(hex: Option, file: Option) -> Result>, Error> { Ok(if let Some(hex) = hex { - Some(prefix_hex::decode(hex).map_err(|e| Error::Miscellaneous(e.to_string()))?) + Some(prefix_hex::decode(hex)?) } else if let Some(file) = file { Some(tokio::fs::read(file).await?) } else { @@ -156,7 +157,7 @@ pub async fn import_mnemonic(path: &str) -> Result { let mut mnemonics = read_mnemonics_from_file(path).await?; if mnemonics.is_empty() { println_log_error!("No valid mnemonics found in '{path}'."); - Err(Error::Miscellaneous("No valid mnemonics found".to_string())) + bail!("No valid mnemonics found".to_string()) } else if mnemonics.len() == 1 { Ok(mnemonics.swap_remove(0)) } else { @@ -278,9 +279,7 @@ async fn read_mnemonics_from_file(path: &str) -> Result, Error> { if verify_mnemonic(&*trimmed).is_ok() { mnemonics.push(trimmed); } else { - return Err(Error::Miscellaneous(format!( - "Invalid mnemonic in file '{path}' at line '{line_index}'." - ))); + bail!("Invalid mnemonic in file '{path}' at line '{line_index}'."); } line_index += 1; } @@ -293,29 +292,21 @@ pub fn to_utc_date_time(ts_millis: u128) -> Result, Error> { let millis = ts_millis % 1000; let secs = (ts_millis - millis) / 1000; - let secs_int = - i64::try_from(secs).map_err(|e| Error::Miscellaneous(format!("Failed to convert timestamp to i64: {e}")))?; - let nanos = u32::try_from(millis * 1000000) - .map_err(|e| Error::Miscellaneous(format!("Failed to convert timestamp to u32: {e}")))?; + let secs_int = i64::try_from(secs).map_err(|e| eyre!("Failed to convert timestamp to i64: {e}"))?; + let nanos = u32::try_from(millis * 1000000).map_err(|e| eyre!("Failed to convert timestamp to u32: {e}"))?; - let naive_time = NaiveDateTime::from_timestamp_opt(secs_int, nanos).ok_or(Error::Miscellaneous( - "Failed to convert timestamp to NaiveDateTime".to_string(), - ))?; + let naive_time = NaiveDateTime::from_timestamp_opt(secs_int, nanos) + .ok_or(eyre!("Failed to convert timestamp to NaiveDateTime"))?; Ok(naive_time.and_utc()) } pub async fn check_file_exists(path: &Path) -> Result<(), Error> { - if !fs::try_exists(path).await.map_err(|e| { - Error::Miscellaneous(format!( - "Error while accessing the file '{path}': '{e}'", - path = path.display() - )) - })? { - return Err(Error::Miscellaneous(format!( - "File '{path}' does not exist.", - path = path.display() - ))); + if !fs::try_exists(path) + .await + .map_err(|e| eyre!("Error while accessing the file '{path}': '{e}'", path = path.display()))? + { + bail!("File '{path}' does not exist.", path = path.display()); } Ok(()) } diff --git a/cli/src/main.rs b/cli/src/main.rs index 5c7c29449c..83958904e6 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,18 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 mod cli; -mod error; mod helper; mod wallet_cli; use clap::Parser; +use eyre::Error; use fern_logger::{LoggerConfigBuilder, LoggerOutputConfigBuilder}; use log::LevelFilter; -use self::{ - cli::{new_wallet, Cli}, - error::Error, -}; +use self::cli::{new_wallet, Cli}; #[macro_export] macro_rules! println_log_info { diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 49fdc8853c..b6c0f3aa06 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -7,8 +7,9 @@ use std::str::FromStr; use clap::{CommandFactory, Parser, Subcommand}; use colored::Colorize; +use eyre::Error; use iota_sdk::{ - client::{request_funds_from_faucet, secret::SecretManager}, + client::{api::options::TransactionOptions, request_funds_from_faucet, secret::SecretManager}, types::block::{ address::{AccountAddress, Bech32Address, ToBech32Ext}, mana::ManaAllotment, @@ -20,12 +21,13 @@ use iota_sdk::{ }, payload::signed_transaction::TransactionId, slot::SlotIndex, + IdentifierError, }, utils::ConvertTo, wallet::{ types::OutputData, BeginStakingParams, ConsolidationParams, CreateDelegationParams, CreateNativeTokenParams, - Error as WalletError, MintNftParams, OutputsToClaim, SendNativeTokenParams, SendNftParams, SendParams, - SyncOptions, TransactionOptions, Wallet, + MintNftParams, OutputsToClaim, ReturnStrategy, SendManaParams, SendNativeTokenParams, SendNftParams, + SendParams, SyncOptions, Wallet, WalletError, }, U256, }; @@ -33,7 +35,6 @@ use rustyline::{error::ReadlineError, history::MemHistory, Config, Editor}; use self::completer::WalletCommandHelper; use crate::{ - error::Error, helper::{bytes_from_hex_or_file, get_password, to_utc_date_time}, println_log_error, println_log_info, }; @@ -53,7 +54,7 @@ impl WalletCli { } /// Commands -#[derive(Debug, Subcommand, strum::EnumVariantNames)] +#[derive(Debug, Subcommand, strum::VariantNames)] #[strum(serialize_all = "kebab-case")] #[allow(clippy::large_enum_variant)] pub enum WalletCommand { @@ -272,6 +273,16 @@ pub enum WalletCommand { #[arg(long, default_value_t = false)] allow_micro_amount: bool, }, + /// Send mana. + SendMana { + /// Recipient address, e.g. rms1qztwng6cty8cfm42nzvq099ev7udhrnk0rw8jt8vttf9kpqnxhpsx869vr3. + address: Bech32Address, + /// Amount of mana to send, e.g. 1000000. + mana: u64, + /// Whether to gift the storage deposit or not. + #[arg(short, long, default_value_t = false)] + gift: bool, + }, /// Send a native token. /// This will create an output with an expiration and storage deposit return unlock condition. SendNativeToken { @@ -348,7 +359,7 @@ pub enum WalletCommand { } fn parse_u256(s: &str) -> Result { - U256::from_dec_str(s).map_err(|e| Error::Miscellaneous(e.to_string())) + Ok(U256::from_dec_str(s)?) } /// Select by transaction ID or list index @@ -359,7 +370,7 @@ pub enum TransactionSelector { } impl FromStr for TransactionSelector { - type Err = Error; + type Err = IdentifierError; fn from_str(s: &str) -> Result { Ok(if let Ok(index) = s.parse() { @@ -378,7 +389,7 @@ pub enum OutputSelector { } impl FromStr for OutputSelector { - type Err = Error; + type Err = IdentifierError; fn from_str(s: &str) -> Result { Ok(if let Ok(index) = s.parse() { @@ -391,8 +402,8 @@ impl FromStr for OutputSelector { // `accounts` command pub async fn accounts_command(wallet: &Wallet) -> Result<(), Error> { - let wallet_data = wallet.data().await; - let accounts = wallet_data.accounts(); + let wallet_ledger = wallet.ledger().await; + let accounts = wallet_ledger.accounts(); let hrp = wallet.client().get_bech32_hrp().await?; println_log_info!("Accounts:\n"); @@ -430,9 +441,9 @@ pub async fn address_command(wallet: &Wallet) -> Result<(), Error> { // `allot-mana` command pub async fn allot_mana_command(wallet: &Wallet, mana: u64, account_id: Option) -> Result<(), Error> { let account_id = { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; account_id - .or_else(|| wallet_data.first_account_id()) + .or_else(|| wallet_ledger.first_account_id()) .ok_or(WalletError::AccountNotFound)? }; @@ -565,9 +576,9 @@ pub async fn claim_command(wallet: &Wallet, output_id: Option) -> Resu /// `claimable-outputs` command pub async fn claimable_outputs_command(wallet: &Wallet) -> Result<(), Error> { for output_id in wallet.claimable_outputs(OutputsToClaim::All).await? { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; // Unwrap: for the iterated `OutputId`s this call will always return `Some(...)`. - let output = &wallet_data.get_output(&output_id).unwrap().output; + let output = &wallet_ledger.get_output(&output_id).unwrap().output; let kind = match output { Output::Nft(_) => "Nft", Output::Basic(_) => "Basic", @@ -613,9 +624,9 @@ pub async fn congestion_command( work_score: Option, ) -> Result<(), Error> { let account_id = { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; account_id - .or_else(|| wallet_data.first_account_id()) + .or_else(|| wallet_ledger.first_account_id()) .ok_or(WalletError::AccountNotFound)? }; @@ -796,7 +807,7 @@ pub async fn destroy_foundry_command(wallet: &Wallet, foundry_id: FoundryId) -> pub async fn end_staking_command(wallet: &Wallet, account_id: AccountId) -> Result<(), Error> { println_log_info!("Ending staking for {account_id}."); - let transaction = wallet.end_staking(account_id).await?; + let transaction = wallet.end_staking(account_id, None).await?; println_log_info!( "End staking transaction sent:\n{:?}\n{:?}", @@ -815,7 +826,7 @@ pub async fn extend_staking_command( ) -> Result<(), Error> { println_log_info!("Extending staking for {account_id} by {additional_epochs} epochs."); - let transaction = wallet.extend_staking(account_id, additional_epochs).await?; + let transaction = wallet.extend_staking(account_id, additional_epochs, None).await?; println_log_info!( "Extend staking transaction sent:\n{:?}\n{:?}", @@ -868,8 +879,8 @@ pub async fn implicit_account_transition_command(wallet: &Wallet, output_id: Out // `implicit-accounts` command pub async fn implicit_accounts_command(wallet: &Wallet) -> Result<(), Error> { - let wallet_data = wallet.data().await; - let implicit_accounts = wallet_data.implicit_accounts(); + let wallet_ledger = wallet.ledger().await; + let implicit_accounts = wallet_ledger.implicit_accounts(); let hrp = wallet.client().get_bech32_hrp().await?; println_log_info!("Implicit accounts:\n"); @@ -934,7 +945,7 @@ pub async fn mint_nft_command( issuer: Option, ) -> Result<(), Error> { let tag = if let Some(hex) = tag { - Some(prefix_hex::decode(hex).map_err(|e| Error::Miscellaneous(e.to_string()))?) + Some(prefix_hex::decode(hex)?) } else { None }; @@ -965,7 +976,7 @@ pub async fn mint_nft_command( // `node-info` command pub async fn node_info_command(wallet: &Wallet) -> Result<(), Error> { - let node_info = serde_json::to_string_pretty(&wallet.client().get_info().await?)?; + let node_info = serde_json::to_string_pretty(&wallet.client().get_node_info().await?)?; println_log_info!("Current node info: {node_info}"); @@ -974,11 +985,11 @@ pub async fn node_info_command(wallet: &Wallet) -> Result<(), Error> { /// `output` command pub async fn output_command(wallet: &Wallet, selector: OutputSelector, metadata: bool) -> Result<(), Error> { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; let output = match selector { - OutputSelector::Id(id) => wallet_data.get_output(&id), + OutputSelector::Id(id) => wallet_ledger.get_output(&id), OutputSelector::Index(index) => { - let mut outputs = wallet_data.outputs().values().collect::>(); + let mut outputs = wallet_ledger.outputs().values().collect::>(); outputs.sort_unstable_by_key(|o| o.output_id); outputs.into_iter().nth(index) } @@ -999,7 +1010,7 @@ pub async fn output_command(wallet: &Wallet, selector: OutputSelector, metadata: /// `outputs` command pub async fn outputs_command(wallet: &Wallet) -> Result<(), Error> { - print_outputs(wallet.data().await.outputs().values().cloned().collect(), "Outputs:") + print_outputs(wallet.ledger().await.outputs().values().cloned().collect(), "Outputs:") } // `send` command @@ -1033,6 +1044,29 @@ pub async fn send_command( Ok(()) } +// `send-mana` command +pub async fn send_mana_command( + wallet: &Wallet, + address: impl ConvertTo, + mana: u64, + gift: bool, +) -> Result<(), Error> { + let params = SendManaParams::new(mana, address.convert()?).with_return_strategy(if gift { + ReturnStrategy::Gift + } else { + ReturnStrategy::Return + }); + let transaction = wallet.send_mana(params, None).await?; + + println_log_info!( + "Transaction sent:\n{:?}\n{:?}", + transaction.transaction_id, + transaction.block_id + ); + + Ok(()) +} + // `send-native-token` command pub async fn send_native_token_command( wallet: &Wallet, @@ -1104,11 +1138,11 @@ pub async fn sync_command(wallet: &Wallet) -> Result<(), Error> { /// `transaction` command pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) -> Result<(), Error> { - let wallet_data = wallet.data().await; + let wallet_ledger = wallet.ledger().await; let transaction = match selector { - TransactionSelector::Id(id) => wallet_data.get_transaction(&id), + TransactionSelector::Id(id) => wallet_ledger.get_transaction(&id), TransactionSelector::Index(index) => { - let mut transactions = wallet_data.transactions().values().collect::>(); + let mut transactions = wallet_ledger.transactions().values().collect::>(); transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); transactions.into_iter().nth(index) } @@ -1125,8 +1159,8 @@ pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) /// `transactions` command pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result<(), Error> { - let wallet_data = wallet.data().await; - let mut transactions = wallet_data.transactions().values().collect::>(); + let wallet_ledger = wallet.ledger().await; + let mut transactions = wallet_ledger.transactions().values().collect::>(); transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); if transactions.is_empty() { @@ -1150,7 +1184,7 @@ pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result /// `unspent-outputs` command pub async fn unspent_outputs_command(wallet: &Wallet) -> Result<(), Error> { print_outputs( - wallet.data().await.unspent_outputs().values().cloned().collect(), + wallet.ledger().await.unspent_outputs().values().cloned().collect(), "Unspent outputs:", ) } @@ -1253,7 +1287,7 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> { let mut delegations = Vec::new(); let mut anchors = Vec::new(); - for output_data in wallet.data().await.unspent_outputs().values() { + for output_data in wallet.ledger().await.unspent_outputs().values() { let output_id = output_data.output_id; output_ids.push(output_id); @@ -1343,7 +1377,7 @@ pub enum PromptResponse { } async fn ensure_password(wallet: &Wallet) -> Result<(), Error> { - if matches!(*wallet.get_secret_manager().read().await, SecretManager::Stronghold(_)) + if matches!(*wallet.secret_manager().read().await, SecretManager::Stronghold(_)) && !wallet.is_stronghold_password_available().await? { let password = get_password("Stronghold password", false)?; @@ -1557,6 +1591,10 @@ pub async fn prompt_internal( }; send_command(wallet, address, amount, return_address, expiration, allow_micro_amount).await } + WalletCommand::SendMana { address, mana, gift } => { + ensure_password(wallet).await?; + send_mana_command(wallet, address, mana, gift).await + } WalletCommand::SendNativeToken { address, token_id, diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index eed1148c0c..e66e4a34d1 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -18,9 +18,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] # Mandatory dependencies -bech32 = { version = "0.10.0-beta", default-features = false, features = [ - "alloc", -] } +bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] } bitflags = { version = "2.4.2", default-features = false } derive_more = { version = "0.99.17", default-features = false, features = [ "from", @@ -46,7 +44,7 @@ iota-crypto = { version = "0.23.1", default-features = false, features = [ "secp256k1", ] } iterator-sorted = { version = "0.2.0", default-features = false } -packable = { version = "0.10.1", default-features = false, features = [ +packable = { version = "0.11.0", default-features = false, features = [ "primitive-types", ] } paste = { version = "1.0.14", default-features = false } @@ -69,7 +67,9 @@ futures = { version = "0.3.30", default-features = false, features = [ "thread-pool", ], optional = true } instant = { version = "0.1.12", default-features = false, optional = true } -iota-ledger-nano = { version = "1.0.4-alpha.3", default-features = false, features = ["nova"], optional = true } +iota-ledger-nano = { version = "1.0.4-alpha.3", default-features = false, features = [ + "nova", +], optional = true } iota_stronghold = { version = "2.0.0", default-features = false, optional = true } log = { version = "0.4.20", default-features = false, optional = true } once_cell = { version = "1.19.0", default-features = false, optional = true } @@ -154,6 +154,7 @@ irc_30 = ["serde", "dep:url"] ledger_nano = ["dep:iota-ledger-nano"] mqtt = ["std", "tls", "dep:regex", "dep:rumqttc", "dep:once_cell"] participation = ["storage"] +protocol_parameters_samples = ["std", "dep:time"] rocksdb = ["storage", "dep:rocksdb"] serde = [ "hashbrown/serde", @@ -405,6 +406,18 @@ name = "block_tagged_data" path = "examples/client/block/04_block_tagged_data.rs" required-features = ["client"] +# High Level examples + +[[example]] +name = "get_transaction_inputs" +path = "examples/client/high_level/get_transaction_inputs.rs" +required-features = ["client"] + +[[example]] +name = "search_address" +path = "examples/client/high_level/search_address.rs" +required-features = ["client"] + # Node API core examples [[example]] @@ -472,6 +485,11 @@ name = "node_api_core_get_included_block_raw" path = "examples/client/node_api_core/16_get_included_block_raw.rs" required-features = ["client"] +[[example]] +name = "node_api_core_get_validators" +path = "examples/client/node_api_core/get_validators.rs" +required-features = ["client"] + # Node API indexer examples [[example]] @@ -581,6 +599,16 @@ name = "build_basic_output" path = "examples/client/output/build_basic_output.rs" required-features = ["client"] +[[example]] +name = "send_all" +path = "examples/client/send_all.rs" +required-features = ["client"] + +[[example]] +name = "tagged_data_to_utf8" +path = "examples/client/tagged_data_to_utf8.rs" +required-features = ["client"] + ### Wallet [[example]] diff --git a/sdk/examples/client/01_generate_addresses.rs b/sdk/examples/client/01_generate_addresses.rs index c3f10a3e7d..d3ab4f6dcd 100644 --- a/sdk/examples/client/01_generate_addresses.rs +++ b/sdk/examples/client/01_generate_addresses.rs @@ -11,11 +11,11 @@ use iota_sdk::client::{ api::GetAddressesOptions, secret::{GenerateAddressOptions, SecretManager}, - Client, Result, + Client, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/02_address_balance.rs b/sdk/examples/client/02_address_balance.rs index ac54630929..b9e027b50f 100644 --- a/sdk/examples/client/02_address_balance.rs +++ b/sdk/examples/client/02_address_balance.rs @@ -11,14 +11,14 @@ use iota_sdk::{ client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::BasicOutputQueryParameters, - secret::SecretManager, Client, Result, + api::GetAddressesOptions, constants::SHIMMER_COIN_TYPE, + node_api::indexer::query_parameters::BasicOutputQueryParameters, secret::SecretManager, Client, }, types::block::output::NativeTokensBuilder, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -36,14 +36,8 @@ async fn main() -> Result<()> { // Generate the first address let first_address = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::from_client(&client) - .await? - .with_account_index(0) - .with_range(0..1), - ) - .await?[0] - .clone(); + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, client.get_bech32_hrp().await?, None) + .await?; // Get output ids of outputs that can be controlled by this address without further unlock constraints let output_ids_response = client diff --git a/sdk/examples/client/07_mqtt.rs b/sdk/examples/client/07_mqtt.rs index d875979241..11bca167b7 100644 --- a/sdk/examples/client/07_mqtt.rs +++ b/sdk/examples/client/07_mqtt.rs @@ -11,14 +11,14 @@ use iota_sdk::{ client::{ mqtt::{BrokerOptions, MqttEvent, MqttPayload, Topic}, - Client, Result, + Client, }, types::block::address::Bech32Address, }; // Connecting to a MQTT broker using raw ip doesn't work with TCP. This is a limitation of rustls. #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let num_events: usize = std::env::args().nth(1).map(|s| s.parse().unwrap()).unwrap_or(10); let address: Bech32Address = std::env::args() diff --git a/sdk/examples/client/block/00_block_no_payload.rs b/sdk/examples/client/block/00_block_no_payload.rs index 5696e1eaf6..cede60d8e8 100644 --- a/sdk/examples/client/block/00_block_no_payload.rs +++ b/sdk/examples/client/block/00_block_no_payload.rs @@ -13,13 +13,13 @@ use iota_sdk::{ client::{ constants::IOTA_COIN_TYPE, secret::{SecretManager, SignBlock}, - Client, Result, + Client, }, types::block::output::AccountId, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/block/01_block_confirmation_time.rs b/sdk/examples/client/block/01_block_confirmation_time.rs index a6d1c3e66d..e11bb9f84b 100644 --- a/sdk/examples/client/block/01_block_confirmation_time.rs +++ b/sdk/examples/client/block/01_block_confirmation_time.rs @@ -13,13 +13,13 @@ use iota_sdk::{ client::{ constants::IOTA_COIN_TYPE, secret::{SecretManager, SignBlock}, - Client, Result, + Client, }, types::{api::core::BlockState, block::output::AccountId}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/block/02_block_custom_parents.rs b/sdk/examples/client/block/02_block_custom_parents.rs index a39e30afee..b7ce9ab6c8 100644 --- a/sdk/examples/client/block/02_block_custom_parents.rs +++ b/sdk/examples/client/block/02_block_custom_parents.rs @@ -13,13 +13,13 @@ use iota_sdk::{ client::{ constants::IOTA_COIN_TYPE, secret::{SecretManager, SignBlock}, - Client, Result, + Client, }, types::block::output::AccountId, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/block/03_block_custom_payload.rs b/sdk/examples/client/block/03_block_custom_payload.rs index 23d08efe1e..b7b03aa31e 100644 --- a/sdk/examples/client/block/03_block_custom_payload.rs +++ b/sdk/examples/client/block/03_block_custom_payload.rs @@ -13,7 +13,7 @@ use iota_sdk::{ client::{ constants::IOTA_COIN_TYPE, secret::{SecretManager, SignBlock}, - Client, Result, + Client, }, types::block::{ output::AccountId, @@ -22,7 +22,7 @@ use iota_sdk::{ }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/block/04_block_tagged_data.rs b/sdk/examples/client/block/04_block_tagged_data.rs index ce1a70320d..b535bbb4f6 100644 --- a/sdk/examples/client/block/04_block_tagged_data.rs +++ b/sdk/examples/client/block/04_block_tagged_data.rs @@ -13,7 +13,7 @@ use iota_sdk::{ client::{ constants::IOTA_COIN_TYPE, secret::{SecretManager, SignBlock}, - Client, Result, + Client, }, types::block::{ output::AccountId, @@ -22,7 +22,7 @@ use iota_sdk::{ }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/client_config.rs b/sdk/examples/client/client_config.rs index 943542e3b0..eced975f5e 100644 --- a/sdk/examples/client/client_config.rs +++ b/sdk/examples/client/client_config.rs @@ -8,10 +8,10 @@ //! cargo run --release --example client_config //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // Create a client instance let client = Client::builder() .from_json( @@ -37,8 +37,8 @@ async fn main() -> Result<()> { .finish() .await?; - let info = client.get_info().await?; - println!("{info:#?}"); + let node_info = client.get_node_info().await?; + println!("{node_info:#?}"); Ok(()) } diff --git a/sdk/examples/client/get_block.rs b/sdk/examples/client/get_block.rs index 00ae778986..750efccb14 100644 --- a/sdk/examples/client/get_block.rs +++ b/sdk/examples/client/get_block.rs @@ -8,10 +8,10 @@ //! cargo run --release --example get_block //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/getting_started.rs b/sdk/examples/client/getting_started.rs index dce63ed8ff..8c0832b81a 100644 --- a/sdk/examples/client/getting_started.rs +++ b/sdk/examples/client/getting_started.rs @@ -7,17 +7,17 @@ //! cargo run --release --example client_getting_started //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let client = Client::builder() .with_node("https://api.testnet.shimmer.network")? // Insert your node URL here .finish() .await?; - let info = client.get_info().await?; - println!("Node Info: {info:?}"); + let node_info = client.get_node_info().await?; + println!("Node Info: {node_info:?}"); Ok(()) } diff --git a/sdk/examples/client/high_level/consolidation.rs b/sdk/examples/client/high_level/consolidation.rs deleted file mode 100644 index 086daa228b..0000000000 --- a/sdk/examples/client/high_level/consolidation.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will consolidate all funds in a range of addresses. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example address_consolidation [ADDRESS INDEX START] [ADDRESS COUNT] -//! ``` - -use iota_sdk::client::{api::GetAddressesOptions, secret::SecretManager, Client, Result}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - dotenvy::dotenv().ok(); - - for var in ["NODE_URL", "EXPLORER_URL", "MNEMONIC"] { - std::env::var(var).expect(&format!(".env variable '{var}' is undefined, see .env.example")); - } - - let address_range_start = std::env::args().nth(1).map(|s| s.parse::().unwrap()).unwrap_or(0); - let address_range_len = std::env::args().nth(2).map(|s| s.parse::().unwrap()).unwrap_or(10); - - let address_range = address_range_start..address_range_start + address_range_len; - println!("Address consolidation range: {:?}", address_range); - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - // Here all funds will be send to the address with the lowest index in the range - let address = client - .consolidate_funds( - &secret_manager, - GetAddressesOptions { - range: address_range, - ..Default::default() - }, - ) - .await?; - - println!( - "Funds consolidated to: {}/addr/{}", - std::env::var("EXPLORER_URL").unwrap(), - address - ); - - Ok(()) -} diff --git a/sdk/examples/client/high_level/inputs_from_transaction_id.rs b/sdk/examples/client/high_level/get_transaction_inputs.rs similarity index 77% rename from sdk/examples/client/high_level/inputs_from_transaction_id.rs rename to sdk/examples/client/high_level/get_transaction_inputs.rs index fe10409d6f..5cede3b3a6 100644 --- a/sdk/examples/client/high_level/inputs_from_transaction_id.rs +++ b/sdk/examples/client/high_level/get_transaction_inputs.rs @@ -7,16 +7,13 @@ //! //! Rename `.env.example` to `.env` first, then run the command: //! ```sh -//! cargo run --release --example inputs_from_transaction_id +//! cargo run --release --example get_transaction_inputs //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::payload::transaction::TransactionId, -}; +use iota_sdk::{client::Client, types::block::payload::signed_transaction::TransactionId}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -33,7 +30,7 @@ async fn main() -> Result<()> { .expect("missing example argument: TRANSACTION ID") .parse::()?; - let inputs = client.inputs_from_transaction_id(&transaction_id).await?; + let inputs = client.get_transaction_inputs(&transaction_id).await?; println!("Transaction inputs:\n{:#?}", inputs); diff --git a/sdk/examples/client/high_level/search_address.rs b/sdk/examples/client/high_level/search_address.rs index ad8de375e0..7b8f066402 100644 --- a/sdk/examples/client/high_level/search_address.rs +++ b/sdk/examples/client/high_level/search_address.rs @@ -12,11 +12,11 @@ use iota_sdk::client::{ api::{search_address, GetAddressesOptions}, constants::SHIMMER_COIN_TYPE, secret::SecretManager, - Client, Result, + Client, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -40,6 +40,7 @@ async fn main() -> Result<()> { secret_manager .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) .await?[0] + .clone() }; println!("Search address: {address:#?}"); diff --git a/sdk/examples/client/ledger_nano.rs b/sdk/examples/client/ledger_nano.rs index cd0d2ea713..c9c07e33a2 100644 --- a/sdk/examples/client/ledger_nano.rs +++ b/sdk/examples/client/ledger_nano.rs @@ -20,11 +20,10 @@ use iota_sdk::client::{ api::GetAddressesOptions, constants::{SHIMMER_COIN_TYPE, SHIMMER_TESTNET_BECH32_HRP}, secret::{ledger_nano::LedgerSecretManager, SecretManager}, - Result, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/ledger_nano_transaction.rs b/sdk/examples/client/ledger_nano_transaction.rs index 8dcdf75a54..790fd060bd 100644 --- a/sdk/examples/client/ledger_nano_transaction.rs +++ b/sdk/examples/client/ledger_nano_transaction.rs @@ -20,13 +20,13 @@ use iota_sdk::client::{ api::GetAddressesOptions, secret::{ledger_nano::LedgerSecretManager, SecretManager}, - Client, Result, + Client, }; // const AMOUNT: u64 = 1_000_000; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/logger.rs b/sdk/examples/client/logger.rs index 875ea7f575..f64dd8049e 100644 --- a/sdk/examples/client/logger.rs +++ b/sdk/examples/client/logger.rs @@ -8,10 +8,10 @@ //! cargo run --release --example client_logger //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -39,7 +39,7 @@ async fn main() -> Result<()> { .await?; // Get node info. - let _ = client.get_info().await?; + let _ = client.get_node_info().await?; println!("Example completed successfully. `client.log` file has been updated."); diff --git a/sdk/examples/client/node_api_core/01_get_routes.rs b/sdk/examples/client/node_api_core/01_get_routes.rs index f8e8dfb614..ab3e472e35 100644 --- a/sdk/examples/client/node_api_core/01_get_routes.rs +++ b/sdk/examples/client/node_api_core/01_get_routes.rs @@ -8,10 +8,10 @@ //! cargo run --release --example node_api_core_get_routes [NODE URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); // Take the node URL from command line argument or use one from env as default. diff --git a/sdk/examples/client/node_api_core/03_get_issuance.rs b/sdk/examples/client/node_api_core/03_get_issuance.rs index 7519bb7f9f..a52ffc826e 100644 --- a/sdk/examples/client/node_api_core/03_get_issuance.rs +++ b/sdk/examples/client/node_api_core/03_get_issuance.rs @@ -9,10 +9,10 @@ //! cargo run --release --example node_api_core_get_issuance [NODE URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/04_post_block.rs b/sdk/examples/client/node_api_core/04_post_block.rs index e6f9ef34d8..f47310223c 100644 --- a/sdk/examples/client/node_api_core/04_post_block.rs +++ b/sdk/examples/client/node_api_core/04_post_block.rs @@ -13,13 +13,13 @@ use iota_sdk::{ client::{ constants::IOTA_COIN_TYPE, secret::{SecretManager, SignBlock}, - Client, Result, + Client, }, types::block::output::AccountId, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/05_post_block_raw.rs b/sdk/examples/client/node_api_core/05_post_block_raw.rs index f4c8265253..aa0f424ce4 100644 --- a/sdk/examples/client/node_api_core/05_post_block_raw.rs +++ b/sdk/examples/client/node_api_core/05_post_block_raw.rs @@ -13,13 +13,13 @@ use iota_sdk::{ client::{ constants::IOTA_COIN_TYPE, secret::{SecretManager, SignBlock}, - Client, Result, + Client, }, types::block::output::AccountId, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/06_get_block.rs b/sdk/examples/client/node_api_core/06_get_block.rs index 99898c6fa8..d1134dae9d 100644 --- a/sdk/examples/client/node_api_core/06_get_block.rs +++ b/sdk/examples/client/node_api_core/06_get_block.rs @@ -8,10 +8,10 @@ //! cargo run --release --example node_api_core_get_block [BLOCK ID] [NODE URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/07_get_block_raw.rs b/sdk/examples/client/node_api_core/07_get_block_raw.rs index 3ac1d8f3d7..4f98a30a29 100644 --- a/sdk/examples/client/node_api_core/07_get_block_raw.rs +++ b/sdk/examples/client/node_api_core/07_get_block_raw.rs @@ -8,10 +8,10 @@ //! cargo run --release --example node_api_core_get_block_raw [BLOCK ID] [NODE URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/08_get_block_metadata.rs b/sdk/examples/client/node_api_core/08_get_block_metadata.rs index 23085c13de..a0f389660a 100644 --- a/sdk/examples/client/node_api_core/08_get_block_metadata.rs +++ b/sdk/examples/client/node_api_core/08_get_block_metadata.rs @@ -8,10 +8,10 @@ //! cargo run --release --example node_api_core_get_block_metadata [BLOCK ID] [NODE URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/09_get_output.rs b/sdk/examples/client/node_api_core/09_get_output.rs index 08a8bf2b41..c6963e5215 100644 --- a/sdk/examples/client/node_api_core/09_get_output.rs +++ b/sdk/examples/client/node_api_core/09_get_output.rs @@ -10,13 +10,10 @@ //! cargo run --release --example node_api_core_get_output [NODE URL] //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::output::OutputId, -}; +use iota_sdk::{client::Client, types::block::output::OutputId}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/10_get_output_raw.rs b/sdk/examples/client/node_api_core/10_get_output_raw.rs index 56787884bf..46bcd6f088 100644 --- a/sdk/examples/client/node_api_core/10_get_output_raw.rs +++ b/sdk/examples/client/node_api_core/10_get_output_raw.rs @@ -10,13 +10,10 @@ //! cargo run --release --example node_api_core_get_output_raw [NODE URL] //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::output::OutputId, -}; +use iota_sdk::{client::Client, types::block::output::OutputId}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/11_get_output_metadata.rs b/sdk/examples/client/node_api_core/11_get_output_metadata.rs index 9f3660ea71..106670e5e6 100644 --- a/sdk/examples/client/node_api_core/11_get_output_metadata.rs +++ b/sdk/examples/client/node_api_core/11_get_output_metadata.rs @@ -10,13 +10,10 @@ //! cargo run --release --example node_api_core_get_output_metadata [NODE URL] //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::output::OutputId, -}; +use iota_sdk::{client::Client, types::block::output::OutputId}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/12_get_output_full.rs b/sdk/examples/client/node_api_core/12_get_output_full.rs index fe82758ea1..d0ed056ca3 100644 --- a/sdk/examples/client/node_api_core/12_get_output_full.rs +++ b/sdk/examples/client/node_api_core/12_get_output_full.rs @@ -11,13 +11,10 @@ //! cargo run --release --example node_api_core_get_output_full [NODE URL] //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::output::OutputId, -}; +use iota_sdk::{client::Client, types::block::output::OutputId}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/15_get_included_block.rs b/sdk/examples/client/node_api_core/15_get_included_block.rs index 28f2593981..fefe3999f5 100644 --- a/sdk/examples/client/node_api_core/15_get_included_block.rs +++ b/sdk/examples/client/node_api_core/15_get_included_block.rs @@ -11,10 +11,10 @@ //! cargo run --release --example node_api_core_get_included_block [NODE URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/16_get_included_block_raw.rs b/sdk/examples/client/node_api_core/16_get_included_block_raw.rs index 95b60a7e8e..5198a6583a 100644 --- a/sdk/examples/client/node_api_core/16_get_included_block_raw.rs +++ b/sdk/examples/client/node_api_core/16_get_included_block_raw.rs @@ -11,10 +11,10 @@ //! cargo run --release --example node_api_core_get_included_block_raw [NODE URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_core/get_validators.rs b/sdk/examples/client/node_api_core/get_validators.rs new file mode 100644 index 0000000000..7467d4f149 --- /dev/null +++ b/sdk/examples/client/node_api_core/get_validators.rs @@ -0,0 +1,40 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +//! This example returns the validators known by the node by querying the corresponding endpoint. +//! You can provide a custom PAGE_SIZE and additionally a CURSOR from a previous request. +//! +//! Rename `.env.example` to `.env` first, then run the command: +//! ```sh +//! cargo run --release --all-features --example node_api_core_get_validators [PAGE_SIZE] [CURSOR] [NODE_URL] +//! ``` + +use iota_sdk::client::{Client, ClientError}; + +#[tokio::main] +async fn main() -> Result<(), ClientError> { + // If not provided we use the default node from the `.env` file. + dotenvy::dotenv().ok(); + + let page_size = std::env::args().nth(1).map(|s| s.parse::().unwrap()); + let cursor = std::env::args().nth(2); + + // Take the node URL from command line argument or use one from env as default. + let node_url = std::env::args() + .nth(3) + .unwrap_or_else(|| std::env::var("NODE_URL").expect("NODE_URL not set")); + + // Create a node client. + let client = Client::builder() + .with_node(&node_url)? + .with_ignore_node_health() + .finish() + .await?; + + // Get validators. + let validators = client.get_validators(page_size, cursor).await?; + + println!("{validators:#?}"); + + Ok(()) +} diff --git a/sdk/examples/client/node_api_indexer/01_get_account_output.rs b/sdk/examples/client/node_api_indexer/01_get_account_output.rs index 2932a6e018..aae41ff1ac 100644 --- a/sdk/examples/client/node_api_indexer/01_get_account_output.rs +++ b/sdk/examples/client/node_api_indexer/01_get_account_output.rs @@ -11,13 +11,10 @@ //! cargo run --release --example node_api_indexer_get_account_output [NODE URL] //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::output::AccountId, -}; +use iota_sdk::{client::Client, types::block::output::AccountId}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_indexer/02_get_account_outputs.rs b/sdk/examples/client/node_api_indexer/02_get_account_outputs.rs index 86cae612d3..d401c9d9d8 100644 --- a/sdk/examples/client/node_api_indexer/02_get_account_outputs.rs +++ b/sdk/examples/client/node_api_indexer/02_get_account_outputs.rs @@ -12,12 +12,12 @@ //! ``` use iota_sdk::{ - client::{node_api::indexer::query_parameters::AccountOutputQueryParameters, Client, Result}, + client::{node_api::indexer::query_parameters::AccountOutputQueryParameters, Client}, types::block::address::Bech32Address, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_indexer/03_get_foundry_output.rs b/sdk/examples/client/node_api_indexer/03_get_foundry_output.rs index 5e8a040fab..fc2c318a22 100644 --- a/sdk/examples/client/node_api_indexer/03_get_foundry_output.rs +++ b/sdk/examples/client/node_api_indexer/03_get_foundry_output.rs @@ -11,13 +11,10 @@ //! cargo run --release --example node_api_indexer_get_foundry_output [NODE URL] //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::output::FoundryId, -}; +use iota_sdk::{client::Client, types::block::output::FoundryId}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs b/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs index b1a993d522..2a5f7bf57e 100644 --- a/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs +++ b/sdk/examples/client/node_api_indexer/04_get_foundry_outputs.rs @@ -12,12 +12,12 @@ //! ``` use iota_sdk::{ - client::{node_api::indexer::query_parameters::FoundryOutputQueryParameters, Client, Result}, + client::{node_api::indexer::query_parameters::FoundryOutputQueryParameters, Client}, types::block::address::Bech32Address, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_indexer/05_get_nft_output.rs b/sdk/examples/client/node_api_indexer/05_get_nft_output.rs index 1214780597..f4b8edb496 100644 --- a/sdk/examples/client/node_api_indexer/05_get_nft_output.rs +++ b/sdk/examples/client/node_api_indexer/05_get_nft_output.rs @@ -11,13 +11,10 @@ //! cargo run --release --example node_api_indexer_get_nft_output [NODE URL] //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::output::NftId, -}; +use iota_sdk::{client::Client, types::block::output::NftId}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_indexer/06_get_nft_outputs.rs b/sdk/examples/client/node_api_indexer/06_get_nft_outputs.rs index bf491f0544..77cbec53b3 100644 --- a/sdk/examples/client/node_api_indexer/06_get_nft_outputs.rs +++ b/sdk/examples/client/node_api_indexer/06_get_nft_outputs.rs @@ -12,12 +12,12 @@ //! ``` use iota_sdk::{ - client::{node_api::indexer::query_parameters::NftOutputQueryParameters, Client, Result}, + client::{node_api::indexer::query_parameters::NftOutputQueryParameters, Client}, types::block::address::Bech32Address, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/node_api_indexer/07_get_random_basic_outputs.rs b/sdk/examples/client/node_api_indexer/07_get_random_basic_outputs.rs index 4cf7f8fbb5..391221f601 100644 --- a/sdk/examples/client/node_api_indexer/07_get_random_basic_outputs.rs +++ b/sdk/examples/client/node_api_indexer/07_get_random_basic_outputs.rs @@ -11,10 +11,10 @@ //! cargo run --release --example node_api_indexer_get_random_basic_outputs [NODE_URL] //! ``` -use iota_sdk::client::{node_api::indexer::query_parameters::BasicOutputQueryParameters, Client, Result}; +use iota_sdk::client::{node_api::indexer::query_parameters::BasicOutputQueryParameters, Client}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/output/build_account_output.rs b/sdk/examples/client/output/build_account_output.rs index af80e6333e..b1af57416e 100644 --- a/sdk/examples/client/output/build_account_output.rs +++ b/sdk/examples/client/output/build_account_output.rs @@ -9,7 +9,7 @@ //! ``` use iota_sdk::{ - client::{Client, Result}, + client::Client, types::block::{ address::Address, output::{ @@ -21,7 +21,7 @@ use iota_sdk::{ }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/output/build_basic_output.rs b/sdk/examples/client/output/build_basic_output.rs index 1d62dac1ea..809744c17d 100644 --- a/sdk/examples/client/output/build_basic_output.rs +++ b/sdk/examples/client/output/build_basic_output.rs @@ -8,18 +8,15 @@ //! cargo run --release --example build_basic_output [ADDRESS] //! ``` -use iota_sdk::{ - client::Result, - types::block::{ - address::Address, - output::{ - feature::{MetadataFeature, SenderFeature, TagFeature}, - unlock_condition::{ - AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, - TimelockUnlockCondition, - }, - BasicOutputBuilder, +use iota_sdk::types::block::{ + address::Address, + output::{ + feature::{MetadataFeature, SenderFeature, TagFeature}, + unlock_condition::{ + AddressUnlockCondition, ExpirationUnlockCondition, StorageDepositReturnUnlockCondition, + TimelockUnlockCondition, }, + BasicOutputBuilder, }, }; @@ -27,7 +24,7 @@ const KEY: &str = "Hello"; const METADATA: &[u8; 6] = b"World!"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/output/build_nft_output.rs b/sdk/examples/client/output/build_nft_output.rs index 57863e7b17..536765966d 100644 --- a/sdk/examples/client/output/build_nft_output.rs +++ b/sdk/examples/client/output/build_nft_output.rs @@ -9,7 +9,7 @@ //! ``` use iota_sdk::{ - client::{Client, Result}, + client::Client, types::block::{ address::Address, output::{ @@ -24,7 +24,7 @@ const MUTABLE_METADATA: &str = "mutable metadata"; const TAG: &str = "my tag"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/quorum.rs b/sdk/examples/client/quorum.rs index c02b9856e7..6e211c3c0e 100644 --- a/sdk/examples/client/quorum.rs +++ b/sdk/examples/client/quorum.rs @@ -15,11 +15,11 @@ use iota_sdk::client::{ api::GetAddressesOptions, node_api::indexer::query_parameters::BasicOutputQueryParameters, secret::SecretManager, - Client, Result, + Client, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/send_all.rs b/sdk/examples/client/send_all.rs index c37471635f..1206389d57 100644 --- a/sdk/examples/client/send_all.rs +++ b/sdk/examples/client/send_all.rs @@ -9,22 +9,32 @@ //! cargo run --release --example send_all //! ``` +use std::collections::BTreeSet; + +use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::QueryParameter, secret::SecretManager, Client, - Result, + api::{options::TransactionOptions, GetAddressesOptions}, + constants::IOTA_COIN_TYPE, + node_api::indexer::query_parameters::BasicOutputQueryParameters, + secret::{SecretManage, SecretManager, SignBlock}, + Client, + }, + types::block::{ + address::{Address, Ed25519Address, ToBech32Ext}, + output::{unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder}, + payload::{Payload, SignedTransactionPayload}, }, - types::block::output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, NativeTokensBuilder}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. // Configure your own mnemonic in ".env". Since the output amount cannot be zero, the mnemonic // `NON_SECURE_USE_DEVELOPMENT_MNEMONIC_1` must contain non-zero balance. dotenvy::dotenv().ok(); - for var in ["NODE_URL", "MNEMONIC", "MNEMONIC_2", "EXPLORER_URL"] { + for var in ["NODE_URL", "MNEMONIC", "MNEMONIC_2", "EXPLORER_URL", "ISSUER_ID"] { std::env::var(var).expect(&format!(".env variable '{var}' is undefined, see .env.example")); } @@ -36,59 +46,103 @@ async fn main() -> Result<()> { let secret_manager_1 = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; let secret_manager_2 = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC_2").unwrap())?; - - let token_supply = client.get_token_supply().await?; - - let address = secret_manager_1 - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; + let issuer_id = std::env::var("ISSUER_ID").unwrap().parse::().unwrap(); + + let chain = Bip44::new(IOTA_COIN_TYPE); + + let from_address = Address::from(Ed25519Address::from_public_key_bytes( + secret_manager_1 + .generate_ed25519_public_keys( + chain.coin_type, + chain.account, + chain.address_index..chain.address_index + 1, + None, + ) + .await?[0] + .to_bytes(), + )) + .to_bech32(client.get_bech32_hrp().await?); // Get output ids of outputs that can be controlled by this address without further unlock constraints let output_ids_response = client - .basic_output_ids(BasicOutputQueryParameters::only_address_unlock_condition(address)) + .basic_output_ids(BasicOutputQueryParameters::only_address_unlock_condition( + from_address.clone(), + )) .await?; // Get the outputs by their id - let outputs_responses = client.get_outputs(&output_ids_response.items).await?; + let outputs_responses = client.get_outputs_with_metadata(&output_ids_response.items).await?; + + let protocol_parameters = client.get_protocol_parameters().await?; - // Calculate the total amount and native tokens + // Calculate the total amount let mut total_amount = 0; - let mut total_native_tokens = NativeTokensBuilder::new(); - for output in outputs_responses { - if let Some(native_tokens) = output.native_tokens() { - total_native_tokens.add_native_tokens(native_tokens.clone())?; + let mut inputs = BTreeSet::new(); + let mut outputs = Vec::new(); + + for res in outputs_responses { + total_amount += res.output.amount(); + if let Some(native_token) = res.output.native_token() { + // We don't want to send the native tokens, so return them and subtract out the storage amount. + let native_token_return = + BasicOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters()) + .add_unlock_condition(AddressUnlockCondition::new(from_address.clone())) + .with_native_token(*native_token) + .finish_output()?; + total_amount -= native_token_return.amount(); + outputs.push(native_token_return); } - total_amount += output.amount(); + inputs.insert(*res.metadata().output_id()); } - let total_native_tokens = total_native_tokens.finish()?; - println!("Total amount: {total_amount}"); - let address = secret_manager_2 + let to_address = secret_manager_2 .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - - let mut basic_output_builder = - BasicOutputBuilder::new_with_amount(total_amount).add_unlock_condition(AddressUnlockCondition::new(address)); + .await?[0] + .clone(); + + // Add the output with the total amount we're sending + outputs.push( + BasicOutputBuilder::new_with_amount(total_amount) + .add_unlock_condition(AddressUnlockCondition::new(to_address.clone())) + .finish_output()?, + ); - for native_token in total_native_tokens { - basic_output_builder = basic_output_builder.add_native_token(native_token); - } - let new_output = basic_output_builder.finish_output(token_supply)?; + let prepared_transaction = client + .build_transaction( + [(from_address.into_inner(), chain)], + outputs, + TransactionOptions { + required_inputs: inputs, + allow_additional_input_selection: false, + ..Default::default() + }, + ) + .await?; + let unlocks = secret_manager_1 + .transaction_unlocks(&prepared_transaction, &protocol_parameters) + .await?; let block = client - .build_block() - .with_secret_manager(&secret_manager_1) - .with_outputs([new_output])? - .finish() + .build_basic_block( + issuer_id, + Payload::from(SignedTransactionPayload::new( + prepared_transaction.transaction, + unlocks, + )?), + ) + .await? + .sign_ed25519(&secret_manager_1, Bip44::new(IOTA_COIN_TYPE)) .await?; + client.post_block(&block).await?; + println!( "Block with all outputs sent: {}/block/{}", std::env::var("EXPLORER_URL").unwrap(), - block.id() + block.id(&protocol_parameters) ); Ok(()) diff --git a/sdk/examples/client/split_funds.rs b/sdk/examples/client/split_funds.rs deleted file mode 100644 index 74eb29625a..0000000000 --- a/sdk/examples/client/split_funds.rs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -//! In this example we will send 100 basic outputs to our first address. -//! -//! Rename `.env.example` to `.env` first, then run the command: -//! ```sh -//! cargo run --release --example client_split_funds -//! ``` - -use iota_sdk::client::{api::GetAddressesOptions, request_funds_from_faucet, secret::SecretManager, Client, Result}; - -#[tokio::main] -async fn main() -> Result<()> { - // This example uses secrets in environment variables for simplicity which should not be done in production. - // Configure your own mnemonic in ".env". Since the output amount cannot be zero, the mnemonic - // `MNEMONIC` must contain non-zero balance. - dotenvy::dotenv().ok(); - - for var in ["NODE_URL", "MNEMONIC", "FAUCET_URL", "EXPLORER_URL"] { - std::env::var(var).expect(&format!(".env variable '{var}' is undefined, see .env.example")); - } - - // Create a node client. - let client = Client::builder() - .with_node(&std::env::var("NODE_URL").unwrap())? - .finish() - .await?; - - let secret_manager = SecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - - let address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::from_client(&client).await?.with_range(0..1)) - .await?[0]; - println!( - "Requesting funds (waiting 15s): {}", - request_funds_from_faucet(&std::env::var("FAUCET_URL").unwrap(), &address,).await? - ); - // wait so the faucet can send the funds - tokio::time::sleep(std::time::Duration::from_secs(15)).await; - - let mut block_builder = client.build_block().with_secret_manager(&secret_manager); - // Insert the output address and amount to spent. The amount cannot be zero. - for _ in 0..100 { - block_builder = block_builder.with_output(address, 1_000_000).await?; - } - let block = block_builder.finish().await?; - - println!( - "Block with split funds sent: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block.id() - ); - - Ok(()) -} diff --git a/sdk/examples/client/stronghold.rs b/sdk/examples/client/stronghold.rs index d57cfb6d9c..e328b6a947 100644 --- a/sdk/examples/client/stronghold.rs +++ b/sdk/examples/client/stronghold.rs @@ -13,13 +13,12 @@ use iota_sdk::{ api::GetAddressesOptions, constants::{SHIMMER_COIN_TYPE, SHIMMER_TESTNET_BECH32_HRP}, secret::{stronghold::StrongholdSecretManager, SecretManager}, - Result, }, crypto::keys::bip39::Mnemonic, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/client/tagged_data_to_utf8.rs b/sdk/examples/client/tagged_data_to_utf8.rs index 9660384ea7..6aa670cdc7 100644 --- a/sdk/examples/client/tagged_data_to_utf8.rs +++ b/sdk/examples/client/tagged_data_to_utf8.rs @@ -8,13 +8,10 @@ //! cargo run --release --example tagged_data_to_utf8 //! ``` -use iota_sdk::{ - client::{Client, Result}, - types::block::payload::TaggedDataPayload, -}; +use iota_sdk::{client::Client, types::block::payload::TaggedDataPayload}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // `hello` in hexadecimal. let tag = prefix_hex::decode::>("0x68656c6c6f")?; // `world` in hexadecimal. diff --git a/sdk/examples/how_tos/account_output/create.rs b/sdk/examples/how_tos/account_output/create.rs index 7add1e3ddc..cb64683835 100644 --- a/sdk/examples/how_tos/account_output/create.rs +++ b/sdk/examples/how_tos/account_output/create.rs @@ -12,10 +12,10 @@ //! cargo run --release --all-features --example create_account_output //! ``` -use iota_sdk::{wallet::Result, Wallet}; +use iota_sdk::Wallet; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { //  This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -43,13 +43,14 @@ async fn main() -> Result<()> { let transaction = wallet.create_account_output(None, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); let balance = wallet.sync(None).await?; diff --git a/sdk/examples/how_tos/account_output/destroy.rs b/sdk/examples/how_tos/account_output/destroy.rs index 98ce52123e..57f2515c51 100644 --- a/sdk/examples/how_tos/account_output/destroy.rs +++ b/sdk/examples/how_tos/account_output/destroy.rs @@ -11,10 +11,10 @@ //! cargo run --release --all-features --example destroy_account_output //! ``` -use iota_sdk::{wallet::Result, Wallet}; +use iota_sdk::Wallet; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -41,14 +41,14 @@ async fn main() -> Result<()> { let transaction = wallet.burn(*account_id, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); println!("Burned Account '{}'", account_id); diff --git a/sdk/examples/how_tos/account_output/implicit_account_creation.rs b/sdk/examples/how_tos/account_output/implicit_account_creation.rs index 9f565da0b8..5c2fe72549 100644 --- a/sdk/examples/how_tos/account_output/implicit_account_creation.rs +++ b/sdk/examples/how_tos/account_output/implicit_account_creation.rs @@ -11,11 +11,11 @@ use iota_sdk::{ client::{constants::SHIMMER_COIN_TYPE, secret::SecretManager}, crypto::keys::bip44::Bip44, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { //  This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/account_output/request_funds.rs b/sdk/examples/how_tos/account_output/request_funds.rs index 37adabba64..7529718ec6 100644 --- a/sdk/examples/how_tos/account_output/request_funds.rs +++ b/sdk/examples/how_tos/account_output/request_funds.rs @@ -10,12 +10,12 @@ use iota_sdk::{ client::request_funds_from_faucet, types::block::address::{AccountAddress, ToBech32Ext}, - wallet::{AccountSyncOptions, Result, SyncOptions}, + wallet::{AccountSyncOptions, SyncOptions}, Wallet, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/account_output/send_amount.rs b/sdk/examples/how_tos/account_output/send_amount.rs index 9388d31f71..13434b64a8 100644 --- a/sdk/examples/how_tos/account_output/send_amount.rs +++ b/sdk/examples/how_tos/account_output/send_amount.rs @@ -7,14 +7,14 @@ //! `cargo run --release --all-features --example account_output_send_amount` use iota_sdk::{ - client::node_api::indexer::query_parameters::BasicOutputQueryParameters, + client::{api::options::TransactionOptions, node_api::indexer::query_parameters::BasicOutputQueryParameters}, types::block::address::{AccountAddress, ToBech32Ext}, - wallet::{AccountSyncOptions, Result, SyncOptions, TransactionOptions}, + wallet::{AccountSyncOptions, SyncOptions}, Wallet, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs b/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs index 6ddb5458a4..6228a1b75e 100644 --- a/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs +++ b/sdk/examples/how_tos/advanced_transactions/advanced_transaction.rs @@ -16,12 +16,11 @@ use iota_sdk::{ }, slot::SlotIndex, }, - wallet::Result, Wallet, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -57,14 +56,14 @@ async fn main() -> Result<()> { let transaction = wallet.send_outputs(vec![basic_output], None).await?; println!("Transaction sent: {}", transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; + println!( - "Block sent: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); } diff --git a/sdk/examples/how_tos/advanced_transactions/claim_transaction.rs b/sdk/examples/how_tos/advanced_transactions/claim_transaction.rs index f4c7aa4d17..2b056b873c 100644 --- a/sdk/examples/how_tos/advanced_transactions/claim_transaction.rs +++ b/sdk/examples/how_tos/advanced_transactions/claim_transaction.rs @@ -6,13 +6,10 @@ //! //! `cargo run --release --all-features --example claim_transaction` -use iota_sdk::{ - wallet::{OutputsToClaim, Result}, - Wallet, -}; +use iota_sdk::{wallet::OutputsToClaim, Wallet}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -43,14 +40,14 @@ async fn main() -> Result<()> { let transaction = wallet.claim_outputs(output_ids).await?; println!("Transaction sent: {}", transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; + println!( - "Block sent: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); Ok(()) diff --git a/sdk/examples/how_tos/advanced_transactions/send_micro_transaction.rs b/sdk/examples/how_tos/advanced_transactions/send_micro_transaction.rs index 3214dc9226..152b6254ed 100644 --- a/sdk/examples/how_tos/advanced_transactions/send_micro_transaction.rs +++ b/sdk/examples/how_tos/advanced_transactions/send_micro_transaction.rs @@ -11,10 +11,7 @@ //! cargo run --release --all-features --example send_micro_transaction //! ``` -use iota_sdk::{ - wallet::{Result, TransactionOptions}, - Wallet, -}; +use iota_sdk::{client::api::options::TransactionOptions, Wallet}; // The base coin micro amount to send const SEND_MICRO_AMOUNT: u64 = 1; @@ -22,7 +19,7 @@ const SEND_MICRO_AMOUNT: u64 = 1; const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -59,15 +56,14 @@ async fn main() -> Result<()> { .await?; println!("Transaction sent: {}", transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); Ok(()) diff --git a/sdk/examples/how_tos/client/get_health.rs b/sdk/examples/how_tos/client/get_health.rs index 7a42ef8978..96570fdbba 100644 --- a/sdk/examples/how_tos/client/get_health.rs +++ b/sdk/examples/how_tos/client/get_health.rs @@ -8,10 +8,10 @@ //! cargo run --release --all-features --example get_health [NODE_URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/client/get_info.rs b/sdk/examples/how_tos/client/get_info.rs index a5d6251471..ea8444752e 100644 --- a/sdk/examples/how_tos/client/get_info.rs +++ b/sdk/examples/how_tos/client/get_info.rs @@ -8,10 +8,10 @@ //! cargo run --release --all-features --example get_info [NODE URL] //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // If not provided we use the default node from the `.env` file. dotenvy::dotenv().ok(); @@ -27,8 +27,8 @@ async fn main() -> Result<()> { .finish() .await?; - // Get node info. - let info = client.get_info().await?.node_info; + // Get info. + let info = client.get_node_info().await?.info; println!("{info}"); diff --git a/sdk/examples/how_tos/client/get_outputs.rs b/sdk/examples/how_tos/client/get_outputs.rs index ec0819c4e1..0997857bc8 100644 --- a/sdk/examples/how_tos/client/get_outputs.rs +++ b/sdk/examples/how_tos/client/get_outputs.rs @@ -13,12 +13,12 @@ //! ``` use iota_sdk::{ - client::{node_api::indexer::query_parameters::BasicOutputQueryParameters, Client, Result}, + client::{node_api::indexer::query_parameters::BasicOutputQueryParameters, Client}, types::block::address::Bech32Address, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/native_tokens/burn.rs b/sdk/examples/how_tos/native_tokens/burn.rs index 48f72d69a1..0150d543e0 100644 --- a/sdk/examples/how_tos/native_tokens/burn.rs +++ b/sdk/examples/how_tos/native_tokens/burn.rs @@ -18,7 +18,6 @@ use iota_sdk::{ types::block::output::{NativeToken, TokenId}, - wallet::Result, Wallet, U256, }; @@ -28,7 +27,7 @@ const MIN_AVAILABLE_AMOUNT: u64 = 11; const BURN_AMOUNT: u64 = 1; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -66,13 +65,14 @@ async fn main() -> Result<()> { let transaction = wallet.burn(NativeToken::new(token_id, BURN_AMOUNT)?, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); let balance = wallet.sync(None).await?; diff --git a/sdk/examples/how_tos/native_tokens/create.rs b/sdk/examples/how_tos/native_tokens/create.rs index be45eaedff..151fdd15b3 100644 --- a/sdk/examples/how_tos/native_tokens/create.rs +++ b/sdk/examples/how_tos/native_tokens/create.rs @@ -11,11 +11,7 @@ //! cargo run --release --all-features --example create_native_token //! ``` -use iota_sdk::{ - types::block::output::feature::Irc30Metadata, - wallet::{CreateNativeTokenParams, Result}, - Wallet, U256, -}; +use iota_sdk::{types::block::output::feature::Irc30Metadata, wallet::CreateNativeTokenParams, Wallet, U256}; // The circulating supply of the native token const CIRCULATING_SUPPLY: u64 = 100; @@ -23,7 +19,7 @@ const CIRCULATING_SUPPLY: u64 = 100; const MAXIMUM_SUPPLY: u64 = 100; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -50,14 +46,13 @@ async fn main() -> Result<()> { let transaction = wallet.create_account_output(None, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); wallet.sync(None).await?; @@ -79,14 +74,14 @@ async fn main() -> Result<()> { let transaction = wallet.create_native_token(params, None).await?; println!("Transaction sent: {}", transaction.transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction.transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction.transaction_id ); println!("Created token: {}", transaction.token_id); diff --git a/sdk/examples/how_tos/native_tokens/destroy_foundry.rs b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs index 7fcfa7a5e3..e16b03d10b 100644 --- a/sdk/examples/how_tos/native_tokens/destroy_foundry.rs +++ b/sdk/examples/how_tos/native_tokens/destroy_foundry.rs @@ -12,10 +12,10 @@ //! cargo run --release --all-features --example destroy_foundry //! ``` -use iota_sdk::{types::block::output::TokenId, wallet::Result, Wallet}; +use iota_sdk::{types::block::output::TokenId, Wallet}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -61,13 +61,14 @@ async fn main() -> Result<()> { .await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); // Sync to make the foundry output available again, because it was used in the melting transaction. @@ -79,13 +80,14 @@ async fn main() -> Result<()> { println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); // Resync to update the foundries list. diff --git a/sdk/examples/how_tos/native_tokens/melt.rs b/sdk/examples/how_tos/native_tokens/melt.rs index 24495ced70..9be0bd492e 100644 --- a/sdk/examples/how_tos/native_tokens/melt.rs +++ b/sdk/examples/how_tos/native_tokens/melt.rs @@ -16,13 +16,13 @@ //! cargo run --release --all-features --example melt_native_token [TOKEN_ID] //! ``` -use iota_sdk::{types::block::output::TokenId, wallet::Result, Wallet}; +use iota_sdk::{types::block::output::TokenId, Wallet}; // The amount of native tokens to melt const MELT_AMOUNT: u64 = 10; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -62,14 +62,14 @@ async fn main() -> Result<()> { let transaction = wallet.melt_native_token(token_id, MELT_AMOUNT, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); let balance = wallet.sync(None).await?; diff --git a/sdk/examples/how_tos/native_tokens/mint.rs b/sdk/examples/how_tos/native_tokens/mint.rs index a424e8e043..c6a1f77997 100644 --- a/sdk/examples/how_tos/native_tokens/mint.rs +++ b/sdk/examples/how_tos/native_tokens/mint.rs @@ -16,13 +16,13 @@ //! cargo run --release --all-features --example mint_native_token [TOKEN_ID] //! ``` -use iota_sdk::{types::block::output::TokenId, wallet::Result, Wallet}; +use iota_sdk::{types::block::output::TokenId, Wallet}; // The amount of native tokens to mint const MINT_AMOUNT: u64 = 10; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -61,14 +61,14 @@ async fn main() -> Result<()> { let transaction = wallet.mint_native_token(token_id, MINT_AMOUNT, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); let balance = wallet.sync(None).await?; diff --git a/sdk/examples/how_tos/native_tokens/send.rs b/sdk/examples/how_tos/native_tokens/send.rs index 083cfbfa0b..e4be141e02 100644 --- a/sdk/examples/how_tos/native_tokens/send.rs +++ b/sdk/examples/how_tos/native_tokens/send.rs @@ -11,11 +11,7 @@ //! cargo run --release --all-features --example send_native_tokens //! ``` -use iota_sdk::{ - types::block::address::Bech32Address, - wallet::{Result, SendNativeTokenParams}, - Wallet, -}; +use iota_sdk::{types::block::address::Bech32Address, wallet::SendNativeTokenParams, Wallet}; use primitive_types::U256; // The native token amount to send @@ -24,7 +20,7 @@ const SEND_NATIVE_TOKEN_AMOUNT: u64 = 10; const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -64,14 +60,14 @@ async fn main() -> Result<()> { let transaction = wallet.send_native_tokens(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); let balance = wallet.sync(None).await?; diff --git a/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs b/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs index add3ea962c..83b60cb1b8 100644 --- a/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs +++ b/sdk/examples/how_tos/nft_collection/00_mint_issuer_nft.rs @@ -19,12 +19,12 @@ use iota_sdk::{ output::{feature::MetadataFeature, NftId, Output, OutputId}, payload::signed_transaction::TransactionId, }, - wallet::{MintNftParams, Result}, + wallet::MintNftParams, Wallet, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -72,20 +72,21 @@ async fn main() -> Result<()> { Ok(()) } -async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<(), Box> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction_id ); + Ok(()) } diff --git a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs index a8e02528d3..fed0087d6e 100644 --- a/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs +++ b/sdk/examples/how_tos/nft_collection/01_mint_collection_nft.rs @@ -23,7 +23,7 @@ use iota_sdk::{ }, payload::signed_transaction::TransactionId, }, - wallet::{MintNftParams, Result}, + wallet::MintNftParams, Wallet, }; @@ -33,7 +33,7 @@ const NFT_COLLECTION_SIZE: usize = 150; const NUM_NFTS_MINTED_PER_TRANSACTION: usize = 50; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -111,20 +111,21 @@ fn get_immutable_metadata(index: usize) -> Irc27Metadata { .with_collection_name("Shimmer OG") } -async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<(), Box> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction_id ); + Ok(()) } diff --git a/sdk/examples/how_tos/nfts/burn_nft.rs b/sdk/examples/how_tos/nfts/burn_nft.rs index 5ed95bc8f3..e5c944824d 100644 --- a/sdk/examples/how_tos/nfts/burn_nft.rs +++ b/sdk/examples/how_tos/nfts/burn_nft.rs @@ -11,10 +11,10 @@ //! cargo run --release --all-features --example burn_nft //! ``` -use iota_sdk::{wallet::Result, Wallet}; +use iota_sdk::Wallet; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -44,14 +44,14 @@ async fn main() -> Result<()> { let transaction = wallet.burn(*nft_id, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); println!("Burned NFT '{}'", nft_id); diff --git a/sdk/examples/how_tos/nfts/mint_nft.rs b/sdk/examples/how_tos/nfts/mint_nft.rs index 616b55f138..faf6f67e7f 100644 --- a/sdk/examples/how_tos/nfts/mint_nft.rs +++ b/sdk/examples/how_tos/nfts/mint_nft.rs @@ -17,7 +17,7 @@ use iota_sdk::{ unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, }, - wallet::{MintNftParams, Result}, + wallet::MintNftParams, Wallet, }; @@ -31,7 +31,7 @@ const NFT1_TAG: &str = "some NFT tag"; const NFT2_AMOUNT: u64 = 1_000_000; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -81,14 +81,13 @@ async fn main() -> Result<()> { let transaction = wallet.mint_nfts(nft_params, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); println!("Minted NFT 1"); @@ -105,14 +104,13 @@ async fn main() -> Result<()> { let transaction = wallet.send_outputs(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); println!("Minted NFT 2"); diff --git a/sdk/examples/how_tos/nfts/send_nft.rs b/sdk/examples/how_tos/nfts/send_nft.rs index 5e045e9e7f..b5804c7629 100644 --- a/sdk/examples/how_tos/nfts/send_nft.rs +++ b/sdk/examples/how_tos/nfts/send_nft.rs @@ -11,16 +11,13 @@ //! cargo run --release --all-features --example send_nft //! ``` -use iota_sdk::{ - wallet::{Result, SendNftParams}, - Wallet, -}; +use iota_sdk::{wallet::SendNftParams, Wallet}; // The address to send the tokens to const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -51,15 +48,14 @@ async fn main() -> Result<()> { let transaction = wallet.send_nft(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); } else { println!("No available NFTs"); diff --git a/sdk/examples/how_tos/outputs/features.rs b/sdk/examples/how_tos/outputs/features.rs index 25e2d8ac66..46dcdd30c4 100644 --- a/sdk/examples/how_tos/outputs/features.rs +++ b/sdk/examples/how_tos/outputs/features.rs @@ -9,7 +9,7 @@ //! ``` use iota_sdk::{ - client::{Client, Result}, + client::Client, types::block::{ address::Address, output::{ @@ -21,7 +21,7 @@ use iota_sdk::{ }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/outputs/unlock_conditions.rs b/sdk/examples/how_tos/outputs/unlock_conditions.rs index 600d921562..61070da921 100644 --- a/sdk/examples/how_tos/outputs/unlock_conditions.rs +++ b/sdk/examples/how_tos/outputs/unlock_conditions.rs @@ -9,7 +9,7 @@ //! ``` use iota_sdk::{ - client::{Client, Result}, + client::Client, types::block::{ address::Address, output::{ @@ -23,7 +23,7 @@ use iota_sdk::{ }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.rs b/sdk/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.rs index 3a9b80bff6..59d112b403 100644 --- a/sdk/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.rs +++ b/sdk/examples/how_tos/sign_and_verify_ed25519/sign_ed25519.rs @@ -16,7 +16,6 @@ use iota_sdk::{ secret::{stronghold::StrongholdSecretManager, SecretManage, SecretManager}, }, crypto::keys::bip39::Mnemonic, - wallet::Result, }; const FOUNDRY_METADATA: &str = r#"{"standard":"IRC30","name":"NativeToken","description":"A native token","symbol":"NT","decimals":6,"logoUrl":"https://my.website/nativeToken.png"}"#; @@ -25,7 +24,7 @@ const INTERNAL_ADDRESS: bool = false; const ADDRESS_INDEX: u32 = 0; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/sign_and_verify_ed25519/verify_ed25519_signature.rs b/sdk/examples/how_tos/sign_and_verify_ed25519/verify_ed25519_signature.rs index 00e0f2a9b4..0c63a80017 100644 --- a/sdk/examples/how_tos/sign_and_verify_ed25519/verify_ed25519_signature.rs +++ b/sdk/examples/how_tos/sign_and_verify_ed25519/verify_ed25519_signature.rs @@ -7,13 +7,13 @@ //! cargo run --release --all-features --example verify_ed25519_signature //! ``` -use iota_sdk::{crypto::signatures::ed25519, types::block::Error}; +use iota_sdk::crypto::signatures::ed25519; const FOUNDRY_METADATA: &str = r#"{"standard":"IRC30","name":"NativeToken","description":"A native token","symbol":"NT","decimals":6,"logoUrl":"https://my.website/nativeToken.png"}"#; const PUBLIC_KEY: &str = "0x67b7fc3f78763c9394fc4fcdb52cf3a973b6e064bdc3defb40a6cb2c880e6f5c"; const ED25519_SIGNATURE: &str = "0x5437ee671f182507103c6ae2f6649083475019f2cc372e674be164577dd123edd7a76291ba88732bbe1fae39688b50a3678bce05c9ef32c9494b3968f4f07a01"; -fn main() -> Result<(), Error> { +fn main() -> Result<(), Box> { let ed25519_public_key = ed25519::PublicKey::try_from_bytes(prefix_hex::decode(PUBLIC_KEY).unwrap())?; let ed25519_signature = ed25519::Signature::from_bytes(prefix_hex::decode(ED25519_SIGNATURE).unwrap()); diff --git a/sdk/examples/how_tos/simple_transaction/request_funds.rs b/sdk/examples/how_tos/simple_transaction/request_funds.rs index d14c2820f3..767c6d1020 100644 --- a/sdk/examples/how_tos/simple_transaction/request_funds.rs +++ b/sdk/examples/how_tos/simple_transaction/request_funds.rs @@ -11,10 +11,10 @@ //! cargo run --release --all-features --example request_funds //! ``` -use iota_sdk::{client::request_funds_from_faucet, wallet::Result, Wallet}; +use iota_sdk::{client::request_funds_from_faucet, Wallet}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/simple_transaction/simple_transaction.rs b/sdk/examples/how_tos/simple_transaction/simple_transaction.rs index 933afb56ed..4269b60f9a 100644 --- a/sdk/examples/how_tos/simple_transaction/simple_transaction.rs +++ b/sdk/examples/how_tos/simple_transaction/simple_transaction.rs @@ -11,7 +11,7 @@ //! cargo run --release --all-features --example simple_transaction //! ``` -use iota_sdk::{wallet::Result, Wallet}; +use iota_sdk::Wallet; // The base coin amount to send const SEND_AMOUNT: u64 = 1_000_000; @@ -19,7 +19,7 @@ const SEND_AMOUNT: u64 = 1_000_000; const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -43,15 +43,14 @@ async fn main() -> Result<()> { println!("Trying to send '{}' coins to '{}'...", SEND_AMOUNT, RECV_ADDRESS); let transaction = wallet.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); Ok(()) diff --git a/sdk/examples/how_tos/wallet/check_balance.rs b/sdk/examples/how_tos/wallet/check_balance.rs index 419dfcf07e..c6eabca658 100644 --- a/sdk/examples/how_tos/wallet/check_balance.rs +++ b/sdk/examples/how_tos/wallet/check_balance.rs @@ -11,10 +11,10 @@ //! cargo run --release --all-features --example check_balance //! ``` -use iota_sdk::{wallet::Result, Wallet}; +use iota_sdk::Wallet; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/how_tos/wallet/consolidate_outputs.rs b/sdk/examples/how_tos/wallet/consolidate_outputs.rs index 8b9df45733..846f1afde6 100644 --- a/sdk/examples/how_tos/wallet/consolidate_outputs.rs +++ b/sdk/examples/how_tos/wallet/consolidate_outputs.rs @@ -12,13 +12,10 @@ //! cargo run --release --all-features --example consolidate_outputs //! ``` -use iota_sdk::{ - wallet::{ConsolidationParams, Result}, - Wallet, -}; +use iota_sdk::{wallet::ConsolidationParams, Wallet}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -49,7 +46,7 @@ async fn main() -> Result<()> { // output. println!("Outputs BEFORE consolidation:"); wallet - .data() + .ledger() .await .unspent_outputs() .values() @@ -73,13 +70,13 @@ async fn main() -> Result<()> { println!("Transaction sent: {}", transaction.transaction_id); // Wait for the consolidation transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); // Sync wallet @@ -89,7 +86,7 @@ async fn main() -> Result<()> { // Outputs after consolidation println!("Outputs AFTER consolidation:"); wallet - .data() + .ledger() .await .unspent_outputs() .values() diff --git a/sdk/examples/how_tos/wallet/create_mnemonic.rs b/sdk/examples/how_tos/wallet/create_mnemonic.rs index b74bf41f03..b394bb7478 100644 --- a/sdk/examples/how_tos/wallet/create_mnemonic.rs +++ b/sdk/examples/how_tos/wallet/create_mnemonic.rs @@ -7,10 +7,10 @@ //! `cargo run --release --all-features --example create_mnemonic` //! ``` -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::Client; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { let mnemonic = Client::generate_mnemonic()?; println!("Generated mnemonic:\n{}", mnemonic.as_ref()); diff --git a/sdk/examples/how_tos/wallet/create_wallet.rs b/sdk/examples/how_tos/wallet/create_wallet.rs index 8d9acb5a92..675eb524e2 100644 --- a/sdk/examples/how_tos/wallet/create_wallet.rs +++ b/sdk/examples/how_tos/wallet/create_wallet.rs @@ -16,11 +16,11 @@ use iota_sdk::{ secret::{stronghold::StrongholdSecretManager, SecretManager}, }, crypto::keys::{bip39::Mnemonic, bip44::Bip44}, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -48,7 +48,7 @@ async fn main() -> Result<()> { let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; // Create the wallet - let wallet = Wallet::builder() + Wallet::builder() .with_secret_manager(SecretManager::Stronghold(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) diff --git a/sdk/examples/how_tos/wallet/list_outputs.rs b/sdk/examples/how_tos/wallet/list_outputs.rs index fc5a77bde9..5bdb880c4f 100644 --- a/sdk/examples/how_tos/wallet/list_outputs.rs +++ b/sdk/examples/how_tos/wallet/list_outputs.rs @@ -8,10 +8,10 @@ //! cargo run --release --all-features --example list_outputs //! ``` -use iota_sdk::{wallet::Result, Wallet}; +use iota_sdk::Wallet; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -29,13 +29,13 @@ async fn main() -> Result<()> { // Print output ids println!("Output ids:"); - for output in wallet.data().await.outputs().values() { + for output in wallet.ledger().await.outputs().values() { println!("{}", output.output_id); } // Print unspent output ids println!("Unspent output ids:"); - for output in wallet.data().await.unspent_outputs().values() { + for output in wallet.ledger().await.unspent_outputs().values() { println!("{}", output.output_id); } diff --git a/sdk/examples/how_tos/wallet/list_transactions.rs b/sdk/examples/how_tos/wallet/list_transactions.rs index fea3b5685c..2d67acff0d 100644 --- a/sdk/examples/how_tos/wallet/list_transactions.rs +++ b/sdk/examples/how_tos/wallet/list_transactions.rs @@ -8,13 +8,10 @@ //! cargo run --release --all-features --example list_transactions //! ``` -use iota_sdk::{ - wallet::{Result, SyncOptions}, - Wallet, -}; +use iota_sdk::{wallet::SyncOptions, Wallet}; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -37,13 +34,13 @@ async fn main() -> Result<()> { // Print transaction ids println!("Sent transactions:"); - for transaction_id in wallet.data().await.transactions().keys() { + for transaction_id in wallet.ledger().await.transactions().keys() { println!("{}", transaction_id); } // Print received transaction ids println!("Received transactions:"); - for transaction_id in wallet.data().await.incoming_transactions().keys() { + for transaction_id in wallet.ledger().await.incoming_transactions().keys() { println!("{}", transaction_id); } diff --git a/sdk/examples/wallet/17_check_unlock_conditions.rs b/sdk/examples/wallet/17_check_unlock_conditions.rs index acaf1fe265..ed010b9895 100644 --- a/sdk/examples/wallet/17_check_unlock_conditions.rs +++ b/sdk/examples/wallet/17_check_unlock_conditions.rs @@ -12,7 +12,6 @@ use iota_sdk::{ types::block::output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, UnlockCondition}, - wallet::Result, Wallet, }; @@ -20,7 +19,7 @@ use iota_sdk::{ const AMOUNT: u64 = 1_000_000; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/wallet/background_syncing.rs b/sdk/examples/wallet/background_syncing.rs index ea2505a758..b39b180ba1 100644 --- a/sdk/examples/wallet/background_syncing.rs +++ b/sdk/examples/wallet/background_syncing.rs @@ -15,11 +15,11 @@ use iota_sdk::{ secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, crypto::keys::bip44::Bip44, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/wallet/events.rs b/sdk/examples/wallet/events.rs index 24d972c47d..798eff5853 100644 --- a/sdk/examples/wallet/events.rs +++ b/sdk/examples/wallet/events.rs @@ -19,7 +19,7 @@ use iota_sdk::{ address::Address, output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder}, }, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; // The amount of base coins we'll send @@ -28,7 +28,7 @@ const SEND_AMOUNT: u64 = 1_000_000; const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -65,14 +65,14 @@ async fn main() -> Result<()> { let transaction = wallet.send_outputs(outputs, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); let balance = wallet.sync(None).await?; diff --git a/sdk/examples/wallet/getting_started.rs b/sdk/examples/wallet/getting_started.rs index 72d7aa4250..64216e879e 100644 --- a/sdk/examples/wallet/getting_started.rs +++ b/sdk/examples/wallet/getting_started.rs @@ -14,11 +14,11 @@ use iota_sdk::{ secret::{stronghold::StrongholdSecretManager, SecretManager}, }, crypto::keys::bip44::Bip44, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/wallet/ledger_nano.rs b/sdk/examples/wallet/ledger_nano.rs index 8334ef9f2e..bdb9391db3 100644 --- a/sdk/examples/wallet/ledger_nano.rs +++ b/sdk/examples/wallet/ledger_nano.rs @@ -20,16 +20,14 @@ use iota_sdk::{ secret::{ledger_nano::LedgerSecretManager, SecretManager}, }, crypto::keys::bip44::Bip44, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; -// The address to send coins to -const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; // The amount of base coins we'll send const SEND_AMOUNT: u64 = 1_000_000; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -38,24 +36,20 @@ async fn main() -> Result<()> { } let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; - let secret_manager = LedgerSecretManager::new(true); + let secret_manager = SecretManager::LedgerNano(LedgerSecretManager::new(true)); + let wallet = Wallet::builder() - .with_secret_manager(SecretManager::LedgerNano(secret_manager)) + .with_secret_manager(secret_manager) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; + let recv_address = wallet.address().await; + println!("recipient address: {recv_address}"); println!("{:?}", wallet.get_ledger_nano_status().await?); - println!("Generating address..."); - let now = tokio::time::Instant::now(); - let address = wallet.generate_ed25519_address(0, 0, None).await?; - println!("took: {:.2?}", now.elapsed()); - - println!("ADDRESS:\n{address:#?}"); - let now = tokio::time::Instant::now(); let balance = wallet.sync(None).await?; println!("Wallet synced in: {:.2?}", now.elapsed()); @@ -63,16 +57,17 @@ async fn main() -> Result<()> { println!("Balance BEFORE:\n{:?}", balance.base_coin()); println!("Sending the coin-transfer transaction..."); - let transaction = wallet.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; + let transaction = wallet.send(SEND_AMOUNT, recv_address, None).await?; println!("Transaction sent: {}", transaction.transaction_id); - let block_id = wallet + wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction.transaction_id ); let now = tokio::time::Instant::now(); diff --git a/sdk/examples/wallet/logger.rs b/sdk/examples/wallet/logger.rs index bb944a5808..907a75120d 100644 --- a/sdk/examples/wallet/logger.rs +++ b/sdk/examples/wallet/logger.rs @@ -10,15 +10,16 @@ use iota_sdk::{ client::{ + api::GetAddressesOptions, constants::SHIMMER_COIN_TYPE, secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, crypto::keys::bip44::Bip44, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -38,18 +39,20 @@ async fn main() -> Result<()> { // Restore a wallet let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; + let secret_manager = SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( + std::env::var("MNEMONIC").unwrap(), + )?); + let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) + + let wallet = Wallet::::builder() .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) + .with_secret_manager(secret_manager) .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - println!("Generating address..."); - let _ = wallet.generate_ed25519_address(0, 0, None).await?; - println!("Syncing wallet"); wallet.sync(None).await?; diff --git a/sdk/examples/wallet/migrate_stronghold_snapshot_v2_to_v3.rs b/sdk/examples/wallet/migrate_stronghold_snapshot_v2_to_v3.rs index 536f12dca7..8b47726162 100644 --- a/sdk/examples/wallet/migrate_stronghold_snapshot_v2_to_v3.rs +++ b/sdk/examples/wallet/migrate_stronghold_snapshot_v2_to_v3.rs @@ -12,14 +12,13 @@ use iota_sdk::client::{ constants::{SHIMMER_COIN_TYPE, SHIMMER_TESTNET_BECH32_HRP}, secret::{stronghold::StrongholdSecretManager, SecretManager}, stronghold::StrongholdAdapter, - Result, }; const V2_PATH: &str = "./tests/wallet/fixtures/v2.stronghold"; const V3_PATH: &str = "./v3.stronghold"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This should fail with error, migration required. let error = if let Err(e) = StrongholdSecretManager::builder() .password("current_password".to_owned()) diff --git a/sdk/examples/wallet/offline_signing/0_generate_address.rs b/sdk/examples/wallet/offline_signing/0_generate_address.rs index 1c4b65e3fb..c8f6d21b19 100644 --- a/sdk/examples/wallet/offline_signing/0_generate_address.rs +++ b/sdk/examples/wallet/offline_signing/0_generate_address.rs @@ -14,7 +14,8 @@ use iota_sdk::{ secret::{stronghold::StrongholdSecretManager, SecretManager}, }, crypto::keys::{bip39::Mnemonic, bip44::Bip44}, - wallet::{ClientOptions, Result, Wallet}, + types::block::address::Bech32Address, + wallet::{ClientOptions, Wallet}, }; const OFFLINE_WALLET_DB_PATH: &str = "./examples/wallet/offline_signing/example-offline-walletdb"; @@ -22,7 +23,7 @@ const STRONGHOLD_SNAPSHOT_PATH: &str = "./examples/wallet/offline_signing/exampl const ADDRESS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.address.json"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -50,17 +51,17 @@ async fn main() -> Result<()> { .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - println!("Generated a new wallet"); - write_wallet_address_to_file(&wallet).await + write_wallet_address_to_file(&wallet.address().await).await?; + + Ok(()) } -async fn write_wallet_address_to_file(wallet: &Wallet) -> Result<()> { +async fn write_wallet_address_to_file(address: &Bech32Address) -> Result<(), Box> { use tokio::io::AsyncWriteExt; - let wallet_address = wallet.address().await; - let json = serde_json::to_string_pretty(&wallet_address)?; + let json = serde_json::to_string_pretty(address)?; let mut file = tokio::io::BufWriter::new(tokio::fs::File::create(ADDRESS_FILE_PATH).await?); println!("example.address.json:\n{json}"); file.write_all(json.as_bytes()).await?; diff --git a/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs b/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs index 67738e46eb..93c374fc96 100644 --- a/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs +++ b/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs @@ -9,22 +9,26 @@ //! ``` use iota_sdk::{ - client::{api::PreparedTransactionDataDto, constants::SHIMMER_COIN_TYPE, secret::SecretManager}, + client::{ + api::PreparedTransactionDataDto, constants::SHIMMER_COIN_TYPE, secret::SecretManager, + stronghold::StrongholdAdapter, + }, crypto::keys::bip44::Bip44, - wallet::{types::Bip44Address, ClientOptions, Result, SendParams, Wallet}, + types::block::address::Bech32Address, + wallet::{ClientOptions, SendParams, Wallet}, }; const ONLINE_WALLET_DB_PATH: &str = "./examples/wallet/offline_signing/example-online-walletdb"; -const ADDRESS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.address.json"; const PREPARED_TRANSACTION_FILE_PATH: &str = "./examples/wallet/offline_signing/example.prepared_transaction.json"; const PROTOCOL_PARAMETERS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.protocol_parameters.json"; +const ADDRESS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.address.json"; // Address to which we want to send the amount. const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; // The amount to send. const SEND_AMOUNT: u64 = 1_000_000; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -34,8 +38,8 @@ async fn main() -> Result<()> { let params = [SendParams::new(SEND_AMOUNT, RECV_ADDRESS)?]; - // Recovers addresses from example `0_address_generation`. - let address = read_address_from_file().await?.into_bech32(); + // Recovers the address generated by the example `0_generate_address.rs`. + let address = read_address_from_file().await?; let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; @@ -44,8 +48,8 @@ async fn main() -> Result<()> { .with_secret_manager(SecretManager::Placeholder) .with_storage_path(ONLINE_WALLET_DB_PATH) .with_client_options(client_options.clone()) - .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .with_address(address) + .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; @@ -71,7 +75,7 @@ async fn main() -> Result<()> { Ok(()) } -async fn read_address_from_file() -> Result { +async fn read_address_from_file() -> Result> { use tokio::io::AsyncReadExt; let mut file = tokio::io::BufReader::new(tokio::fs::File::open(ADDRESS_FILE_PATH).await?); @@ -81,7 +85,7 @@ async fn read_address_from_file() -> Result { Ok(serde_json::from_str(&json)?) } -async fn write_data_to_file(data: impl serde::Serialize, path: &str) -> Result<()> { +async fn write_data_to_file(data: impl serde::Serialize, path: &str) -> Result<(), Box> { use tokio::io::AsyncWriteExt; let json = serde_json::to_string_pretty(&data)?; diff --git a/sdk/examples/wallet/offline_signing/2_sign_transaction.rs b/sdk/examples/wallet/offline_signing/2_sign_transaction.rs index 7a5c8d244d..dabf816d6a 100644 --- a/sdk/examples/wallet/offline_signing/2_sign_transaction.rs +++ b/sdk/examples/wallet/offline_signing/2_sign_transaction.rs @@ -10,14 +10,10 @@ use iota_sdk::{ client::{ - api::{ - transaction::validate_signed_transaction_payload_length, PreparedTransactionData, SignedTransactionData, - SignedTransactionDataDto, - }, + api::{PreparedTransactionData, SignedTransactionData, SignedTransactionDataDto}, secret::{stronghold::StrongholdSecretManager, SecretManage, SecretManager}, }, types::{block::payload::SignedTransactionPayload, TryFromDto}, - wallet::Result, }; const STRONGHOLD_SNAPSHOT_PATH: &str = "./examples/wallet/offline_signing/example.stronghold"; @@ -26,7 +22,7 @@ const PROTOCOL_PARAMETERS_FILE_PATH: &str = "./examples/wallet/offline_signing/e const SIGNED_TRANSACTION_FILE_PATH: &str = "./examples/wallet/offline_signing/example.signed_transaction.json"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -52,7 +48,7 @@ async fn main() -> Result<()> { let signed_transaction = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&signed_transaction)?; + signed_transaction.validate_length()?; let signed_transaction_data = SignedTransactionData { payload: signed_transaction, @@ -67,7 +63,7 @@ async fn main() -> Result<()> { Ok(()) } -async fn read_data_from_file(path: &str) -> Result { +async fn read_data_from_file(path: &str) -> Result> { use tokio::io::AsyncReadExt; let mut file = tokio::io::BufReader::new(tokio::fs::File::open(path).await?); @@ -77,7 +73,9 @@ async fn read_data_from_file(path: &str) -> Result { Ok(json) } -async fn write_signed_transaction_to_file(signed_transaction_data: &SignedTransactionData) -> Result<()> { +async fn write_signed_transaction_to_file( + signed_transaction_data: &SignedTransactionData, +) -> Result<(), Box> { use tokio::io::AsyncWriteExt; let dto = SignedTransactionDataDto::from(signed_transaction_data); diff --git a/sdk/examples/wallet/offline_signing/3_send_transaction.rs b/sdk/examples/wallet/offline_signing/3_send_transaction.rs index aaaef1c8a1..d421b4eb75 100644 --- a/sdk/examples/wallet/offline_signing/3_send_transaction.rs +++ b/sdk/examples/wallet/offline_signing/3_send_transaction.rs @@ -15,7 +15,6 @@ use iota_sdk::{ Client, }, types::{block::payload::signed_transaction::TransactionId, TryFromDto}, - wallet::Result, Wallet, }; @@ -23,7 +22,7 @@ const ONLINE_WALLET_DB_PATH: &str = "./examples/wallet/offline_signing/example-o const SIGNED_TRANSACTION_FILE_PATH: &str = "./examples/wallet/offline_signing/example.signed_transaction.json"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -49,7 +48,9 @@ async fn main() -> Result<()> { Ok(()) } -async fn read_signed_transaction_from_file(client: &Client) -> Result { +async fn read_signed_transaction_from_file( + client: &Client, +) -> Result> { use tokio::io::AsyncReadExt; let mut file = tokio::io::BufReader::new(tokio::fs::File::open(SIGNED_TRANSACTION_FILE_PATH).await?); @@ -64,20 +65,21 @@ async fn read_signed_transaction_from_file(client: &Client) -> Result Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<(), Box> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction_id ); + Ok(()) } diff --git a/sdk/examples/wallet/participation.rs b/sdk/examples/wallet/participation.rs index 3720d33dcc..6944c07e53 100644 --- a/sdk/examples/wallet/participation.rs +++ b/sdk/examples/wallet/participation.rs @@ -38,7 +38,7 @@ const INCREASE_VOTING_POWER_AMOUNT: u64 = 1000001; const DECREASE_VOTING_POWER_AMOUNT: u64 = 1; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); diff --git a/sdk/examples/wallet/spammer.rs b/sdk/examples/wallet/spammer.rs index 25f0ece4d4..52786a6f73 100644 --- a/sdk/examples/wallet/spammer.rs +++ b/sdk/examples/wallet/spammer.rs @@ -20,7 +20,7 @@ use iota_sdk::{ output::BasicOutput, payload::signed_transaction::TransactionId, }, - wallet::{ClientOptions, FilterOptions, Result, SendParams, Wallet}, + wallet::{ClientOptions, FilterOptions, SendParams, Wallet}, }; // The number of spamming rounds. @@ -31,7 +31,7 @@ const SEND_AMOUNT: u64 = 1_000_000; const NUM_SIMULTANEOUS_TXS: usize = 16; #[tokio::main(flavor = "multi_thread")] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -48,20 +48,10 @@ async fn main() -> Result<()> { let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; let bip_path = Bip44::new(SHIMMER_COIN_TYPE); - let address = Bech32Address::new( - Hrp::from_str_unchecked("smr"), - Address::from( - secret_manager - .generate_ed25519_addresses(bip_path.coin_type, bip_path.account, 0..1, None) - .await?[0], - ), - ); - let wallet = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_client_options(client_options) .with_bip_path(bip_path) - .with_address(address) .finish() .await?; @@ -74,7 +64,7 @@ async fn main() -> Result<()> { // We make sure that for all threads there are always inputs available to // fund the transaction, otherwise we create enough unspent outputs. let num_unspent_basic_outputs_with_send_amount = wallet - .data() + .ledger() .await .filtered_unspent_outputs(FilterOptions { output_types: Some(vec![BasicOutput::KIND]), @@ -101,7 +91,7 @@ async fn main() -> Result<()> { println!("ROUND {i}/{NUM_ROUNDS}"); let round_timer = tokio::time::Instant::now(); - let mut tasks = tokio::task::JoinSet::>::new(); + let mut tasks = tokio::task::JoinSet::>::new(); for n in 0..num_simultaneous_txs { let recv_address = recv_address.clone(); @@ -160,7 +150,10 @@ async fn main() -> Result<()> { Ok(()) } -async fn ensure_enough_funds(wallet: &Wallet, bech32_address: &Bech32Address) -> Result<()> { +async fn ensure_enough_funds( + wallet: &Wallet, + bech32_address: &Bech32Address, +) -> Result<(), Box> { let balance = wallet.sync(None).await?; let available_funds = balance.base_coin().available(); println!("Available funds: {available_funds}"); @@ -201,20 +194,21 @@ async fn ensure_enough_funds(wallet: &Wallet, bech32_address: &Bech32Address) -> } } -async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<(), Box> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction_id ); + Ok(()) } diff --git a/sdk/examples/wallet/storage.rs b/sdk/examples/wallet/storage.rs index c901a155e3..a96d849364 100644 --- a/sdk/examples/wallet/storage.rs +++ b/sdk/examples/wallet/storage.rs @@ -14,11 +14,11 @@ use iota_sdk::{ secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, crypto::keys::bip44::Bip44, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -48,8 +48,7 @@ async fn main() -> Result<()> { Ok(()) } -async fn sync_print_balance(wallet: &Wallet) -> Result<()> { - let alias = wallet.alias().await; +async fn sync_print_balance(wallet: &Wallet) -> Result<(), Box> { let now = tokio::time::Instant::now(); let balance = wallet.sync(None).await?; println!("Wallet synced in: {:.2?}", now.elapsed()); diff --git a/sdk/examples/wallet/wallet.rs b/sdk/examples/wallet/wallet.rs index a9d8bae81e..5405febc93 100644 --- a/sdk/examples/wallet/wallet.rs +++ b/sdk/examples/wallet/wallet.rs @@ -21,7 +21,7 @@ use iota_sdk::{ secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, types::block::payload::signed_transaction::TransactionId, - wallet::{ClientOptions, Result, Wallet}, + wallet::{ClientOptions, Wallet}, }; // The amount of coins to send @@ -30,7 +30,7 @@ const SEND_AMOUNT: u64 = 1_000_000; const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> Result<(), Box> { // This example uses secrets in environment variables for simplicity which should not be done in production. dotenvy::dotenv().ok(); @@ -53,24 +53,19 @@ async fn main() -> Result<()> { Ok(()) } -async fn create_wallet() -> Result { +async fn create_wallet() -> Result> { let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - Wallet::builder() + Ok(Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() - .await + .await?) } -async fn print_address(wallet: &Wallet) -> Result<()> { - println!("Wallet address: {}", wallet.address().await); - Ok(()) -} - -async fn sync_print_balance(wallet: &Wallet, full_report: bool) -> Result<()> { +async fn sync_print_balance(wallet: &Wallet, full_report: bool) -> Result<(), Box> { let now = tokio::time::Instant::now(); let balance = wallet.sync(None).await?; println!("Wallet synced in: {:.2?}", now.elapsed()); @@ -82,20 +77,21 @@ async fn sync_print_balance(wallet: &Wallet, full_report: bool) -> Result<()> { Ok(()) } -async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<()> { +async fn wait_for_inclusion(transaction_id: &TransactionId, wallet: &Wallet) -> Result<(), Box> { println!( "Transaction sent: {}/transaction/{}", std::env::var("EXPLORER_URL").unwrap(), transaction_id ); - // Wait for transaction to get accepted - let block_id = wallet + wallet .wait_for_transaction_acceptance(transaction_id, None, None) .await?; + println!( - "Tx accepted in block: {}/block/{}", + "Tx accepted: {}/transactions/{}", std::env::var("EXPLORER_URL").unwrap(), - block_id + transaction_id ); + Ok(()) } diff --git a/sdk/src/client/api/address.rs b/sdk/src/client/api/address.rs index 6d5501d4d7..d8a70a96b4 100644 --- a/sdk/src/client/api/address.rs +++ b/sdk/src/client/api/address.rs @@ -10,12 +10,15 @@ use crate::{ client::{ constants::{SHIMMER_COIN_TYPE, SHIMMER_TESTNET_BECH32_HRP}, secret::{GenerateAddressOptions, SecretManage, SecretManager}, - Client, Result, + Client, ClientError, }, types::block::address::{Address, Bech32Address, Hrp, ToBech32Ext}, utils::ConvertTo, }; +// TODO: Should we rename ths struct to `GetAddressOptions`, thereby moving out the `range` field, so +// it can be used by `GenerateEd25519Address` and `GenerateEd25519Addresses`? Do we even still need +// the latter? #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(default)] @@ -33,7 +36,10 @@ pub struct GetAddressesOptions { } impl GetAddressesOptions { - pub async fn from_client(client: &Client) -> Result { + // TODO: can we remove this function? It's not clear from the outside that it's just the default + // with a requested HRP. I think the caller can just do what this function does. Also ... with this + // we do several API requests unnecessarily since oftentimes we could just re-use the HRP. + pub async fn from_client(client: &Client) -> Result { Ok(Self::default().with_bech32_hrp(client.get_bech32_hrp().await?)) } @@ -62,7 +68,7 @@ impl GetAddressesOptions { } /// Set bech32 human readable part (hrp) from something that might be valid - pub fn try_with_bech32_hrp(mut self, bech32_hrp: impl ConvertTo) -> Result { + pub fn try_with_bech32_hrp(mut self, bech32_hrp: impl ConvertTo) -> Result { self.bech32_hrp = bech32_hrp.convert()?; Ok(self) } @@ -97,7 +103,34 @@ impl Default for GetAddressesOptions { } impl SecretManager { - /// Get a vector of public bech32 addresses + // TODO: while `SecretManage::generate...` returns `Ed25519Address`, `SecretManager` + // converts those to `Bech32Address`es, hence, should we add `bech32` to its method name + // to make that the difference clear? + // TODO: make `account_index` and `address_index` impl Into>? + /// Generates a Bech32 formatted Ed25519 address. + pub async fn generate_ed25519_address( + &self, + coin_type: u32, + account_index: u32, + address_index: u32, + bech32_hrp: impl ConvertTo, + options: impl Into> + Send, + ) -> Result { + let hrp: Hrp = bech32_hrp.convert()?; + Ok(SecretManage::generate_ed25519_addresses( + self, + coin_type, + account_index, + address_index..address_index + 1, + options, + ) + // Panic: if the secret manager hasn't failed then there must be an address. + .await?[0] + .to_bech32(hrp)) + } + + // TODO: Same as for `generate_ed25519_address`. + /// Generates a vector of Bech32 formatted Ed25519 addresses. pub async fn generate_ed25519_addresses( &self, GetAddressesOptions { @@ -107,7 +140,7 @@ impl SecretManager { bech32_hrp, options, }: GetAddressesOptions, - ) -> Result> { + ) -> Result, ClientError> { Ok( SecretManage::generate_ed25519_addresses(self, coin_type, account_index, range, options) .await? @@ -117,7 +150,29 @@ impl SecretManager { ) } - /// Get a vector of EVM address strings + /// Generates a single EVM address hex string. + pub async fn generate_evm_address( + &self, + coin_type: u32, + account_index: u32, + address_index: u32, + options: impl Into> + Send, + ) -> Result { + Ok(prefix_hex::encode( + SecretManage::generate_evm_addresses( + self, + coin_type, + account_index, + address_index..address_index + 1, + options, + ) + // Panic: if the secret manager hasn't failed then there must be an address. + .await?[0] + .as_ref(), + )) + } + + /// Generates a vector of EVM address hex strings. pub async fn generate_evm_addresses( &self, GetAddressesOptions { @@ -127,7 +182,7 @@ impl SecretManager { options, .. }: GetAddressesOptions, - ) -> Result> { + ) -> Result, ClientError> { Ok( SecretManage::generate_evm_addresses(self, coin_type, account_index, range, options) .await? @@ -146,7 +201,7 @@ pub async fn search_address( account_index: u32, range: Range, address: &Address, -) -> Result<(u32, bool)> { +) -> Result<(u32, bool), ClientError> { let opts = GetAddressesOptions::default() .with_coin_type(coin_type) .with_account_index(account_index) @@ -161,7 +216,7 @@ pub async fn search_address( return Ok((range.start + index as u32, true)); } } - Err(crate::client::Error::InputAddressNotFound { + Err(ClientError::InputAddressNotFound { address: address.clone().to_bech32(bech32_hrp).to_string(), range: format!("{range:?}"), }) diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs b/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs deleted file mode 100644 index 9a45039fb6..0000000000 --- a/sdk/src/client/api/block_builder/input_selection/requirement/amount.rs +++ /dev/null @@ -1,443 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::{HashMap, HashSet}; - -use super::{native_tokens::get_native_tokens, Error, InputSelection, Requirement}; -use crate::{ - client::secret::types::InputSigningData, - types::block::{ - address::Address, - input::INPUT_COUNT_MAX, - output::{ - unlock_condition::StorageDepositReturnUnlockCondition, AccountOutputBuilder, FoundryOutputBuilder, - MinimumOutputAmount, NftOutputBuilder, Output, OutputId, TokenId, - }, - slot::SlotIndex, - }, -}; - -/// Get the `StorageDepositReturnUnlockCondition`, if not expired. -pub(crate) fn sdruc_not_expired( - output: &Output, - slot_index: SlotIndex, -) -> Option<&StorageDepositReturnUnlockCondition> { - // PANIC: safe to unwrap as outputs without unlock conditions have been filtered out already. - let unlock_conditions = output.unlock_conditions().unwrap(); - - unlock_conditions.storage_deposit_return().and_then(|sdr| { - let expired = unlock_conditions - .expiration() - .map_or(false, |expiration| slot_index >= expiration.slot_index()); - - // We only have to send the storage deposit return back if the output is not expired - if !expired { Some(sdr) } else { None } - }) -} - -impl InputSelection { - pub(crate) fn amount_sums(&self) -> (u64, u64, HashMap, HashMap) { - let mut inputs_sum = 0; - let mut outputs_sum = 0; - let mut inputs_sdr = HashMap::new(); - let mut outputs_sdr = HashMap::new(); - - for selected_input in &self.selected_inputs { - inputs_sum += selected_input.output.amount(); - - if let Some(sdruc) = sdruc_not_expired(&selected_input.output, self.creation_slot) { - *inputs_sdr.entry(sdruc.return_address().clone()).or_default() += sdruc.amount(); - } - } - - for output in self.non_remainder_outputs() { - outputs_sum += output.amount(); - - if let Output::Basic(output) = output { - if let Some(address) = output.simple_deposit_address() { - *outputs_sdr.entry(address.clone()).or_default() += output.amount(); - } - } - } - - // TODO explanation about that - for (sdr_address, input_sdr_amount) in &inputs_sdr { - let output_sdr_amount = outputs_sdr.get(sdr_address).unwrap_or(&0); - - if input_sdr_amount > output_sdr_amount { - outputs_sum += input_sdr_amount - output_sdr_amount; - } - } - - (inputs_sum, outputs_sum, inputs_sdr, outputs_sdr) - } -} - -#[derive(Debug, Clone)] -struct AmountSelection { - newly_selected_inputs: HashMap, - inputs_sum: u64, - outputs_sum: u64, - inputs_sdr: HashMap, - outputs_sdr: HashMap, - remainder_amount: u64, - native_tokens_remainder: bool, - mana_remainder: bool, - selected_native_tokens: HashSet, -} - -impl AmountSelection { - fn new(input_selection: &InputSelection) -> Result { - let (inputs_sum, outputs_sum, inputs_sdr, outputs_sdr) = input_selection.amount_sums(); - let selected_native_tokens = HashSet::::from_iter( - input_selection - .selected_inputs - .iter() - .filter_map(|i| i.output.native_token().map(|n| *n.token_id())), - ); - let (remainder_amount, native_tokens_remainder, mana_remainder) = input_selection.remainder_amount()?; - - Ok(Self { - newly_selected_inputs: HashMap::new(), - inputs_sum, - outputs_sum, - inputs_sdr, - outputs_sdr, - remainder_amount, - native_tokens_remainder, - mana_remainder, - selected_native_tokens, - }) - } - - fn missing_amount(&self) -> u64 { - // If there is already a remainder, make sure it's enough to cover the storage deposit. - if self.inputs_sum > self.outputs_sum { - let diff = self.inputs_sum - self.outputs_sum; - - if self.remainder_amount > diff { - self.remainder_amount - diff - } else { - 0 - } - } else if self.inputs_sum < self.outputs_sum { - self.outputs_sum - self.inputs_sum - } else if self.native_tokens_remainder || self.mana_remainder { - self.remainder_amount - } else { - 0 - } - } - - fn fulfil<'a>( - &mut self, - input_selection: &InputSelection, - inputs: impl Iterator, - ) -> Result { - for input in inputs { - if self.newly_selected_inputs.contains_key(input.output_id()) { - continue; - } - - if let Some(sdruc) = sdruc_not_expired(&input.output, input_selection.creation_slot) { - // Skip if no additional amount is made available - if input.output.amount() == sdruc.amount() { - continue; - } - let input_sdr = self.inputs_sdr.get(sdruc.return_address()).unwrap_or(&0) + sdruc.amount(); - let output_sdr = *self.outputs_sdr.get(sdruc.return_address()).unwrap_or(&0); - - if input_sdr > output_sdr { - let diff = input_sdr - output_sdr; - self.outputs_sum += diff; - *self.outputs_sdr.entry(sdruc.return_address().clone()).or_default() += sdruc.amount(); - } - - *self.inputs_sdr.entry(sdruc.return_address().clone()).or_default() += sdruc.amount(); - } - - if let Some(nt) = input.output.native_token() { - self.selected_native_tokens.insert(*nt.token_id()); - } - - self.inputs_sum += input.output.amount(); - self.newly_selected_inputs.insert(*input.output_id(), input.clone()); - - if input.output.native_token().is_some() { - // Recalculate the remaining amount, as a new native token may require a new remainder output. - let (remainder_amount, native_tokens_remainder, mana_remainder) = - self.remainder_amount(input_selection)?; - log::debug!( - "Calculated new remainder_amount: {remainder_amount}, native_tokens_remainder: {native_tokens_remainder}" - ); - self.remainder_amount = remainder_amount; - self.native_tokens_remainder = native_tokens_remainder; - self.mana_remainder = mana_remainder; - } - - if self.missing_amount() == 0 { - return Ok(true); - } - } - - Ok(false) - } - - pub(crate) fn remainder_amount(&self, input_selection: &InputSelection) -> Result<(u64, bool, bool), Error> { - let input_native_tokens = - get_native_tokens(self.newly_selected_inputs.values().map(|input| &input.output))?.finish()?; - - input_selection.required_remainder_amount(Some(input_native_tokens)) - } - - fn into_newly_selected_inputs(self) -> Vec { - self.newly_selected_inputs.into_values().collect() - } -} - -impl InputSelection { - fn fulfil<'a>( - &self, - base_inputs: impl Iterator + Clone, - amount_selection: &mut AmountSelection, - ) -> Result { - // No native token, expired SDRUC. - let inputs = base_inputs.clone().filter(|input| { - input.output.native_token().is_none() && sdruc_not_expired(&input.output, self.creation_slot).is_none() - }); - - if amount_selection.fulfil(self, inputs)? { - return Ok(true); - } - - // No native token, unexpired SDRUC. - let inputs = base_inputs.clone().filter(|input| { - input.output.native_token().is_none() && sdruc_not_expired(&input.output, self.creation_slot).is_some() - }); - - if amount_selection.fulfil(self, inputs)? { - return Ok(true); - } - - // Native token, expired SDRUC. - let inputs = base_inputs.clone().filter(|input| { - input.output.native_token().is_some() && sdruc_not_expired(&input.output, self.creation_slot).is_none() - }); - - if amount_selection.fulfil(self, inputs)? { - return Ok(true); - } - - // Native token, unexpired SDRUC. - let inputs = base_inputs.clone().filter(|input| { - input.output.native_token().is_some() && sdruc_not_expired(&input.output, self.creation_slot).is_some() - }); - - if amount_selection.fulfil(self, inputs)? { - return Ok(true); - } - - // Everything else. - if amount_selection.fulfil(self, base_inputs)? { - return Ok(true); - } - - Ok(false) - } - - fn reduce_funds_of_chains(&mut self, amount_selection: &mut AmountSelection) -> Result<(), Error> { - // Only consider automatically transitioned outputs. - for output in self.added_outputs.iter_mut() { - let diff = amount_selection.missing_amount(); - let amount = output.amount(); - let minimum_amount = output.minimum_amount(self.protocol_parameters.storage_score_parameters()); - - let new_amount = if amount >= diff + minimum_amount { - amount - diff - } else { - minimum_amount - }; - - // TODO check that new_amount is enough for the storage cost - - // PANIC: unwrap is fine as non-chain outputs have been filtered out already. - log::debug!( - "Reducing amount of {} to {} to fulfill amount requirement", - output.chain_id().unwrap(), - new_amount - ); - - let new_output = match output { - Output::Account(output) => AccountOutputBuilder::from(&*output) - .with_amount(new_amount) - .finish_output()?, - Output::Foundry(output) => FoundryOutputBuilder::from(&*output) - .with_amount(new_amount) - .finish_output()?, - Output::Nft(output) => NftOutputBuilder::from(&*output) - .with_amount(new_amount) - .finish_output()?, - _ => panic!("only account, nft and foundry can be automatically created"), - }; - - amount_selection.outputs_sum -= amount - new_amount; - *output = new_output; - - if amount_selection.missing_amount() == 0 { - return Ok(()); - } - } - - Err(Error::InsufficientAmount { - found: amount_selection.inputs_sum, - required: amount_selection.inputs_sum + amount_selection.missing_amount(), - }) - } - - pub(crate) fn fulfill_amount_requirement(&mut self) -> Result, Error> { - let mut amount_selection = AmountSelection::new(self)?; - - if amount_selection.missing_amount() == 0 { - log::debug!("Amount requirement already fulfilled"); - return Ok(amount_selection.into_newly_selected_inputs()); - } else { - log::debug!( - "Fulfilling amount requirement with input {}, output {}, input sdrs {:?} and output sdrs {:?}", - amount_selection.inputs_sum, - amount_selection.outputs_sum, - amount_selection.inputs_sdr, - amount_selection.outputs_sdr - ); - } - - // TODO if consolidate strategy: sum all the lowest amount until diff is covered. - // TODO this would be lowest amount of input strategy. - - // Try to select outputs first with ordering from low to high amount, if that fails, try reversed. - - log::debug!("Ordering inputs from low to high amount"); - // Sort inputs per amount, low to high. - self.available_inputs - .sort_by(|left, right| left.output.amount().cmp(&right.output.amount())); - - if let Some(r) = self.fulfill_amount_requirement_inner(&mut amount_selection)? { - return Ok(r); - } - - if self.selected_inputs.len() + amount_selection.newly_selected_inputs.len() > INPUT_COUNT_MAX.into() { - // Clear before trying with reversed ordering. - log::debug!("Clearing amount selection"); - amount_selection = AmountSelection::new(self)?; - - log::debug!("Ordering inputs from high to low amount"); - // Sort inputs per amount, high to low. - self.available_inputs - .sort_by(|left, right| right.output.amount().cmp(&left.output.amount())); - - if let Some(r) = self.fulfill_amount_requirement_inner(&mut amount_selection)? { - return Ok(r); - } - } - - if self.selected_inputs.len() + amount_selection.newly_selected_inputs.len() > INPUT_COUNT_MAX.into() { - return Err(Error::InvalidInputCount( - self.selected_inputs.len() + amount_selection.newly_selected_inputs.len(), - )); - } - - if amount_selection.missing_amount() != 0 { - self.reduce_funds_of_chains(&mut amount_selection)?; - } - - log::debug!( - "Outputs {:?} selected to fulfill the amount requirement", - amount_selection.newly_selected_inputs - ); - - self.available_inputs - .retain(|input| !amount_selection.newly_selected_inputs.contains_key(input.output_id())); - - Ok(amount_selection.into_newly_selected_inputs()) - } - - fn fulfill_amount_requirement_inner( - &mut self, - amount_selection: &mut AmountSelection, - ) -> Result>, Error> { - let basic_ed25519_inputs = self.available_inputs.iter().filter(|input| { - if let Output::Basic(output) = &input.output { - output - .unlock_conditions() - .locked_address( - output.address(), - self.creation_slot, - self.protocol_parameters.committable_age_range(), - ) - .expect("slot index was provided") - .expect("expiration unlockable outputs already filtered out") - .is_ed25519() - } else { - false - } - }); - - if self.fulfil(basic_ed25519_inputs, amount_selection)? { - return Ok(None); - } - - let basic_non_ed25519_inputs = self.available_inputs.iter().filter(|input| { - if let Output::Basic(output) = &input.output { - !output - .unlock_conditions() - .locked_address( - output.address(), - self.creation_slot, - self.protocol_parameters.committable_age_range(), - ) - .expect("slot index was provided") - .expect("expiration unlockable outputs already filtered out") - .is_ed25519() - } else { - false - } - }); - - if self.fulfil(basic_non_ed25519_inputs, amount_selection)? { - return Ok(None); - } - - // Other kinds of outputs. - - log::debug!("Trying other types of outputs"); - - let mut inputs = self - .available_inputs - .iter() - .filter(|input| !input.output.is_basic()) - .peekable(); - - if inputs.peek().is_some() { - amount_selection.fulfil(self, inputs)?; - - log::debug!( - "Outputs {:?} selected to fulfill the amount requirement", - amount_selection.newly_selected_inputs - ); - log::debug!("Triggering another amount round as non-basic outputs need to be transitioned first"); - - if self.selected_inputs.len() + amount_selection.newly_selected_inputs.len() <= INPUT_COUNT_MAX.into() { - self.available_inputs - .retain(|input| !amount_selection.newly_selected_inputs.contains_key(input.output_id())); - - // TODO explanation of Amount - self.requirements.push(Requirement::Amount); - - Ok(Some(amount_selection.clone().into_newly_selected_inputs())) - } else { - Ok(None) - } - } else { - Ok(None) - } - } -} diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/issuer.rs b/sdk/src/client/api/block_builder/input_selection/requirement/issuer.rs deleted file mode 100644 index 99c961c418..0000000000 --- a/sdk/src/client/api/block_builder/input_selection/requirement/issuer.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use super::{Error, InputSelection, Requirement}; -use crate::{client::secret::types::InputSigningData, types::block::address::Address}; - -impl InputSelection { - /// Fulfills an issuer requirement by fulfilling the equivalent sender requirement. - /// Potentially converts the error for a more accurate one. - pub(crate) fn fulfill_issuer_requirement(&mut self, address: &Address) -> Result, Error> { - log::debug!("Treating {address:?} issuer requirement as a sender requirement"); - - match self.fulfill_sender_requirement(address) { - Ok(res) => Ok(res), - Err(Error::UnfulfillableRequirement(Requirement::Sender(_))) => { - Err(Error::UnfulfillableRequirement(Requirement::Issuer(address.clone()))) - } - Err(e) => Err(e), - } - } -} diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/native_tokens.rs b/sdk/src/client/api/block_builder/input_selection/requirement/native_tokens.rs deleted file mode 100644 index aa0fef119e..0000000000 --- a/sdk/src/client/api/block_builder/input_selection/requirement/native_tokens.rs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::{cmp::Ordering, collections::HashSet}; - -use primitive_types::U256; - -use super::{Error, InputSelection}; -use crate::{ - client::secret::types::InputSigningData, - types::block::output::{NativeToken, NativeTokens, NativeTokensBuilder, Output, TokenScheme}, -}; - -pub(crate) fn get_native_tokens<'a>(outputs: impl Iterator) -> Result { - let mut required_native_tokens = NativeTokensBuilder::new(); - - for output in outputs { - if let Some(native_token) = output.native_token() { - required_native_tokens.add_native_token(*native_token)?; - } - } - - Ok(required_native_tokens) -} - -// TODO only handles one side -pub(crate) fn get_native_tokens_diff( - inputs: &NativeTokensBuilder, - outputs: &NativeTokensBuilder, -) -> Result, Error> { - let mut native_tokens_diff = NativeTokensBuilder::new(); - - for (token_id, input_amount) in inputs.iter() { - match outputs.get(token_id) { - None => { - native_tokens_diff.insert(*token_id, *input_amount); - } - Some(output_amount) => { - if input_amount > output_amount { - native_tokens_diff.insert(*token_id, input_amount - output_amount); - } - } - } - } - - if native_tokens_diff.is_empty() { - Ok(None) - } else { - Ok(Some(native_tokens_diff.finish()?)) - } -} - -impl InputSelection { - pub(crate) fn fulfill_native_tokens_requirement(&mut self) -> Result, Error> { - let mut input_native_tokens = get_native_tokens(self.selected_inputs.iter().map(|input| &input.output))?; - let mut output_native_tokens = get_native_tokens(self.non_remainder_outputs())?; - let (minted_native_tokens, melted_native_tokens) = self.get_minted_and_melted_native_tokens()?; - - input_native_tokens.merge(minted_native_tokens)?; - output_native_tokens.merge(melted_native_tokens)?; - - if let Some(burn) = self.burn.as_ref() { - output_native_tokens.merge(NativeTokensBuilder::from(burn.native_tokens.clone()))?; - } - - // TODO weird that it happens in this direction? - if let Some(diffs) = get_native_tokens_diff(&output_native_tokens, &input_native_tokens)? { - log::debug!( - "Fulfilling native tokens requirement with input {input_native_tokens:?} and output {output_native_tokens:?}" - ); - - let mut newly_selected_inputs = Vec::new(); - let mut newly_selected_ids = HashSet::new(); - - for diff in diffs.iter() { - let mut amount = U256::zero(); - // TODO sort ? - let inputs = self.available_inputs.iter().filter(|input| { - input - .output - .native_token() - .is_some_and(|native_token| native_token.token_id() == diff.token_id()) - }); - - for input in inputs { - amount += input - .output - .native_token() - // PANIC: safe to unwrap as the filter guarantees inputs containing this native token. - .unwrap() - .amount(); - - if newly_selected_ids.insert(*input.output_id()) { - newly_selected_inputs.push(input.clone()); - } - - if amount >= diff.amount() { - break; - } - } - - if amount < diff.amount() { - return Err(Error::InsufficientNativeTokenAmount { - token_id: *diff.token_id(), - found: amount, - required: diff.amount(), - }); - } - } - - log::debug!("Outputs {newly_selected_ids:?} selected to fulfill the native tokens requirement"); - - self.available_inputs - .retain(|input| !newly_selected_ids.contains(input.output_id())); - - Ok(newly_selected_inputs) - } else { - log::debug!("Native tokens requirement already fulfilled"); - - Ok(Vec::new()) - } - } - - pub(crate) fn get_minted_and_melted_native_tokens( - &self, - ) -> Result<(NativeTokensBuilder, NativeTokensBuilder), Error> { - let mut minted_native_tokens = NativeTokensBuilder::new(); - let mut melted_native_tokens = NativeTokensBuilder::new(); - - for output in self.non_remainder_outputs() { - if let Output::Foundry(output_foundry) = output { - let TokenScheme::Simple(output_foundry_simple_ts) = output_foundry.token_scheme(); - let mut initial_creation = true; - - for input in &self.selected_inputs { - if let Output::Foundry(input_foundry) = &input.output { - let token_id = output_foundry.token_id(); - - if output_foundry.id() == input_foundry.id() { - initial_creation = false; - let TokenScheme::Simple(input_foundry_simple_ts) = input_foundry.token_scheme(); - - match output_foundry_simple_ts - .circulating_supply() - .cmp(&input_foundry_simple_ts.circulating_supply()) - { - Ordering::Greater => { - let minted_native_token_amount = output_foundry_simple_ts.circulating_supply() - - input_foundry_simple_ts.circulating_supply(); - - minted_native_tokens - .add_native_token(NativeToken::new(token_id, minted_native_token_amount)?)?; - } - Ordering::Less => { - let melted_native_token_amount = input_foundry_simple_ts.circulating_supply() - - output_foundry_simple_ts.circulating_supply(); - - melted_native_tokens - .add_native_token(NativeToken::new(token_id, melted_native_token_amount)?)?; - } - Ordering::Equal => {} - } - } - } - } - - // If we created the foundry with this transaction, then we need to add the circulating supply as minted - // tokens - if initial_creation { - let circulating_supply = output_foundry_simple_ts.circulating_supply(); - - if !circulating_supply.is_zero() { - minted_native_tokens - .add_native_token(NativeToken::new(output_foundry.token_id(), circulating_supply)?)?; - } - } - } - } - - Ok((minted_native_tokens, melted_native_tokens)) - } -} diff --git a/sdk/src/client/api/block_builder/mod.rs b/sdk/src/client/api/block_builder/mod.rs index 35c06c12a1..c632dea9c1 100644 --- a/sdk/src/client/api/block_builder/mod.rs +++ b/sdk/src/client/api/block_builder/mod.rs @@ -1,12 +1,12 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -pub mod input_selection; +pub mod options; pub mod transaction; +pub mod transaction_builder; -pub use self::transaction::verify_semantic; use crate::{ - client::{constants::FIVE_MINUTES_IN_NANOSECONDS, ClientInner, Error, Result}, + client::{constants::FIVE_MINUTES_IN_NANOSECONDS, Client, ClientError}, types::block::{ core::{BlockHeader, UnsignedBlock}, output::AccountId, @@ -15,12 +15,12 @@ use crate::{ }, }; -impl ClientInner { +impl Client { pub async fn build_basic_block( &self, issuer_id: AccountId, payload: impl Into> + Send, - ) -> Result { + ) -> Result { let issuance = self.get_issuance().await?; let issuing_time = { @@ -39,7 +39,7 @@ impl ClientInner { ..issuance.latest_parent_block_issuing_time + FIVE_MINUTES_IN_NANOSECONDS) .contains(&issuing_time) { - return Err(Error::TimeNotSynced { + return Err(ClientError::TimeNotSynced { current_time: issuing_time, tangle_time: issuance.latest_parent_block_issuing_time, }); diff --git a/sdk/src/wallet/operations/transaction/options.rs b/sdk/src/client/api/block_builder/options.rs similarity index 81% rename from sdk/src/wallet/operations/transaction/options.rs rename to sdk/src/client/api/block_builder/options.rs index f3bc9bcde4..44d95f98d4 100644 --- a/sdk/src/wallet/operations/transaction/options.rs +++ b/sdk/src/client/api/block_builder/options.rs @@ -6,12 +6,11 @@ use alloc::collections::{BTreeMap, BTreeSet}; use serde::{Deserialize, Serialize}; use crate::{ - client::api::input_selection::Burn, + client::api::transaction_builder::Burn, types::block::{ address::Address, - context_input::ContextInput, output::{AccountId, OutputId}, - payload::{signed_transaction::TransactionCapabilities, tagged_data::TaggedDataPayload}, + payload::tagged_data::TaggedDataPayload, }, }; @@ -24,11 +23,9 @@ pub struct TransactionOptions { pub remainder_value_strategy: RemainderValueStrategy, /// An optional tagged data payload. pub tagged_data_payload: Option, - /// Transaction context inputs to include. - pub context_inputs: Vec, /// Inputs that must be used for the transaction. pub required_inputs: BTreeSet, - /// Specifies what needs to be burned during input selection. + /// Specifies what needs to be burned in the transaction. pub burn: Option, /// A string attached to the transaction. pub note: Option, @@ -36,8 +33,6 @@ pub struct TransactionOptions { pub allow_micro_amount: bool, /// Whether to allow the selection of additional inputs for this transaction. pub allow_additional_input_selection: bool, - /// Transaction capabilities. - pub capabilities: Option, /// Mana allotments for the transaction. pub mana_allotments: BTreeMap, /// Optional block issuer to which the transaction will have required mana allotted. @@ -49,13 +44,11 @@ impl Default for TransactionOptions { Self { remainder_value_strategy: Default::default(), tagged_data_payload: Default::default(), - context_inputs: Default::default(), required_inputs: Default::default(), burn: Default::default(), note: Default::default(), allow_micro_amount: false, allow_additional_input_selection: true, - capabilities: Default::default(), mana_allotments: Default::default(), issuer_id: Default::default(), } diff --git a/sdk/src/client/api/block_builder/transaction.rs b/sdk/src/client/api/block_builder/transaction.rs index 9e9cedb501..6edf74ee4e 100644 --- a/sdk/src/client/api/block_builder/transaction.rs +++ b/sdk/src/client/api/block_builder/transaction.rs @@ -3,12 +3,13 @@ //! Transaction preparation and signing -use alloc::collections::BTreeMap; - use packable::PackableExt; use crate::{ - client::{secret::types::InputSigningData, Error, Result}, + client::{ + api::{PreparedTransactionData, SignedTransactionData}, + ClientError, + }, types::block::{ output::{Output, OutputId}, payload::signed_transaction::{SignedTransactionPayload, Transaction}, @@ -27,61 +28,85 @@ const SINGLE_UNLOCK_LENGTH: usize = 1 + 1 + Ed25519Signature::PUBLIC_KEY_LENGTH // Type + reference index const REFERENCE_ACCOUNT_NFT_UNLOCK_LENGTH: usize = 1 + 2; -/// Verifies the semantic of a prepared transaction. -pub fn verify_semantic( - input_signing_data: &[InputSigningData], - transaction_payload: &SignedTransactionPayload, - mana_rewards: BTreeMap, - protocol_parameters: ProtocolParameters, -) -> crate::client::Result> { - let inputs = input_signing_data - .iter() - .map(|input| (input.output_id(), &input.output)) - .collect::>(); +impl PreparedTransactionData { + /// Verifies the semantic of a prepared transaction. + pub fn verify_semantic(&self, protocol_parameters: &ProtocolParameters) -> Result<(), TransactionFailureReason> { + let inputs = self + .inputs_data + .iter() + .map(|input| (input.output_id(), &input.output)) + .collect::>(); + + let context = SemanticValidationContext::new( + &self.transaction, + &inputs, + None, + Some(&self.mana_rewards), + protocol_parameters, + ); + + context.validate() + } +} - let context = SemanticValidationContext::new( - transaction_payload.transaction(), - &inputs, - Some(transaction_payload.unlocks()), - mana_rewards, - protocol_parameters, - ); +impl SignedTransactionData { + /// Verifies the semantic of a prepared transaction. + pub fn verify_semantic(&self, protocol_parameters: &ProtocolParameters) -> Result<(), TransactionFailureReason> { + let inputs = self + .inputs_data + .iter() + .map(|input| (input.output_id(), &input.output)) + .collect::>(); - Ok(context.validate()?) + let context = SemanticValidationContext::new( + self.payload.transaction(), + &inputs, + Some(self.payload.unlocks()), + Some(&self.mana_rewards), + protocol_parameters, + ); + + context.validate() + } } -/// Verifies that the signed transaction payload doesn't exceed the block size limit with 8 parents. -pub fn validate_signed_transaction_payload_length(signed_transaction_payload: &SignedTransactionPayload) -> Result<()> { - let signed_transaction_payload_bytes = signed_transaction_payload.pack_to_vec(); - if signed_transaction_payload_bytes.len() > MAX_TX_LENGTH_FOR_BLOCK_WITH_8_PARENTS { - return Err(Error::InvalidSignedTransactionPayloadLength { - length: signed_transaction_payload_bytes.len(), - max_length: MAX_TX_LENGTH_FOR_BLOCK_WITH_8_PARENTS, - }); +impl SignedTransactionPayload { + /// Verifies that the signed transaction payload doesn't exceed the block size limit with 8 parents. + pub fn validate_length(&self) -> Result<(), ClientError> { + let signed_transaction_payload_bytes = self.pack_to_vec(); + if signed_transaction_payload_bytes.len() > MAX_TX_LENGTH_FOR_BLOCK_WITH_8_PARENTS { + return Err(ClientError::InvalidSignedTransactionPayloadLength { + length: signed_transaction_payload_bytes.len(), + max_length: MAX_TX_LENGTH_FOR_BLOCK_WITH_8_PARENTS, + }); + } + Ok(()) } - Ok(()) } -/// Verifies that the transaction doesn't exceed the block size limit with 8 parents. -/// Assuming one signature unlock and otherwise reference/account/nft unlocks. `validate_transaction_payload_length()` -/// should later be used to check the length again with the correct unlocks. -pub fn validate_transaction_length(transaction: &Transaction) -> Result<()> { - let transaction_bytes = transaction.pack_to_vec(); +impl Transaction { + /// Verifies that the transaction doesn't exceed the block size limit with 8 parents. + /// Assuming one signature unlock and otherwise reference/account/nft unlocks. + /// `validate_transaction_payload_length()` should later be used to check the length again with the correct + /// unlocks. + pub fn validate_length(&self) -> Result<(), ClientError> { + let transaction_bytes = self.pack_to_vec(); - // Assuming there is only 1 signature unlock and the rest is reference/account/nft unlocks - let reference_account_nft_unlocks_amount = transaction.inputs().len() - 1; + // Assuming there is only 1 signature unlock and the rest is reference/account/nft unlocks + let reference_account_nft_unlocks_amount = self.inputs().len() - 1; - // Max tx payload length - length for one signature unlock (there might be more unlocks, we check with them - // later again, when we built the transaction payload) - let max_length = MAX_TX_LENGTH_FOR_BLOCK_WITH_8_PARENTS - - SINGLE_UNLOCK_LENGTH - - (reference_account_nft_unlocks_amount * REFERENCE_ACCOUNT_NFT_UNLOCK_LENGTH); + // Max tx payload length - length for one signature unlock (there might be more unlocks, we check with them + // later again, when we built the transaction payload) + let max_length = MAX_TX_LENGTH_FOR_BLOCK_WITH_8_PARENTS + - SINGLE_UNLOCK_LENGTH + - (reference_account_nft_unlocks_amount * REFERENCE_ACCOUNT_NFT_UNLOCK_LENGTH); - if transaction_bytes.len() > max_length { - return Err(Error::InvalidTransactionLength { - length: transaction_bytes.len(), - max_length, - }); + if transaction_bytes.len() > max_length { + return Err(ClientError::InvalidTransactionLength { + length: transaction_bytes.len(), + max_length, + }); + } + Ok(()) } - Ok(()) } diff --git a/sdk/src/client/api/block_builder/input_selection/burn.rs b/sdk/src/client/api/block_builder/transaction_builder/burn.rs similarity index 82% rename from sdk/src/client/api/block_builder/input_selection/burn.rs rename to sdk/src/client/api/block_builder/transaction_builder/burn.rs index c621b1dfb0..1cab2c59dd 100644 --- a/sdk/src/client/api/block_builder/input_selection/burn.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/burn.rs @@ -9,11 +9,17 @@ use serde::{Deserialize, Serialize}; use crate::types::block::output::{AccountId, DelegationId, FoundryId, NativeToken, NftId, TokenId}; -/// A type to specify what needs to be burned during input selection. +/// A type to specify what needs to be burned in a transaction. /// Nothing will be burned that has not been explicitly set with this struct. #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Burn { + // Whether initial excess mana should be burned (only from inputs/outputs that have been specified manually). + #[serde(default)] + pub(crate) mana: bool, + // Whether generated mana should be burned. + #[serde(default)] + pub(crate) generated_mana: bool, /// Accounts to burn. #[serde(default, skip_serializing_if = "HashSet::is_empty")] pub(crate) accounts: HashSet, @@ -37,6 +43,28 @@ impl Burn { Self::default() } + /// Sets the flag to [`Burn`] initial excess mana. + pub fn set_mana(mut self, burn_mana: bool) -> Self { + self.mana = burn_mana; + self + } + + /// Returns whether to [`Burn`] mana. + pub fn mana(&self) -> bool { + self.mana + } + + /// Sets the flag to [`Burn`] generated mana. + pub fn set_generated_mana(mut self, burn_generated_mana: bool) -> Self { + self.generated_mana = burn_generated_mana; + self + } + + /// Returns whether to [`Burn`] generated mana. + pub fn generated_mana(&self) -> bool { + self.generated_mana + } + /// Adds an account to [`Burn`]. pub fn add_account(mut self, account_id: AccountId) -> Self { self.accounts.insert(account_id); diff --git a/sdk/src/client/api/block_builder/input_selection/error.rs b/sdk/src/client/api/block_builder/transaction_builder/error.rs similarity index 67% rename from sdk/src/client/api/block_builder/input_selection/error.rs rename to sdk/src/client/api/block_builder/transaction_builder/error.rs index bd0c302a87..06dfa4c538 100644 --- a/sdk/src/client/api/block_builder/input_selection/error.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/error.rs @@ -1,24 +1,30 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! Error handling for input selection. +//! Error handling for transaction builder. use std::fmt::Debug; use primitive_types::U256; use super::Requirement; -use crate::types::block::output::{ChainId, OutputId, TokenId}; +use crate::types::block::{ + context_input::ContextInputError, + mana::ManaError, + output::{ChainId, NativeTokenError, OutputError, OutputId, TokenId}, + payload::PayloadError, + semantic::TransactionFailureReason, + signature::SignatureError, + unlock::UnlockError, + BlockError, +}; -/// Errors related to input selection. +/// Errors related to transaction builder. #[derive(Debug, Eq, PartialEq, thiserror::Error)] #[non_exhaustive] -pub enum Error { +pub enum TransactionBuilderError { #[error("additional inputs required for {0:?}, but additional input selection is disabled")] AdditionalInputsRequired(Requirement), - /// Block error. - #[error("{0}")] - Block(#[from] crate::types::block::Error), /// Can't burn and transition an output at the same time. #[error("can't burn and transition an output at the same time, chain ID: {0}")] BurnAndTransition(ChainId), @@ -59,12 +65,9 @@ pub enum Error { /// No input with matching ed25519 address provided. #[error("no input with matching ed25519 address provided")] MissingInputWithEd25519Address, - /// No available inputs were provided to input selection. + /// No available inputs were provided to transaction builder. #[error("no available inputs provided")] NoAvailableInputsProvided, - /// Required input is forbidden. - #[error("required input {0} is forbidden")] - RequiredInputIsForbidden(OutputId), /// Required input is not available. #[error("required input {0} is not available")] RequiredInputIsNotAvailable(OutputId), @@ -75,4 +78,31 @@ pub enum Error { #[error("unsupported address type {0}")] // TODO replace with string when 2.0 has Address::kind_str UnsupportedAddressType(u8), + /// Block error. + #[error("{0}")] + Block(#[from] BlockError), + /// Output errors. + #[error("{0}")] + Output(#[from] OutputError), + /// Payload errors. + #[error("{0}")] + Payload(#[from] PayloadError), + /// Signature errors. + #[error("{0}")] + Signature(#[from] SignatureError), + /// Mana errors. + #[error("{0}")] + Mana(#[from] ManaError), + /// Native token errors. + #[error("{0}")] + NativeToken(#[from] NativeTokenError), + /// Context input errors. + #[error("{0}")] + ContextInput(#[from] ContextInputError), + /// Unlock errors. + #[error("{0}")] + Unlock(#[from] UnlockError), + /// Semantic errors. + #[error("{0}")] + Semantic(#[from] TransactionFailureReason), } diff --git a/sdk/src/client/api/block_builder/input_selection/mod.rs b/sdk/src/client/api/block_builder/transaction_builder/mod.rs similarity index 67% rename from sdk/src/client/api/block_builder/input_selection/mod.rs rename to sdk/src/client/api/block_builder/transaction_builder/mod.rs index 5a827c72d7..0e951614e4 100644 --- a/sdk/src/client/api/block_builder/input_selection/mod.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/mod.rs @@ -1,7 +1,7 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! Input selection for transactions +//! Builder for transactions pub(crate) mod burn; pub(crate) mod error; @@ -10,29 +10,33 @@ pub(crate) mod requirement; pub(crate) mod transition; use alloc::collections::BTreeMap; -use core::ops::Deref; use std::collections::{HashMap, HashSet}; +use crypto::keys::bip44::Bip44; use packable::PackableExt; -use self::requirement::account::is_account_with_id; -pub use self::{burn::Burn, error::Error, requirement::Requirement}; +pub use self::{burn::Burn, error::TransactionBuilderError, requirement::Requirement}; use crate::{ client::{ - api::{PreparedTransactionData, RemainderData}, + api::{ + options::{RemainderValueStrategy, TransactionOptions}, + PreparedTransactionData, RemainderData, + }, + node_api::indexer::query_parameters::OutputQueryParameters, secret::types::InputSigningData, + Client, ClientError, }, types::block::{ - address::{AccountAddress, Address, NftAddress}, - context_input::ContextInput, + address::{AccountAddress, Address, NftAddress, ToBech32Ext}, + context_input::{BlockIssuanceCreditContextInput, CommitmentContextInput, ContextInput, RewardContextInput}, input::{Input, UtxoInput, INPUT_COUNT_RANGE}, mana::ManaAllotment, output::{ - AccountId, AccountOutput, AccountOutputBuilder, AnchorOutputBuilder, BasicOutputBuilder, FoundryOutput, - NativeTokensBuilder, NftOutput, NftOutputBuilder, Output, OutputId, OUTPUT_COUNT_RANGE, + AccountId, AccountOutputBuilder, AnchorOutputBuilder, BasicOutputBuilder, NftOutputBuilder, Output, + OutputId, OUTPUT_COUNT_RANGE, }, payload::{ - signed_transaction::{Transaction, TransactionCapabilities}, + signed_transaction::{Transaction, TransactionCapabilities, TransactionCapabilityFlag}, TaggedDataPayload, }, protocol::{CommittableAgeRange, ProtocolParameters}, @@ -40,14 +44,147 @@ use crate::{ }, }; -/// Working state for the input selection algorithm. +impl Client { + /// Builds a transaction using the given inputs, outputs, addresses, and options. + pub async fn build_transaction( + &self, + addresses: impl IntoIterator + Send, + outputs: impl IntoIterator + Send, + options: TransactionOptions, + ) -> Result { + let addresses = addresses.into_iter().collect::>(); + let protocol_parameters = self.get_protocol_parameters().await?; + let slot_commitment_id = self.get_issuance().await?.latest_commitment.id(); + + let hrp = protocol_parameters.bech32_hrp(); + + let mut available_inputs = Vec::new(); + for (address, chain) in &addresses { + let output_ids = self + .output_ids(OutputQueryParameters::new().unlockable_by_address(address.clone().to_bech32(hrp))) + .await? + .items; + available_inputs.extend( + self.get_outputs_with_metadata(&output_ids) + .await? + .into_iter() + .map(|res| { + Ok(InputSigningData { + output: res.output, + output_metadata: res.metadata, + chain: Some(*chain), + }) + }) + .collect::, ClientError>>()?, + ); + } + + self.build_transaction_inner( + addresses.into_keys(), + available_inputs, + outputs, + options, + slot_commitment_id, + protocol_parameters, + ) + .await + } + + /// Builds a transaction using the given inputs, outputs, addresses, and options. + pub(crate) async fn build_transaction_inner( + &self, + addresses: impl IntoIterator + Send, + available_inputs: impl IntoIterator + Send, + outputs: impl IntoIterator + Send, + options: TransactionOptions, + slot_commitment_id: SlotCommitmentId, + protocol_parameters: ProtocolParameters, + ) -> Result { + let outputs = outputs.into_iter().collect::>(); + let creation_slot = self.get_slot_index().await?; + + let reference_mana_cost = if let Some(issuer_id) = options.issuer_id { + Some(self.get_account_congestion(&issuer_id, None).await?.reference_mana_cost) + } else { + None + }; + let remainder_address = match options.remainder_value_strategy { + RemainderValueStrategy::ReuseAddress => None, + RemainderValueStrategy::CustomAddress(address) => Some(address), + }; + + let mut mana_rewards = HashMap::new(); + + if let Some(burn) = &options.burn { + for delegation_id in burn.delegations() { + let output_id = self.delegation_output_id(*delegation_id).await?; + mana_rewards.insert( + output_id, + self.get_output_mana_rewards(&output_id, slot_commitment_id.slot_index()) + .await? + .rewards, + ); + } + } + + for output_id in &options.required_inputs { + let input = self.get_output(output_id).await?; + if input.output.can_claim_rewards(outputs.iter().find(|o| { + input + .output + .chain_id() + .map(|chain_id| chain_id.or_from_output_id(output_id)) + == o.chain_id() + })) { + mana_rewards.insert( + *output_id, + self.get_output_mana_rewards(output_id, slot_commitment_id.slot_index()) + .await? + .rewards, + ); + } + } + + let mut transaction_builder = TransactionBuilder::new( + available_inputs, + outputs, + addresses, + creation_slot, + slot_commitment_id, + protocol_parameters, + ) + .with_required_inputs(options.required_inputs) + .with_mana_rewards(mana_rewards) + .with_payload(options.tagged_data_payload) + .with_mana_allotments(options.mana_allotments) + .with_remainder_address(remainder_address) + .with_burn(options.burn); + + if let (Some(account_id), Some(reference_mana_cost)) = (options.issuer_id, reference_mana_cost) { + transaction_builder = transaction_builder.with_min_mana_allotment(account_id, reference_mana_cost); + } + + if !options.allow_additional_input_selection { + transaction_builder = transaction_builder.disable_additional_input_selection(); + } + + let prepared_transaction_data = transaction_builder.finish()?; + + prepared_transaction_data.transaction.validate_length()?; + + Ok(prepared_transaction_data) + } +} + +/// Working state for the transaction builder algorithm. #[derive(Debug)] -pub struct InputSelection { +pub struct TransactionBuilder { available_inputs: Vec, required_inputs: HashSet, - forbidden_inputs: HashSet, selected_inputs: Vec, - context_inputs: HashSet, + bic_context_inputs: HashSet, + commitment_context_input: Option, + reward_context_inputs: HashSet, provided_outputs: Vec, added_outputs: Vec, addresses: HashSet
, @@ -81,8 +218,8 @@ pub(crate) struct Remainders { added_mana: u64, } -impl InputSelection { - /// Creates a new [`InputSelection`]. +impl TransactionBuilder { + /// Creates a new [`TransactionBuilder`]. pub fn new( available_inputs: impl IntoIterator, outputs: impl IntoIterator, @@ -116,9 +253,10 @@ impl InputSelection { Self { available_inputs, required_inputs: HashSet::new(), - forbidden_inputs: HashSet::new(), selected_inputs: Vec::new(), - context_inputs: HashSet::new(), + bic_context_inputs: HashSet::new(), + commitment_context_input: None, + reward_context_inputs: HashSet::new(), provided_outputs: outputs.into_iter().collect(), added_outputs: Vec::new(), addresses, @@ -137,7 +275,7 @@ impl InputSelection { } } - fn init(&mut self) -> Result<(), Error> { + fn init(&mut self) -> Result<(), TransactionBuilderError> { // If automatic min mana allotment is enabled, we need to initialize the allotment debt. if let Some(MinManaAllotment { issuer_id, @@ -156,19 +294,7 @@ impl InputSelection { Requirement::NativeTokens, ]); - // Removes forbidden inputs from available inputs. - self.available_inputs - .retain(|input| !self.forbidden_inputs.contains(input.output_id())); - - // This is to avoid a borrow of self since there is a mutable borrow in the loop already. - let required_inputs = std::mem::take(&mut self.required_inputs); - - for required_input in required_inputs { - // Checks that required input is not forbidden. - if self.forbidden_inputs.contains(&required_input) { - return Err(Error::RequiredInputIsForbidden(required_input)); - } - + for required_input in self.required_inputs.clone() { // Checks that required input is available. match self .available_inputs @@ -180,9 +306,9 @@ impl InputSelection { let input = self.available_inputs.swap_remove(index); // Selects required input. - self.select_input(input)? + self.select_input(input)?; } - None => return Err(Error::RequiredInputIsNotAvailable(required_input)), + None => return Err(TransactionBuilderError::RequiredInputIsNotAvailable(required_input)), } } @@ -198,21 +324,21 @@ impl InputSelection { /// Selects inputs that meet the requirements of the outputs to satisfy the semantic validation of the overall /// transaction. Also creates a remainder output and chain transition outputs if required. - pub fn select(mut self) -> Result { + pub fn finish(mut self) -> Result { if !OUTPUT_COUNT_RANGE.contains(&(self.provided_outputs.len() as u16)) { // If burn or mana allotments are provided, outputs will be added later, in the other cases it will just // create remainder outputs. - if !(self.provided_outputs.is_empty() - && (self.burn.is_some() || !self.mana_allotments.is_empty() || !self.required_inputs.is_empty())) + if !self.provided_outputs.is_empty() + || (self.burn.is_none() && self.mana_allotments.is_empty() && self.required_inputs.is_empty()) { - return Err(Error::InvalidOutputCount(self.provided_outputs.len())); + return Err(TransactionBuilderError::InvalidOutputCount(self.provided_outputs.len())); } } self.filter_inputs(); if self.available_inputs.is_empty() { - return Err(Error::NoAvailableInputsProvided); + return Err(TransactionBuilderError::NoAvailableInputsProvided); } // Creates the initial state, selected inputs and requirements, based on the provided outputs. @@ -221,16 +347,16 @@ impl InputSelection { // Process all the requirements until there are no more. while let Some(requirement) = self.requirements.pop() { // Fulfill the requirement. - let inputs = self.fulfill_requirement(&requirement)?; + self.fulfill_requirement(&requirement)?; + } - if !self.allow_additional_input_selection && !inputs.is_empty() { - return Err(Error::AdditionalInputsRequired(requirement)); - } + let (input_mana, output_mana) = self.mana_sums(false)?; - // Select suggested inputs. - for input in inputs { - self.select_input(input)?; - } + if input_mana < output_mana { + return Err(TransactionBuilderError::InsufficientMana { + found: input_mana, + required: output_mana, + }); } // If there is no min allotment calculation, then we should update the remainders as the last step @@ -239,13 +365,13 @@ impl InputSelection { } if !INPUT_COUNT_RANGE.contains(&(self.selected_inputs.len() as u16)) { - return Err(Error::InvalidInputCount(self.selected_inputs.len())); + return Err(TransactionBuilderError::InvalidInputCount(self.selected_inputs.len())); } if self.remainders.added_mana > 0 { let remainder_address = self .get_remainder_address()? - .ok_or(Error::MissingInputWithEd25519Address)? + .ok_or(TransactionBuilderError::MissingInputWithEd25519Address)? .0; let added_mana = self.remainders.added_mana; if let Some(output) = self.get_output_for_added_mana(&remainder_address) { @@ -261,6 +387,19 @@ impl InputSelection { } } + // If we're burning generated mana, set the capability flag. + if self.burn.as_ref().map_or(false, |b| b.generated_mana()) { + // Get the mana sums with generated mana to see whether there's a difference. + if !self + .transaction_capabilities + .has_capability(TransactionCapabilityFlag::BurnMana) + && input_mana < self.total_selected_mana(true)? + { + self.transaction_capabilities + .add_capability(TransactionCapabilityFlag::BurnMana); + } + } + let outputs = self .provided_outputs .into_iter() @@ -271,27 +410,34 @@ impl InputSelection { // Check again, because more outputs may have been added. if !OUTPUT_COUNT_RANGE.contains(&(outputs.len() as u16)) { - return Err(Error::InvalidOutputCount(outputs.len())); + return Err(TransactionBuilderError::InvalidOutputCount(outputs.len())); } - Self::validate_transitions(&self.selected_inputs, &outputs)?; - for output_id in self.mana_rewards.keys() { if !self.selected_inputs.iter().any(|i| output_id == i.output_id()) { - return Err(Error::ExtraManaRewards(*output_id)); + return Err(TransactionBuilderError::ExtraManaRewards(*output_id)); } } let inputs_data = Self::sort_input_signing_data( self.selected_inputs, - self.creation_slot, + self.latest_slot_commitment_id.slot_index(), self.protocol_parameters.committable_age_range(), )?; let mut inputs: Vec = Vec::new(); + let mut context_inputs = self + .bic_context_inputs + .into_iter() + .map(ContextInput::from) + .chain(self.commitment_context_input.map(ContextInput::from)) + .collect::>(); - for input in &inputs_data { + for (idx, input) in inputs_data.iter().enumerate() { inputs.push(Input::Utxo(UtxoInput::from(*input.output_id()))); + if self.reward_context_inputs.contains(input.output_id()) { + context_inputs.push(RewardContextInput::new(idx as u16).unwrap().into()); + } } let mana_allotments = self @@ -306,7 +452,7 @@ impl InputSelection { .with_inputs(inputs) .with_outputs(outputs) .with_mana_allotments(mana_allotments) - .with_context_inputs(self.context_inputs) + .with_context_inputs(context_inputs) .with_creation_slot(self.creation_slot) .with_capabilities(self.transaction_capabilities); @@ -316,17 +462,22 @@ impl InputSelection { let transaction = builder.finish_with_params(&self.protocol_parameters)?; - Ok(PreparedTransactionData { + let data = PreparedTransactionData { transaction, inputs_data, remainders: self.remainders.data, mana_rewards: self.mana_rewards.into_iter().collect(), - }) + }; + + data.verify_semantic(&self.protocol_parameters)?; + + Ok(data) } - fn select_input(&mut self, input: InputSigningData) -> Result<(), Error> { + fn select_input(&mut self, input: InputSigningData) -> Result, TransactionBuilderError> { log::debug!("Selecting input {:?}", input.output_id()); + let mut added_output = None; if let Some(output) = self.transition_input(&input)? { // No need to check for `outputs_requirements` because // - the sender feature doesn't need to be verified as it has been removed @@ -334,6 +485,7 @@ impl InputSelection { // - input doesn't need to be checked for as we just transitioned it // - foundry account requirement should have been met already by a prior `required_account_nft_addresses` self.added_outputs.push(output); + added_output = self.added_outputs.last(); } if let Some(requirement) = self.required_account_nft_addresses(&input)? { @@ -348,40 +500,28 @@ impl InputSelection { self.requirements.push(Requirement::ContextInputs); } - Ok(()) + Ok(added_output) } - /// Sets the required inputs of an [`InputSelection`]. + /// Sets the required inputs of an [`TransactionBuilder`]. pub fn with_required_inputs(mut self, inputs: impl IntoIterator) -> Self { self.required_inputs = inputs.into_iter().collect(); self } - /// Sets the forbidden inputs of an [`InputSelection`]. - pub fn with_forbidden_inputs(mut self, inputs: impl IntoIterator) -> Self { - self.forbidden_inputs = inputs.into_iter().collect(); - self - } - - /// Sets the context inputs of an [`InputSelection`]. - pub fn with_context_inputs(mut self, context_inputs: impl IntoIterator) -> Self { - self.context_inputs = context_inputs.into_iter().collect(); - self - } - - /// Sets the burn of an [`InputSelection`]. + /// Sets the burn of an [`TransactionBuilder`]. pub fn with_burn(mut self, burn: impl Into>) -> Self { self.burn = burn.into(); self } - /// Sets the remainder address of an [`InputSelection`]. + /// Sets the remainder address of an [`TransactionBuilder`]. pub fn with_remainder_address(mut self, address: impl Into>) -> Self { self.remainders.address = address.into(); self } - /// Sets the mana allotments of an [`InputSelection`]. + /// Sets the mana allotments of an [`TransactionBuilder`]. pub fn with_mana_allotments(mut self, mana_allotments: impl IntoIterator) -> Self { self.mana_allotments = mana_allotments.into_iter().collect(); self @@ -421,26 +561,39 @@ impl InputSelection { self } - /// Sets the transaction capabilities. - pub fn with_transaction_capabilities( - mut self, - transaction_capabilities: impl Into, - ) -> Self { - self.transaction_capabilities = transaction_capabilities.into(); - self - } - pub(crate) fn all_outputs(&self) -> impl Iterator { - self.non_remainder_outputs() - .chain(self.remainders.data.iter().map(|r| &r.output)) - .chain(&self.remainders.storage_deposit_returns) + self.non_remainder_outputs().chain(self.remainder_outputs()) } pub(crate) fn non_remainder_outputs(&self) -> impl Iterator { self.provided_outputs.iter().chain(&self.added_outputs) } - fn required_account_nft_addresses(&self, input: &InputSigningData) -> Result, Error> { + pub(crate) fn remainder_outputs(&self) -> impl Iterator { + self.remainders + .data + .iter() + .map(|r| &r.output) + .chain(&self.remainders.storage_deposit_returns) + } + + pub(crate) fn context_inputs(&self) -> impl Iterator + '_ { + self.bic_context_inputs + .iter() + .copied() + .map(ContextInput::from) + .chain(self.commitment_context_input.map(ContextInput::from)) + .chain(self.selected_inputs.iter().enumerate().filter_map(|(idx, input)| { + self.reward_context_inputs + .contains(input.output_id()) + .then_some(RewardContextInput::new(idx as u16).unwrap().into()) + })) + } + + fn required_account_nft_addresses( + &self, + input: &InputSigningData, + ) -> Result, TransactionBuilderError> { let required_address = input .output .required_address( @@ -532,7 +685,7 @@ impl InputSelection { mut inputs: Vec, commitment_slot_index: SlotIndex, committable_age_range: CommittableAgeRange, - ) -> Result, Error> { + ) -> Result, TransactionBuilderError> { // initially sort by output to make it deterministic // TODO: rethink this, we only need it deterministic for tests, for the protocol it doesn't matter, also there // might be a more efficient way to do this @@ -621,133 +774,4 @@ impl InputSelection { Ok(sorted_inputs) } - - fn validate_transitions(inputs: &[InputSigningData], outputs: &[Output]) -> Result<(), Error> { - let mut input_native_tokens_builder = NativeTokensBuilder::new(); - let mut output_native_tokens_builder = NativeTokensBuilder::new(); - let mut input_accounts = Vec::new(); - let mut input_chains_foundries = hashbrown::HashMap::new(); - let mut input_foundries = Vec::new(); - let mut input_nfts = Vec::new(); - - for input in inputs { - if let Some(native_token) = input.output.native_token() { - input_native_tokens_builder.add_native_token(*native_token)?; - } - match &input.output { - Output::Basic(basic) => { - if basic.is_implicit_account() { - input_accounts.push(input); - } - } - Output::Account(_) => { - input_accounts.push(input); - } - Output::Foundry(foundry) => { - input_chains_foundries.insert(foundry.chain_id(), (input.output_id(), &input.output)); - input_foundries.push(input); - } - Output::Nft(_) => { - input_nfts.push(input); - } - _ => {} - } - } - - for output in outputs { - if let Some(native_token) = output.native_token() { - output_native_tokens_builder.add_native_token(*native_token)?; - } - } - - // Validate utxo chain transitions - for output in outputs { - match output { - Output::Account(account_output) => { - // Null id outputs are just minted and can't be a transition - if account_output.account_id().is_null() { - continue; - } - - let account_input = input_accounts - .iter() - .find(|i| is_account_with_id(&i.output, account_output.account_id(), i.output_id())) - .expect("ISA is broken because there is no account input"); - - match &account_input.output { - Output::Account(account) => { - if let Err(err) = AccountOutput::transition_inner( - account, - account_output, - &input_chains_foundries, - outputs, - ) { - log::debug!("validate_transitions error {err:?}"); - return Err(Error::UnfulfillableRequirement(Requirement::Account( - *account_output.account_id(), - ))); - } - } - Output::Basic(_) => { - // TODO https://github.com/iotaledger/iota-sdk/issues/1664 - } - _ => panic!( - "unreachable: \"input_accounts\" only contains account outputs and implicit account (basic) outputs" - ), - } - } - Output::Foundry(foundry_output) => { - let foundry_id = foundry_output.id(); - let foundry_input = input_foundries.iter().find(|i| { - if let Output::Foundry(foundry_input) = &i.output { - foundry_id == foundry_input.id() - } else { - false - } - }); - if let Some(foundry_input) = foundry_input { - if let Err(err) = FoundryOutput::transition_inner( - foundry_input.output.as_foundry(), - foundry_output, - input_native_tokens_builder.deref(), - output_native_tokens_builder.deref(), - // We use `all` capabilities here because this transition may be burning - // native tokens, and validation will fail without the capability. - &TransactionCapabilities::all(), - ) { - log::debug!("validate_transitions error {err:?}"); - return Err(Error::UnfulfillableRequirement(Requirement::Foundry( - foundry_output.id(), - ))); - } - } - } - Output::Nft(nft_output) => { - // Null id outputs are just minted and can't be a transition - if nft_output.nft_id().is_null() { - continue; - } - - let nft_input = input_nfts - .iter() - .find(|i| { - if let Output::Nft(nft_input) = &i.output { - *nft_output.nft_id() == nft_input.nft_id_non_null(i.output_id()) - } else { - false - } - }) - .expect("ISA is broken because there is no nft input"); - - if let Err(err) = NftOutput::transition_inner(nft_input.output.as_nft(), nft_output) { - log::debug!("validate_transitions error {err:?}"); - return Err(Error::UnfulfillableRequirement(Requirement::Nft(*nft_output.nft_id()))); - } - } - // other output types don't do transitions - _ => {} - } - } - Ok(()) - } } diff --git a/sdk/src/client/api/block_builder/input_selection/remainder.rs b/sdk/src/client/api/block_builder/transaction_builder/remainder.rs similarity index 62% rename from sdk/src/client/api/block_builder/input_selection/remainder.rs rename to sdk/src/client/api/block_builder/transaction_builder/remainder.rs index 1e9442ac7a..7111bdfb7b 100644 --- a/sdk/src/client/api/block_builder/input_selection/remainder.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/remainder.rs @@ -5,26 +5,23 @@ use alloc::collections::BTreeMap; use std::collections::HashMap; use crypto::keys::bip44::Bip44; +use primitive_types::U256; -use super::{Error, InputSelection}; +use super::{TransactionBuilder, TransactionBuilderError}; use crate::{ - client::api::{ - input_selection::requirement::native_tokens::{get_native_tokens, get_native_tokens_diff}, - RemainderData, - }, + client::api::{transaction_builder::requirement::native_tokens::get_native_tokens_diff, RemainderData}, types::block::{ address::{Address, Ed25519Address}, output::{ - unlock_condition::AddressUnlockCondition, AccountOutput, AnchorOutput, BasicOutput, BasicOutputBuilder, - NativeTokens, NativeTokensBuilder, NftOutput, Output, StorageScoreParameters, + unlock_condition::AddressUnlockCondition, AccountOutput, BasicOutput, BasicOutputBuilder, NativeToken, + NftOutput, Output, StorageScoreParameters, TokenId, }, - Error as BlockError, }, }; -impl InputSelection { +impl TransactionBuilder { /// Updates the remainders, overwriting old values. - pub(crate) fn update_remainders(&mut self) -> Result<(), Error> { + pub(crate) fn update_remainders(&mut self) -> Result<(), TransactionBuilderError> { let (storage_deposit_returns, remainders) = self.storage_deposit_returns_and_remainders()?; self.remainders.storage_deposit_returns = storage_deposit_returns; @@ -34,7 +31,7 @@ impl InputSelection { } /// Gets the remainder address from configuration of finds one from the inputs. - pub(crate) fn get_remainder_address(&self) -> Result)>, Error> { + pub(crate) fn get_remainder_address(&self) -> Result)>, TransactionBuilderError> { if let Some(remainder_address) = &self.remainders.address { // Search in inputs for the Bip44 chain for the remainder address, so the ledger can regenerate it for input in self.available_inputs.iter().chain(self.selected_inputs.iter()) { @@ -70,26 +67,9 @@ impl InputSelection { Ok(None) } - pub(crate) fn remainder_amount(&self) -> Result<(u64, bool, bool), Error> { - let mut input_native_tokens = get_native_tokens(self.selected_inputs.iter().map(|input| &input.output))?; - let mut output_native_tokens = get_native_tokens(self.non_remainder_outputs())?; - let (minted_native_tokens, melted_native_tokens) = self.get_minted_and_melted_native_tokens()?; - - input_native_tokens.merge(minted_native_tokens)?; - output_native_tokens.merge(melted_native_tokens)?; - - if let Some(burn) = self.burn.as_ref() { - output_native_tokens.merge(NativeTokensBuilder::from(burn.native_tokens.clone()))?; - } - - let native_tokens_diff = get_native_tokens_diff(&input_native_tokens, &output_native_tokens)?; - - self.required_remainder_amount(native_tokens_diff) - } - pub(crate) fn storage_deposit_returns_and_remainders( &mut self, - ) -> Result<(Vec, Vec), Error> { + ) -> Result<(Vec, Vec), TransactionBuilderError> { let (input_amount, output_amount, inputs_sdr, outputs_sdr) = self.amount_sums(); let mut storage_deposit_returns = Vec::new(); @@ -110,48 +90,34 @@ impl InputSelection { } } - let mut input_native_tokens = get_native_tokens(self.selected_inputs.iter().map(|input| &input.output))?; - let mut output_native_tokens = get_native_tokens(self.non_remainder_outputs())?; - let (minted_native_tokens, melted_native_tokens) = self.get_minted_and_melted_native_tokens()?; - - input_native_tokens.merge(minted_native_tokens)?; - output_native_tokens.merge(melted_native_tokens)?; - - if let Some(burn) = self.burn.as_ref() { - output_native_tokens.merge(NativeTokensBuilder::from(burn.native_tokens.clone()))?; - } - - let native_tokens_diff = get_native_tokens_diff(&input_native_tokens, &output_native_tokens)?; + let (input_nts, output_nts) = self.get_input_output_native_tokens(); + log::debug!("input_nts: {input_nts:#?}"); + log::debug!("output_nts: {output_nts:#?}"); + let native_tokens_diff = get_native_tokens_diff(input_nts, output_nts); let (input_mana, output_mana) = self.mana_sums(false)?; - if input_amount == output_amount && input_mana == output_mana && native_tokens_diff.is_none() { - log::debug!("No remainder required"); - return Ok((storage_deposit_returns, Vec::new())); - } + let amount_diff = input_amount.checked_sub(output_amount).expect("amount underflow"); + let mut mana_diff = input_mana.checked_sub(output_mana).expect("mana underflow"); - let amount_diff = input_amount - .checked_sub(output_amount) - .ok_or(BlockError::ConsumedAmountOverflow)?; - let mut mana_diff = input_mana - .checked_sub(output_mana) - .ok_or(BlockError::ConsumedManaOverflow)?; + // If we are burning mana, then we can subtract out the burned amount. + if self.burn.as_ref().map_or(false, |b| b.mana()) { + mana_diff = mana_diff.saturating_sub(self.initial_mana_excess()?); + } let (remainder_address, chain) = self .get_remainder_address()? - .ok_or(Error::MissingInputWithEd25519Address)?; + .ok_or(TransactionBuilderError::MissingInputWithEd25519Address)?; // If there is a mana remainder, try to fit it in an existing output - if input_mana > output_mana { - if self.output_for_added_mana_exists(&remainder_address) { - log::debug!("Allocating {mana_diff} excess input mana for output with address {remainder_address}"); - self.remainders.added_mana = std::mem::take(&mut mana_diff); - // If we have no other remainders, we are done - if input_amount == output_amount && native_tokens_diff.is_none() { - log::debug!("No more remainder required"); - return Ok((storage_deposit_returns, Vec::new())); - } - } + if mana_diff > 0 && self.output_for_added_mana_exists(&remainder_address) { + log::debug!("Allocating {mana_diff} excess input mana for output with address {remainder_address}"); + self.remainders.added_mana = std::mem::take(&mut mana_diff); + } + + if input_amount == output_amount && mana_diff == 0 && native_tokens_diff.is_empty() { + log::debug!("No remainder required"); + return Ok((storage_deposit_returns, Vec::new())); } let remainder_outputs = create_remainder_outputs( @@ -181,12 +147,10 @@ impl InputSelection { pub(crate) fn get_output_for_added_mana(&mut self, remainder_address: &Address) -> Option<&mut Output> { // Establish the order in which we want to pick an output - let sort_order = HashMap::from([ - (AccountOutput::KIND, 1), - (BasicOutput::KIND, 2), - (NftOutput::KIND, 3), - (AnchorOutput::KIND, 4), - ]); + let sort_order = [AccountOutput::KIND, BasicOutput::KIND, NftOutput::KIND] + .into_iter() + .zip(0..) + .collect::>(); // Remove those that do not have an ordering and sort let ordered_outputs = self .provided_outputs @@ -210,11 +174,9 @@ impl InputSelection { /// Calculates the required amount for required remainder outputs (multiple outputs are required if multiple native /// tokens are remaining) and returns if there are native tokens as remainder. - pub(crate) fn required_remainder_amount( - &self, - remainder_native_tokens: Option, - ) -> Result<(u64, bool, bool), Error> { - let native_tokens_remainder = remainder_native_tokens.is_some(); + pub(crate) fn required_remainder_amount(&self) -> Result<(u64, bool, bool), TransactionBuilderError> { + let (input_nts, output_nts) = self.get_input_output_native_tokens(); + let remainder_native_tokens = get_native_tokens_diff(input_nts, output_nts); let remainder_builder = BasicOutputBuilder::new_with_minimum_amount(self.protocol_parameters.storage_score_parameters()) @@ -222,14 +184,19 @@ impl InputSelection { [0; 32], )))); - let remainder_amount = if let Some(native_tokens) = remainder_native_tokens { + let remainder_amount = if !remainder_native_tokens.is_empty() { let nt_remainder_amount = remainder_builder - .with_native_token(*native_tokens.first().unwrap()) + .with_native_token( + remainder_native_tokens + .first_key_value() + .map(|(token_id, amount)| NativeToken::new(*token_id, amount)) + .unwrap()?, + ) .finish_output()? .amount(); // Amount can be just multiplied, because all remainder outputs with a native token have the same storage // cost. - nt_remainder_amount * native_tokens.len() as u64 + nt_remainder_amount * remainder_native_tokens.len() as u64 } else { remainder_builder.finish_output()?.amount() }; @@ -239,46 +206,49 @@ impl InputSelection { let remainder_address = self.get_remainder_address()?.map(|v| v.0); // Mana can potentially be added to an appropriate existing output instead of a new remainder output - let mana_remainder = selected_mana > required_mana + let mut mana_remainder = selected_mana > required_mana && remainder_address.map_or(true, |remainder_address| { !self.output_for_added_mana_exists(&remainder_address) }); + // If we are burning mana, we may not need a mana remainder + if self.burn.as_ref().map_or(false, |b| b.mana()) { + let initial_excess = self.initial_mana_excess()?; + mana_remainder &= selected_mana > required_mana + initial_excess; + } - Ok((remainder_amount, native_tokens_remainder, mana_remainder)) + Ok((remainder_amount, !remainder_native_tokens.is_empty(), mana_remainder)) } } fn create_remainder_outputs( amount_diff: u64, mana_diff: u64, - native_tokens_diff: Option, + mut native_tokens: BTreeMap, remainder_address: Address, remainder_address_chain: Option, storage_score_parameters: StorageScoreParameters, -) -> Result, Error> { +) -> Result, TransactionBuilderError> { let mut remainder_outputs = Vec::new(); let mut remaining_amount = amount_diff; let mut catchall_native_token = None; // Start with the native tokens - if let Some(native_tokens) = native_tokens_diff { - if let Some((last, nts)) = native_tokens.split_last() { - // Save this one for the catchall - catchall_native_token.replace(*last); - // Create remainder outputs with min amount - for native_token in nts { - let output = BasicOutputBuilder::new_with_minimum_amount(storage_score_parameters) - .add_unlock_condition(AddressUnlockCondition::new(remainder_address.clone())) - .with_native_token(*native_token) - .finish_output()?; - log::debug!( - "Created remainder output of amount {}, mana {} and native token {native_token:?} for {remainder_address:?}", - output.amount(), - output.mana() - ); - remaining_amount = remaining_amount.saturating_sub(output.amount()); - remainder_outputs.push(output); - } + if let Some((token_id, amount)) = native_tokens.pop_last() { + // Save this one for the catchall + catchall_native_token.replace(NativeToken::new(token_id, amount)?); + // Create remainder outputs with min amount + for (token_id, amount) in native_tokens { + let output = BasicOutputBuilder::new_with_minimum_amount(storage_score_parameters) + .add_unlock_condition(AddressUnlockCondition::new(remainder_address.clone())) + .with_native_token(NativeToken::new(token_id, amount)?) + .finish_output()?; + log::debug!( + "Created remainder output of amount {}, mana {} and native token ({token_id}: {amount}) for {remainder_address:?}", + output.amount(), + output.mana() + ); + remaining_amount = remaining_amount.saturating_sub(output.amount()); + remainder_outputs.push(output); } } let mut catchall = BasicOutputBuilder::new_with_amount(remaining_amount) diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/account.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/account.rs similarity index 69% rename from sdk/src/client/api/block_builder/input_selection/requirement/account.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/account.rs index 87f8cd20cd..c3fa661855 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/account.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/account.rs @@ -1,11 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use super::{Error, InputSelection, Requirement}; -use crate::{ - client::secret::types::InputSigningData, - types::block::output::{AccountId, Output, OutputId}, -}; +use super::{Requirement, TransactionBuilder, TransactionBuilderError}; +use crate::types::block::output::{AccountId, Output, OutputId}; /// Checks if an output is an account with output ID that matches the given account ID. pub(crate) fn is_account_with_id(output: &Output, account_id: &AccountId, output_id: &OutputId) -> bool { @@ -26,12 +23,9 @@ pub(crate) fn is_account_with_id_non_null(output: &Output, account_id: &AccountI } } -impl InputSelection { +impl TransactionBuilder { /// Fulfills an account requirement by selecting the appropriate account from the available inputs. - pub(crate) fn fulfill_account_requirement( - &mut self, - account_id: AccountId, - ) -> Result, Error> { + pub(crate) fn fulfill_account_requirement(&mut self, account_id: AccountId) -> Result<(), TransactionBuilderError> { // Check if the requirement is already fulfilled. if let Some(input) = self .selected_inputs @@ -42,20 +36,33 @@ impl InputSelection { "{account_id:?} requirement already fulfilled by {:?}", input.output_id() ); - return Ok(Vec::new()); + return Ok(()); } + if !self.allow_additional_input_selection { + return Err(TransactionBuilderError::AdditionalInputsRequired(Requirement::Account( + account_id, + ))); + } + + use std::backtrace::Backtrace; + log::debug!("BACKTRACE: {:?}", Backtrace::force_capture()); + // Check if the requirement can be fulfilled. let index = self .available_inputs .iter() .position(|input| is_account_with_id(&input.output, &account_id, input.output_id())) - .ok_or(Error::UnfulfillableRequirement(Requirement::Account(account_id)))?; + .ok_or(TransactionBuilderError::UnfulfillableRequirement(Requirement::Account( + account_id, + )))?; // Remove the input from the available inputs, swap to make it O(1). let input = self.available_inputs.swap_remove(index); log::debug!("{account_id:?} requirement fulfilled by {:?}", input.output_id()); - Ok(vec![input]) + self.select_input(input)?; + + Ok(()) } } diff --git a/sdk/src/client/api/block_builder/transaction_builder/requirement/amount.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/amount.rs new file mode 100644 index 0000000000..ad2ae9f372 --- /dev/null +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/amount.rs @@ -0,0 +1,260 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::{collections::HashMap, sync::OnceLock}; + +use super::{Requirement, TransactionBuilder, TransactionBuilderError}; +use crate::{ + client::{api::transaction_builder::requirement::PriorityMap, secret::types::InputSigningData}, + types::block::{ + address::Address, + output::{ + unlock_condition::StorageDepositReturnUnlockCondition, AccountOutput, AccountOutputBuilder, BasicOutput, + FoundryOutput, FoundryOutputBuilder, MinimumOutputAmount, NftOutput, NftOutputBuilder, Output, + }, + slot::{SlotCommitmentId, SlotIndex}, + }, +}; + +/// Get the `StorageDepositReturnUnlockCondition`, if not expired. +pub(crate) fn sdruc_not_expired( + output: &Output, + slot_index: SlotIndex, +) -> Option<&StorageDepositReturnUnlockCondition> { + // PANIC: safe to unwrap as outputs without unlock conditions have been filtered out already. + let unlock_conditions = output.unlock_conditions().unwrap(); + + unlock_conditions.storage_deposit_return().and_then(|sdr| { + let expired = unlock_conditions + .expiration() + .map_or(false, |expiration| slot_index >= expiration.slot_index()); + + // We only have to send the storage deposit return back if the output is not expired + (!expired).then_some(sdr) + }) +} + +impl TransactionBuilder { + pub(crate) fn fulfill_amount_requirement(&mut self) -> Result<(), TransactionBuilderError> { + let (mut input_amount, mut output_amount) = self.amount_balance()?; + if input_amount >= output_amount { + log::debug!("Amount requirement already fulfilled"); + return Ok(()); + } + + log::debug!("Fulfilling amount requirement with input amount {input_amount}, output amount {output_amount}"); + + if !self.allow_additional_input_selection { + return Err(TransactionBuilderError::AdditionalInputsRequired(Requirement::Amount)); + } + // If we have no inputs to balance with, try reducing outputs instead + if self.available_inputs.is_empty() { + if !self.reduce_funds_of_chains(input_amount, &mut output_amount)? { + return Err(TransactionBuilderError::InsufficientAmount { + found: input_amount, + required: output_amount, + }); + } + } else { + let mut priority_map = PriorityMap::::generate(&mut self.available_inputs); + loop { + let Some(input) = priority_map.next(output_amount - input_amount, self.latest_slot_commitment_id) + else { + break; + }; + log::debug!("selecting input with amount {}", input.output.amount()); + self.select_input(input)?; + (input_amount, output_amount) = self.amount_balance()?; + // Try to reduce output funds + if self.reduce_funds_of_chains(input_amount, &mut output_amount)? { + break; + } + } + // Return unselected inputs to the available list + for input in priority_map.into_inputs() { + self.available_inputs.push(input); + } + if output_amount > input_amount { + return Err(TransactionBuilderError::InsufficientAmount { + found: input_amount, + required: output_amount, + }); + } + } + + Ok(()) + } + + pub(crate) fn amount_sums(&self) -> (u64, u64, HashMap, HashMap) { + let mut inputs_sum = 0; + let mut outputs_sum = 0; + let mut inputs_sdr = HashMap::new(); + let mut outputs_sdr = HashMap::new(); + + for selected_input in &self.selected_inputs { + inputs_sum += selected_input.output.amount(); + + if let Some(sdruc) = sdruc_not_expired(&selected_input.output, self.latest_slot_commitment_id.slot_index()) + { + *inputs_sdr.entry(sdruc.return_address().clone()).or_default() += sdruc.amount(); + } + } + + for output in self.non_remainder_outputs() { + outputs_sum += output.amount(); + + if let Output::Basic(output) = output { + if let Some(address) = output.simple_deposit_address() { + *outputs_sdr.entry(address.clone()).or_default() += output.amount(); + } + } + } + + // TODO explanation about that + for (sdr_address, input_sdr_amount) in &inputs_sdr { + let output_sdr_amount = outputs_sdr.get(sdr_address).unwrap_or(&0); + + if input_sdr_amount > output_sdr_amount { + outputs_sum += input_sdr_amount - output_sdr_amount; + } + } + + (inputs_sum, outputs_sum, inputs_sdr, outputs_sdr) + } + + pub(crate) fn amount_balance(&self) -> Result<(u64, u64), TransactionBuilderError> { + let (inputs_sum, mut outputs_sum, _, _) = self.amount_sums(); + let (remainder_amount, native_tokens_remainder, mana_remainder) = self.required_remainder_amount()?; + if inputs_sum > outputs_sum { + let diff = inputs_sum - outputs_sum; + + if remainder_amount > diff { + outputs_sum += remainder_amount - diff + } + } else if native_tokens_remainder || mana_remainder { + outputs_sum += remainder_amount + } + Ok((inputs_sum, outputs_sum)) + } + + fn reduce_funds_of_chains( + &mut self, + input_amount: u64, + output_amount: &mut u64, + ) -> Result { + if *output_amount > input_amount { + // Only consider automatically transitioned outputs. + for output in self.added_outputs.iter_mut() { + let missing_amount = *output_amount - input_amount; + let amount = output.amount(); + let minimum_amount = output.minimum_amount(self.protocol_parameters.storage_score_parameters()); + + let new_amount = if amount >= missing_amount + minimum_amount { + *output_amount = input_amount; + amount - missing_amount + } else { + *output_amount -= amount - minimum_amount; + minimum_amount + }; + + // PANIC: unwrap is fine as non-chain outputs have been filtered out already. + log::debug!( + "Reducing amount of {} to {} to fulfill amount requirement", + output.chain_id().unwrap(), + new_amount + ); + + *output = match output { + Output::Account(output) => AccountOutputBuilder::from(&*output) + .with_amount(new_amount) + .finish_output()?, + Output::Foundry(output) => FoundryOutputBuilder::from(&*output) + .with_amount(new_amount) + .finish_output()?, + Output::Nft(output) => NftOutputBuilder::from(&*output) + .with_amount(new_amount) + .finish_output()?, + _ => continue, + }; + + if *output_amount == input_amount { + break; + } + } + } + + Ok(input_amount >= *output_amount) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct AmountPriority { + kind_priority: usize, + has_native_token: bool, +} + +impl PartialOrd for AmountPriority { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for AmountPriority { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + (self.kind_priority, self.has_native_token).cmp(&(other.kind_priority, other.has_native_token)) + } +} + +impl From<&InputSigningData> for Option { + fn from(value: &InputSigningData) -> Self { + sort_order_type() + .get(&value.output.kind()) + .map(|&kind_priority| AmountPriority { + kind_priority, + has_native_token: value.output.native_token().is_some(), + }) + } +} + +/// Establish the order in which we want to pick an input +pub fn sort_order_type() -> &'static HashMap { + static MAP: OnceLock> = OnceLock::new(); + MAP.get_or_init(|| { + [ + BasicOutput::KIND, + AccountOutput::KIND, + NftOutput::KIND, + FoundryOutput::KIND, + ] + .into_iter() + .zip(0_usize..) + .collect::>() + }) +} + +impl PriorityMap { + fn next(&mut self, missing_amount: u64, slot_committment_id: SlotCommitmentId) -> Option { + let amount_sort = |output: &Output| { + let mut amount = output.amount(); + if let Some(sdruc) = sdruc_not_expired(output, slot_committment_id.slot_index()) { + amount -= sdruc.amount(); + } + // If the amount is greater than the missing amount, we want the smallest ones first + if amount >= missing_amount { + (false, amount) + // Otherwise, we want the biggest first + } else { + (true, u64::MAX - amount) + } + }; + if let Some((priority, mut inputs)) = self.0.pop_first() { + // Sort in reverse so we can pop from the back + inputs.sort_unstable_by(|i1, i2| amount_sort(&i2.output).cmp(&amount_sort(&i1.output))); + let input = inputs.pop(); + if !inputs.is_empty() { + self.0.insert(priority, inputs); + } + return input; + } + None + } +} diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/context_inputs.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/context_inputs.rs similarity index 72% rename from sdk/src/client/api/block_builder/input_selection/requirement/context_inputs.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/context_inputs.rs index ed95a7b027..963754114c 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/context_inputs.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/context_inputs.rs @@ -1,37 +1,35 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use super::{Error, InputSelection}; -use crate::{ - client::secret::types::InputSigningData, - types::block::{ - context_input::{BlockIssuanceCreditContextInput, CommitmentContextInput, RewardContextInput}, - output::{AccountId, DelegationOutputBuilder, Output}, - }, +use super::{TransactionBuilder, TransactionBuilderError}; +use crate::types::block::{ + context_input::{BlockIssuanceCreditContextInput, CommitmentContextInput}, + output::{AccountId, DelegationOutputBuilder, Output}, }; -impl InputSelection { - pub(crate) fn fulfill_context_inputs_requirement(&mut self) -> Result, Error> { +impl TransactionBuilder { + pub(crate) fn fulfill_context_inputs_requirement(&mut self) -> Result<(), TransactionBuilderError> { let mut needs_commitment_context = false; - for (idx, input) in self.selected_inputs.iter().enumerate() { + for input in &self.selected_inputs { match &input.output { // Transitioning an issuer account requires a BlockIssuanceCreditContextInput. Output::Account(account) => { if account.features().block_issuer().is_some() { log::debug!("Adding block issuance context input for transitioned account output"); - self.context_inputs.insert( - BlockIssuanceCreditContextInput::from(account.account_id_non_null(input.output_id())) - .into(), - ); + self.bic_context_inputs.insert(BlockIssuanceCreditContextInput::from( + account.account_id_non_null(input.output_id()), + )); } } // Transitioning an implicit account requires a BlockIssuanceCreditContextInput. Output::Basic(basic) => { if basic.is_implicit_account() { log::debug!("Adding block issuance context input for transitioned implicit account output"); - self.context_inputs - .insert(BlockIssuanceCreditContextInput::from(AccountId::from(input.output_id())).into()); + self.bic_context_inputs + .insert(BlockIssuanceCreditContextInput::from(AccountId::from( + input.output_id(), + ))); } } _ => (), @@ -49,7 +47,7 @@ impl InputSelection { if self.mana_rewards.get(input.output_id()).is_some() { log::debug!("Adding reward and commitment context input for output claiming mana rewards"); - self.context_inputs.insert(RewardContextInput::new(idx as _)?.into()); + self.reward_context_inputs.insert(*input.output_id()); needs_commitment_context = true; } } @@ -81,26 +79,17 @@ impl InputSelection { needs_commitment_context = true; } // BlockIssuanceCreditContextInput requires a CommitmentContextInput. - if self - .context_inputs - .iter() - .any(|c| c.kind() == BlockIssuanceCreditContextInput::KIND) - { + if !self.bic_context_inputs.is_empty() { // TODO https://github.com/iotaledger/iota-sdk/issues/1740 log::debug!("Adding commitment context input for output with block issuance credit context input"); needs_commitment_context = true; } - if needs_commitment_context - && !self - .context_inputs - .iter() - .any(|c| c.kind() == CommitmentContextInput::KIND) - { + if needs_commitment_context && self.commitment_context_input.is_none() { // TODO https://github.com/iotaledger/iota-sdk/issues/1740 - self.context_inputs - .insert(CommitmentContextInput::new(self.latest_slot_commitment_id).into()); + self.commitment_context_input + .replace(CommitmentContextInput::new(self.latest_slot_commitment_id)); } - Ok(Vec::new()) + Ok(()) } } diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/delegation.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/delegation.rs similarity index 77% rename from sdk/src/client/api/block_builder/input_selection/requirement/delegation.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/delegation.rs index b2e11a5269..96b6faf949 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/delegation.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/delegation.rs @@ -1,11 +1,8 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use super::{Error, InputSelection, Requirement}; -use crate::{ - client::secret::types::InputSigningData, - types::block::output::{DelegationId, Output, OutputId}, -}; +use super::{Requirement, TransactionBuilder, TransactionBuilderError}; +use crate::types::block::output::{DelegationId, Output, OutputId}; /// Checks if an output is an delegation with a given delegation ID. /// Assumes that the output delegation ID can be null and hashes the output ID. @@ -29,12 +26,12 @@ pub(crate) fn is_delegation_with_id_non_null(output: &Output, delegation_id: &De } } -impl InputSelection { +impl TransactionBuilder { /// Fulfills an delegation requirement by selecting the appropriate delegation from the available inputs. pub(crate) fn fulfill_delegation_requirement( &mut self, delegation_id: DelegationId, - ) -> Result, Error> { + ) -> Result<(), TransactionBuilderError> { // Check if the requirement is already fulfilled. if let Some(input) = self .selected_inputs @@ -45,7 +42,13 @@ impl InputSelection { "{delegation_id:?} requirement already fulfilled by {:?}", input.output_id() ); - return Ok(Vec::new()); + return Ok(()); + } + + if !self.allow_additional_input_selection { + return Err(TransactionBuilderError::AdditionalInputsRequired( + Requirement::Delegation(delegation_id), + )); } // Check if the requirement can be fulfilled. @@ -53,12 +56,16 @@ impl InputSelection { .available_inputs .iter() .position(|input| is_delegation_with_id(&input.output, &delegation_id, input.output_id())) - .ok_or(Error::UnfulfillableRequirement(Requirement::Delegation(delegation_id)))?; + .ok_or(TransactionBuilderError::UnfulfillableRequirement( + Requirement::Delegation(delegation_id), + ))?; // Remove the input from the available inputs, swap to make it O(1). let input = self.available_inputs.swap_remove(index); log::debug!("{delegation_id:?} requirement fulfilled by {:?}", input.output_id()); - Ok(vec![input]) + self.select_input(input)?; + + Ok(()) } } diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/ed25519.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/ed25519.rs similarity index 84% rename from sdk/src/client/api/block_builder/input_selection/requirement/ed25519.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/ed25519.rs index 5862af536c..8f53fb28fa 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/ed25519.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/ed25519.rs @@ -1,10 +1,10 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use super::{Error, InputSelection, Requirement}; +use super::{Requirement, TransactionBuilder, TransactionBuilderError}; use crate::{client::secret::types::InputSigningData, types::block::address::Address}; -impl InputSelection { +impl TransactionBuilder { // Checks if a selected input unlocks a given ED25519 address. fn selected_unlocks_ed25519_address(&self, input: &InputSigningData, address: &Address) -> bool { let required_address = input @@ -39,7 +39,7 @@ impl InputSelection { } /// Fulfills an ed25519 sender requirement by selecting an available input that unlocks its address. - pub(crate) fn fulfill_ed25519_requirement(&mut self, address: &Address) -> Result, Error> { + pub(crate) fn fulfill_ed25519_requirement(&mut self, address: &Address) -> Result<(), TransactionBuilderError> { // Checks if the requirement is already fulfilled. if let Some(input) = self .selected_inputs @@ -50,7 +50,13 @@ impl InputSelection { "{address:?} sender requirement already fulfilled by {:?}", input.output_id() ); - return Ok(Vec::new()); + return Ok(()); + } + + if !self.allow_additional_input_selection { + return Err(TransactionBuilderError::AdditionalInputsRequired(Requirement::Ed25519( + address.clone(), + ))); } // Checks if the requirement can be fulfilled by a basic output. @@ -75,9 +81,13 @@ impl InputSelection { log::debug!("{address:?} sender requirement fulfilled by {:?}", input.output_id(),); - Ok(vec![input]) + self.select_input(input)?; + + Ok(()) } - None => Err(Error::UnfulfillableRequirement(Requirement::Ed25519(address.clone()))), + None => Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Ed25519( + address.clone(), + ))), } } } diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/foundry.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/foundry.rs similarity index 64% rename from sdk/src/client/api/block_builder/input_selection/requirement/foundry.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/foundry.rs index f1a2f5be1b..1404de1872 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/foundry.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/foundry.rs @@ -1,11 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use super::{Error, InputSelection, Requirement}; -use crate::{ - client::secret::types::InputSigningData, - types::block::output::{FoundryId, Output}, -}; +use super::{Requirement, TransactionBuilder, TransactionBuilderError}; +use crate::types::block::output::{FoundryId, Output}; /// Checks if an output is a foundry with a given foundry ID. pub(crate) fn is_foundry_with_id(output: &Output, foundry_id: &FoundryId) -> bool { @@ -16,12 +13,9 @@ pub(crate) fn is_foundry_with_id(output: &Output, foundry_id: &FoundryId) -> boo } } -impl InputSelection { +impl TransactionBuilder { /// Fulfills a foundry requirement by selecting the appropriate foundry from the available inputs. - pub(crate) fn fulfill_foundry_requirement( - &mut self, - foundry_id: FoundryId, - ) -> Result, Error> { + pub(crate) fn fulfill_foundry_requirement(&mut self, foundry_id: FoundryId) -> Result<(), TransactionBuilderError> { // Check if the requirement is already fulfilled. if let Some(input) = self .selected_inputs @@ -32,7 +26,13 @@ impl InputSelection { "{foundry_id:?} requirement already fulfilled by {:?}", input.output_id() ); - return Ok(Vec::new()); + return Ok(()); + } + + if !self.allow_additional_input_selection { + return Err(TransactionBuilderError::AdditionalInputsRequired(Requirement::Foundry( + foundry_id, + ))); } // Check if the requirement can be fulfilled. @@ -40,12 +40,16 @@ impl InputSelection { .available_inputs .iter() .position(|input| is_foundry_with_id(&input.output, &foundry_id)) - .ok_or(Error::UnfulfillableRequirement(Requirement::Foundry(foundry_id)))?; + .ok_or(TransactionBuilderError::UnfulfillableRequirement(Requirement::Foundry( + foundry_id, + )))?; // Remove the input from the available inputs, swap to make it O(1). let input = self.available_inputs.swap_remove(index); log::debug!("{foundry_id:?} requirement fulfilled by {:?}", input.output_id()); - Ok(vec![input]) + self.select_input(input)?; + + Ok(()) } } diff --git a/sdk/src/client/api/block_builder/transaction_builder/requirement/issuer.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/issuer.rs new file mode 100644 index 0000000000..18120a71a9 --- /dev/null +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/issuer.rs @@ -0,0 +1,20 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use super::{Requirement, TransactionBuilder, TransactionBuilderError}; +use crate::types::block::address::Address; + +impl TransactionBuilder { + /// Fulfills an issuer requirement by fulfilling the equivalent sender requirement. + /// Potentially converts the error for a more accurate one. + pub(crate) fn fulfill_issuer_requirement(&mut self, address: &Address) -> Result<(), TransactionBuilderError> { + log::debug!("Treating {address:?} issuer requirement as a sender requirement"); + + self.fulfill_sender_requirement(address).map_err(|e| match e { + TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(_)) => { + TransactionBuilderError::UnfulfillableRequirement(Requirement::Issuer(address.clone())) + } + e => e, + }) + } +} diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/mana.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/mana.rs similarity index 57% rename from sdk/src/client/api/block_builder/input_selection/requirement/mana.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/mana.rs index 95e4ae81f0..58a0b8989f 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/mana.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/mana.rs @@ -1,30 +1,28 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; +use std::{collections::HashMap, sync::OnceLock}; -use super::{Error, InputSelection}; +use super::{TransactionBuilder, TransactionBuilderError}; use crate::{ client::{ - api::input_selection::{MinManaAllotment, Requirement}, + api::transaction_builder::{requirement::PriorityMap, MinManaAllotment, Requirement}, secret::types::InputSigningData, }, types::block::{ address::Address, input::{Input, UtxoInput}, mana::ManaAllotment, - output::{AccountOutputBuilder, Output}, + output::{AccountOutput, AccountOutputBuilder, BasicOutput, FoundryOutput, NftOutput, Output}, payload::{signed_transaction::Transaction, SignedTransactionPayload}, signature::Ed25519Signature, unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, - Error as BlockError, + BlockError, }, }; -impl InputSelection { - pub(crate) fn fulfill_mana_requirement(&mut self) -> Result, Error> { +impl TransactionBuilder { + pub(crate) fn fulfill_mana_requirement(&mut self) -> Result<(), TransactionBuilderError> { let Some(MinManaAllotment { issuer_id, reference_mana_cost, @@ -32,13 +30,16 @@ impl InputSelection { }) = self.min_mana_allotment else { // If there is no min allotment calculation needed, just check mana - return self.get_inputs_for_mana_balance(); + self.get_inputs_for_mana_balance()?; + return Ok(()); }; + let mut should_recalculate = false; + if !self.selected_inputs.is_empty() && self.all_outputs().next().is_some() { self.selected_inputs = Self::sort_input_signing_data( std::mem::take(&mut self.selected_inputs), - self.creation_slot, + self.latest_slot_commitment_id.slot_index(), self.protocol_parameters.committable_age_range(), )?; @@ -60,9 +61,8 @@ impl InputSelection { // Add the empty allotment so the work score includes it self.mana_allotments.entry(issuer_id).or_default(); - // If the transaction fails to build, just keep going in case another requirement helps let transaction = builder - .with_context_inputs(self.context_inputs.clone()) + .with_context_inputs(self.context_inputs()) .with_mana_allotments( self.mana_allotments .iter() @@ -84,27 +84,33 @@ impl InputSelection { } = self .min_mana_allotment .as_mut() - .ok_or(Error::UnfulfillableRequirement(Requirement::Mana))?; + .ok_or(TransactionBuilderError::UnfulfillableRequirement(Requirement::Mana))?; // Add the required allotment to the issuing allotment if required_allotment_mana > self.mana_allotments[issuer_id] { log::debug!("Allotting at least {required_allotment_mana} mana to account ID {issuer_id}"); - let additional_allotment = required_allotment_mana - self.mana_allotments[&issuer_id]; + let additional_allotment = required_allotment_mana - self.mana_allotments[issuer_id]; log::debug!("{additional_allotment} additional mana required to meet minimum allotment"); // Unwrap: safe because we always add the record above *self.mana_allotments.get_mut(issuer_id).unwrap() = required_allotment_mana; log::debug!("Adding {additional_allotment} to allotment debt {allotment_debt}"); *allotment_debt += additional_allotment; + should_recalculate = true; } else { log::debug!("Setting allotment debt to {}", self.mana_allotments[issuer_id]); *allotment_debt = self.mana_allotments[issuer_id]; + // Since the allotment is fine, check if the mana balance is good because + // we can exit early in that case. + let (input_mana, output_mana) = self.mana_sums(true)?; + if input_mana == output_mana { + log::debug!("allotments and mana are both correct, no further action needed"); + return Ok(()); + } } - self.reduce_account_output()?; + should_recalculate |= self.reduce_account_output()?; } else { - if !self.requirements.contains(&Requirement::Mana) { - self.requirements.push(Requirement::Mana); - } + should_recalculate = true; } // Remainders can only be calculated when the input mana is >= the output mana @@ -113,20 +119,16 @@ impl InputSelection { self.update_remainders()?; } - let additional_inputs = self.get_inputs_for_mana_balance()?; - // If we needed more inputs to cover the additional allotment mana - // then update remainders and re-run this requirement - if !additional_inputs.is_empty() { - if !self.requirements.contains(&Requirement::Mana) { - self.requirements.push(Requirement::Mana); - } - return Ok(additional_inputs); + should_recalculate |= self.get_inputs_for_mana_balance()?; + + if should_recalculate && !self.requirements.contains(&Requirement::Mana) { + self.requirements.push(Requirement::Mana); } - Ok(Vec::new()) + Ok(()) } - pub(crate) fn reduce_account_output(&mut self) -> Result<(), Error> { + fn reduce_account_output(&mut self) -> Result { let MinManaAllotment { issuer_id, allotment_debt, @@ -134,7 +136,7 @@ impl InputSelection { } = self .min_mana_allotment .as_mut() - .ok_or(Error::UnfulfillableRequirement(Requirement::Mana))?; + .ok_or(TransactionBuilderError::UnfulfillableRequirement(Requirement::Mana))?; if let Some(output) = self .provided_outputs .iter_mut() @@ -153,11 +155,12 @@ impl InputSelection { .finish_output()?; *allotment_debt = allotment_debt.saturating_sub(output_mana); log::debug!("Allotment debt after reduction: {}", allotment_debt); + return Ok(true); } - Ok(()) + Ok(false) } - pub(crate) fn null_transaction_unlocks(&self) -> Result { + pub(crate) fn null_transaction_unlocks(&self) -> Result { let mut blocks = Vec::new(); let mut block_indexes = HashMap::::new(); @@ -197,7 +200,7 @@ impl InputSelection { // than the current block index match &required_address { Address::Ed25519(_) | Address::ImplicitAccountCreation(_) => {} - _ => Err(Error::MissingInputWithEd25519Address)?, + _ => Err(TransactionBuilderError::MissingInputWithEd25519Address)?, } let block = SignatureUnlock::new( @@ -235,57 +238,164 @@ impl InputSelection { Ok(Unlocks::new(blocks)?) } - pub(crate) fn get_inputs_for_mana_balance(&mut self) -> Result, Error> { - let (mut selected_mana, required_mana) = self.mana_sums(true)?; + pub(crate) fn get_inputs_for_mana_balance(&mut self) -> Result { + let (mut selected_mana, mut required_mana) = self.mana_sums(true)?; log::debug!("Mana requirement selected mana: {selected_mana}, required mana: {required_mana}"); + let mut added_inputs = false; if selected_mana >= required_mana { log::debug!("Mana requirement already fulfilled"); - Ok(Vec::new()) } else { - let mut inputs = Vec::new(); - - // TODO we should do as for the amount and have preferences on which inputs to pick. - while let Some(input) = self.available_inputs.pop() { - selected_mana += self.total_mana(&input)?; - inputs.push(input); + if !self.allow_additional_input_selection { + return Err(TransactionBuilderError::AdditionalInputsRequired(Requirement::Mana)); + } + let include_generated = self.burn.as_ref().map_or(true, |b| !b.generated_mana()); + let mut priority_map = PriorityMap::::generate(&mut self.available_inputs); + loop { + let Some(input) = priority_map.next(required_mana - selected_mana) else { + break; + }; + selected_mana += self.total_mana(&input, include_generated)?; + if let Some(output) = self.select_input(input)? { + required_mana += output.mana(); + } + added_inputs = true; if selected_mana >= required_mana { break; } } - if selected_mana < required_mana { - return Err(Error::InsufficientMana { - found: selected_mana, - required: required_mana, - }); + // Return unselected inputs to the available list + for input in priority_map.into_inputs() { + self.available_inputs.push(input); } + } + Ok(added_inputs) + } - Ok(inputs) + pub(crate) fn initial_mana_excess(&self) -> Result { + let output_mana = self.provided_outputs.iter().map(|o| o.mana()).sum::(); + let mut input_mana = 0; + let include_generated = self.burn.as_ref().map_or(true, |b| !b.generated_mana()); + + for input in self + .selected_inputs + .iter() + .filter(|i| self.required_inputs.contains(i.output_id())) + { + input_mana += self.total_mana(input, include_generated)?; } + + Ok(input_mana.saturating_sub(output_mana)) } - pub(crate) fn mana_sums(&self, include_remainders: bool) -> Result<(u64, u64), Error> { - let required_mana = if include_remainders { - self.all_outputs().map(|o| o.mana()).sum::() + self.remainders.added_mana - } else { - self.non_remainder_outputs().map(|o| o.mana()).sum::() - } + self.mana_allotments.values().sum::(); + pub(crate) fn mana_sums(&self, include_remainders: bool) -> Result<(u64, u64), TransactionBuilderError> { + let mut required_mana = + self.non_remainder_outputs().map(|o| o.mana()).sum::() + self.mana_allotments.values().sum::(); + if include_remainders { + // Add the remainder outputs mana as well as the excess mana we've allocated to add to existing outputs + // later. + required_mana += self.remainder_outputs().map(|o| o.mana()).sum::() + self.remainders.added_mana; + } + + Ok((self.total_selected_mana(None)?, required_mana)) + } + + pub(crate) fn total_selected_mana( + &self, + include_generated: impl Into> + Copy, + ) -> Result { let mut selected_mana = 0; + let include_generated = include_generated + .into() + .unwrap_or_else(|| self.burn.as_ref().map_or(true, |b| !b.generated_mana())); for input in &self.selected_inputs { - selected_mana += self.total_mana(input)?; + selected_mana += self.total_mana(input, include_generated)?; } - Ok((selected_mana, required_mana)) + + Ok(selected_mana) } - fn total_mana(&self, input: &InputSigningData) -> Result { + fn total_mana(&self, input: &InputSigningData, include_generated: bool) -> Result { Ok(self.mana_rewards.get(input.output_id()).copied().unwrap_or_default() - + input.output.available_mana( - &self.protocol_parameters, - input.output_id().transaction_id().slot_index(), - self.creation_slot, - )?) + + if include_generated { + input.output.available_mana( + &self.protocol_parameters, + input.output_id().transaction_id().slot_index(), + self.creation_slot, + )? + } else { + input.output.mana() + }) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct ManaPriority { + kind_priority: usize, + has_native_token: bool, +} + +impl PartialOrd for ManaPriority { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for ManaPriority { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + (self.kind_priority, self.has_native_token).cmp(&(other.kind_priority, other.has_native_token)) + } +} + +impl From<&InputSigningData> for Option { + fn from(value: &InputSigningData) -> Self { + sort_order_type() + .get(&value.output.kind()) + .map(|&kind_priority| ManaPriority { + kind_priority, + has_native_token: value.output.native_token().is_some(), + }) + } +} + +/// Establish the order in which we want to pick an input +pub fn sort_order_type() -> &'static HashMap { + static MAP: OnceLock> = OnceLock::new(); + MAP.get_or_init(|| { + [ + BasicOutput::KIND, + NftOutput::KIND, + AccountOutput::KIND, + FoundryOutput::KIND, + ] + .into_iter() + .zip(0_usize..) + .collect::>() + }) +} + +impl PriorityMap { + fn next(&mut self, missing_mana: u64) -> Option { + let mana_sort = |mana: u64| { + // If the mana is greater than the missing mana, we want the smallest ones first + if mana >= missing_mana { + (false, mana) + // Otherwise, we want the biggest first + } else { + (true, u64::MAX - mana) + } + }; + if let Some((priority, mut inputs)) = self.0.pop_first() { + // Sort in reverse so we can pop from the back + inputs.sort_unstable_by(|i1, i2| mana_sort(i2.output.mana()).cmp(&mana_sort(i1.output.mana()))); + let input = inputs.pop(); + if !inputs.is_empty() { + self.0.insert(priority, inputs); + } + return input; + } + None } } diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/mod.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/mod.rs similarity index 79% rename from sdk/src/client/api/block_builder/input_selection/requirement/mod.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/mod.rs index ac2751ae56..1d257b9d4f 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/mod.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/mod.rs @@ -13,16 +13,19 @@ pub(crate) mod native_tokens; pub(crate) mod nft; pub(crate) mod sender; +use alloc::collections::BTreeMap; + use self::{ account::is_account_with_id_non_null, delegation::is_delegation_with_id_non_null, foundry::is_foundry_with_id, nft::is_nft_with_id_non_null, }; -use super::{Error, InputSelection}; +use super::{TransactionBuilder, TransactionBuilderError}; use crate::{ client::secret::types::InputSigningData, types::block::{ address::Address, output::{AccountId, ChainId, DelegationId, Features, FoundryId, NftId, Output}, + payload::signed_transaction::TransactionCapabilityFlag, }, }; @@ -53,10 +56,10 @@ pub enum Requirement { ContextInputs, } -impl InputSelection { +impl TransactionBuilder { /// Fulfills a requirement by selecting the appropriate available inputs. /// Returns the selected inputs and an optional new requirement. - pub(crate) fn fulfill_requirement(&mut self, requirement: &Requirement) -> Result, Error> { + pub(crate) fn fulfill_requirement(&mut self, requirement: &Requirement) -> Result<(), TransactionBuilderError> { log::debug!("Fulfilling requirement {requirement:?}"); match requirement { @@ -161,19 +164,26 @@ impl InputSelection { } /// Gets requirements from burn. - pub(crate) fn burn_requirements(&mut self) -> Result<(), Error> { + pub(crate) fn burn_requirements(&mut self) -> Result<(), TransactionBuilderError> { if let Some(burn) = self.burn.as_ref() { + if burn.mana() && self.initial_mana_excess()? > 0 { + self.transaction_capabilities + .add_capability(TransactionCapabilityFlag::BurnMana); + } + for account_id in &burn.accounts { if self .non_remainder_outputs() .any(|output| is_account_with_id_non_null(output, account_id)) { - return Err(Error::BurnAndTransition(ChainId::from(*account_id))); + return Err(TransactionBuilderError::BurnAndTransition(ChainId::from(*account_id))); } let requirement = Requirement::Account(*account_id); log::debug!("Adding {requirement:?} from burn"); self.requirements.push(requirement); + self.transaction_capabilities + .add_capability(TransactionCapabilityFlag::DestroyAccountOutputs); } for foundry_id in &burn.foundries { @@ -181,12 +191,14 @@ impl InputSelection { .non_remainder_outputs() .any(|output| is_foundry_with_id(output, foundry_id)) { - return Err(Error::BurnAndTransition(ChainId::from(*foundry_id))); + return Err(TransactionBuilderError::BurnAndTransition(ChainId::from(*foundry_id))); } let requirement = Requirement::Foundry(*foundry_id); log::debug!("Adding {requirement:?} from burn"); self.requirements.push(requirement); + self.transaction_capabilities + .add_capability(TransactionCapabilityFlag::DestroyFoundryOutputs); } for nft_id in &burn.nfts { @@ -194,12 +206,14 @@ impl InputSelection { .non_remainder_outputs() .any(|output| is_nft_with_id_non_null(output, nft_id)) { - return Err(Error::BurnAndTransition(ChainId::from(*nft_id))); + return Err(TransactionBuilderError::BurnAndTransition(ChainId::from(*nft_id))); } let requirement = Requirement::Nft(*nft_id); log::debug!("Adding {requirement:?} from burn"); self.requirements.push(requirement); + self.transaction_capabilities + .add_capability(TransactionCapabilityFlag::DestroyNftOutputs); } for delegation_id in &burn.delegations { @@ -207,7 +221,9 @@ impl InputSelection { .non_remainder_outputs() .any(|output| is_delegation_with_id_non_null(output, delegation_id)) { - return Err(Error::BurnAndTransition(ChainId::from(*delegation_id))); + return Err(TransactionBuilderError::BurnAndTransition(ChainId::from( + *delegation_id, + ))); } let requirement = Requirement::Delegation(*delegation_id); @@ -219,3 +235,30 @@ impl InputSelection { Ok(()) } } + +/// A mapping of prioritized inputs. +/// This allows us to avoid sorting all available inputs every loop, and instead we iterate once and sort +/// only the smaller index vectors as needed. +#[derive(Debug)] +struct PriorityMap

(BTreeMap>); + +impl PriorityMap

+where + for<'a> Option

: From<&'a InputSigningData>, +{ + fn generate(available_inputs: &mut Vec) -> Self { + let inputs = core::mem::take(available_inputs); + Self(inputs.into_iter().fold(BTreeMap::new(), |mut map, i| { + if let Some(priority) = Option::

::from(&i) { + map.entry(priority).or_default().push(i); + } else { + available_inputs.push(i); + } + map + })) + } + + fn into_inputs(self) -> impl Iterator { + self.0.into_values().flatten() + } +} diff --git a/sdk/src/client/api/block_builder/transaction_builder/requirement/native_tokens.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/native_tokens.rs new file mode 100644 index 0000000000..5595928a10 --- /dev/null +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/native_tokens.rs @@ -0,0 +1,196 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::collections::BTreeMap; +use std::{cmp::Ordering, collections::HashSet}; + +use primitive_types::U256; + +use super::{TransactionBuilder, TransactionBuilderError}; +use crate::{ + client::api::transaction_builder::Requirement, + types::block::{ + output::{Output, TokenId, TokenScheme}, + payload::signed_transaction::TransactionCapabilityFlag, + }, +}; + +impl TransactionBuilder { + pub(crate) fn fulfill_native_tokens_requirement(&mut self) -> Result<(), TransactionBuilderError> { + let (input_nts, output_nts) = self.get_input_output_native_tokens(); + let diffs = get_native_tokens_diff(output_nts, input_nts); + if self.burn.as_ref().map_or(false, |burn| !burn.native_tokens.is_empty()) { + self.transaction_capabilities + .add_capability(TransactionCapabilityFlag::BurnNativeTokens); + } + if diffs.is_empty() { + log::debug!("Native tokens requirement already fulfilled"); + + return Ok(()); + } + + if !self.allow_additional_input_selection { + return Err(TransactionBuilderError::AdditionalInputsRequired( + Requirement::NativeTokens, + )); + } + + log::debug!("Fulfilling native tokens requirement"); + + let mut newly_selected_inputs = Vec::new(); + let mut newly_selected_ids = HashSet::new(); + + for (&token_id, &amount) in diffs.iter() { + let mut input_amount = U256::zero(); + // TODO sort ? + let inputs = self.available_inputs.iter().filter(|input| { + input + .output + .native_token() + .is_some_and(|native_token| native_token.token_id() == &token_id) + }); + + for input in inputs { + input_amount += input + .output + .native_token() + // PANIC: safe to unwrap as the filter guarantees inputs containing this native token. + .unwrap() + .amount(); + + if newly_selected_ids.insert(*input.output_id()) { + newly_selected_inputs.push(input.clone()); + } + + if input_amount >= amount { + break; + } + } + + if input_amount < amount { + return Err(TransactionBuilderError::InsufficientNativeTokenAmount { + token_id, + found: input_amount, + required: amount, + }); + } + } + + log::debug!("Outputs {newly_selected_ids:?} selected to fulfill the native tokens requirement"); + + self.available_inputs + .retain(|input| !newly_selected_ids.contains(input.output_id())); + + for input in newly_selected_inputs { + self.select_input(input)?; + } + + Ok(()) + } + + pub(crate) fn get_input_output_native_tokens(&self) -> (BTreeMap, BTreeMap) { + let mut input_native_tokens = self + .selected_inputs + .iter() + .filter_map(|i| i.output.native_token().map(|t| (*t.token_id(), t.amount()))) + .fold(BTreeMap::new(), |mut nts, (token_id, amount)| { + *nts.entry(token_id).or_default() += amount; + nts + }); + let mut output_native_tokens = self + .non_remainder_outputs() + .filter_map(|output| output.native_token().map(|t| (*t.token_id(), t.amount()))) + .fold(BTreeMap::new(), |mut nts, (token_id, amount)| { + *nts.entry(token_id).or_default() += amount; + nts + }); + let (minted_native_tokens, melted_native_tokens) = self.get_minted_and_melted_native_tokens(); + + minted_native_tokens + .into_iter() + .fold(&mut input_native_tokens, |nts, (token_id, amount)| { + *nts.entry(token_id).or_default() += amount; + nts + }); + melted_native_tokens + .into_iter() + .fold(&mut output_native_tokens, |nts, (token_id, amount)| { + *nts.entry(token_id).or_default() += amount; + nts + }); + + if let Some(burn) = self.burn.as_ref() { + burn.native_tokens + .iter() + .fold(&mut output_native_tokens, |nts, (token_id, amount)| { + *nts.entry(*token_id).or_default() += *amount; + nts + }); + } + (input_native_tokens, output_native_tokens) + } + + pub(crate) fn get_minted_and_melted_native_tokens(&self) -> (BTreeMap, BTreeMap) { + let mut minted_native_tokens = BTreeMap::new(); + let mut melted_native_tokens = BTreeMap::new(); + + for output in self.non_remainder_outputs() { + if let Output::Foundry(output_foundry) = output { + let TokenScheme::Simple(output_foundry_simple_ts) = output_foundry.token_scheme(); + let mut initial_creation = true; + + for input in &self.selected_inputs { + if let Output::Foundry(input_foundry) = &input.output { + let token_id = output_foundry.token_id(); + + if output_foundry.id() == input_foundry.id() { + initial_creation = false; + let TokenScheme::Simple(input_foundry_simple_ts) = input_foundry.token_scheme(); + + match output_foundry_simple_ts + .circulating_supply() + .cmp(&input_foundry_simple_ts.circulating_supply()) + { + Ordering::Greater => { + let minted_native_token_amount = output_foundry_simple_ts.circulating_supply() + - input_foundry_simple_ts.circulating_supply(); + + *minted_native_tokens.entry(token_id).or_default() += minted_native_token_amount; + } + Ordering::Less => { + let melted_native_token_amount = input_foundry_simple_ts.circulating_supply() + - output_foundry_simple_ts.circulating_supply(); + + *melted_native_tokens.entry(token_id).or_default() += melted_native_token_amount; + } + Ordering::Equal => {} + } + } + } + } + + // If we created the foundry with this transaction, then we need to add the circulating supply as minted + // tokens + if initial_creation { + *minted_native_tokens.entry(output_foundry.token_id()).or_default() += + output_foundry_simple_ts.circulating_supply(); + } + } + } + + (minted_native_tokens, melted_native_tokens) + } +} + +pub(crate) fn get_native_tokens_diff( + first: BTreeMap, + second: BTreeMap, +) -> BTreeMap { + first + .into_iter() + .filter_map(|(id, in_amount)| { + let out_amount = second.get(&id).copied().unwrap_or_default(); + (in_amount > out_amount).then_some((id, in_amount.saturating_sub(out_amount))) + }) + .collect() +} diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/nft.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/nft.rs similarity index 75% rename from sdk/src/client/api/block_builder/input_selection/requirement/nft.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/nft.rs index 0ae00082c3..505b561cfb 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/nft.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/nft.rs @@ -1,11 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use super::{Error, InputSelection, Requirement}; -use crate::{ - client::secret::types::InputSigningData, - types::block::output::{NftId, Output, OutputId}, -}; +use super::{Requirement, TransactionBuilder, TransactionBuilderError}; +use crate::types::block::output::{NftId, Output, OutputId}; /// Checks if an output is an nft with a given nft ID. /// Assumes that the output nft ID can be null and hashes the output ID. @@ -29,9 +26,9 @@ pub(crate) fn is_nft_with_id_non_null(output: &Output, nft_id: &NftId) -> bool { } } -impl InputSelection { +impl TransactionBuilder { /// Fulfills an nft requirement by selecting the appropriate nft from the available inputs. - pub(crate) fn fulfill_nft_requirement(&mut self, nft_id: NftId) -> Result, Error> { + pub(crate) fn fulfill_nft_requirement(&mut self, nft_id: NftId) -> Result<(), TransactionBuilderError> { // Check if the requirement is already fulfilled. if let Some(input) = self .selected_inputs @@ -39,7 +36,13 @@ impl InputSelection { .find(|input| is_nft_with_id(&input.output, &nft_id, input.output_id())) { log::debug!("{nft_id:?} requirement already fulfilled by {:?}", input.output_id()); - return Ok(Vec::new()); + return Ok(()); + } + + if !self.allow_additional_input_selection { + return Err(TransactionBuilderError::AdditionalInputsRequired(Requirement::Nft( + nft_id, + ))); } // Check if the requirement can be fulfilled. @@ -47,12 +50,16 @@ impl InputSelection { .available_inputs .iter() .position(|input| is_nft_with_id(&input.output, &nft_id, input.output_id())) - .ok_or(Error::UnfulfillableRequirement(Requirement::Nft(nft_id)))?; + .ok_or(TransactionBuilderError::UnfulfillableRequirement(Requirement::Nft( + nft_id, + )))?; // Remove the input from the available inputs, swap to make it O(1). let input = self.available_inputs.swap_remove(index); log::debug!("{nft_id:?} requirement fulfilled by {:?}", input.output_id()); - Ok(vec![input]) + self.select_input(input)?; + + Ok(()) } } diff --git a/sdk/src/client/api/block_builder/input_selection/requirement/sender.rs b/sdk/src/client/api/block_builder/transaction_builder/requirement/sender.rs similarity index 58% rename from sdk/src/client/api/block_builder/input_selection/requirement/sender.rs rename to sdk/src/client/api/block_builder/transaction_builder/requirement/sender.rs index e481cf3023..11d951cdef 100644 --- a/sdk/src/client/api/block_builder/input_selection/requirement/sender.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/requirement/sender.rs @@ -1,46 +1,45 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use super::{Error, InputSelection, Requirement}; -use crate::{client::secret::types::InputSigningData, types::block::address::Address}; +use super::{Requirement, TransactionBuilder, TransactionBuilderError}; +use crate::types::block::address::Address; -impl InputSelection { +impl TransactionBuilder { /// Fulfills a sender requirement by selecting an available input that unlocks its address. - pub(crate) fn fulfill_sender_requirement(&mut self, address: &Address) -> Result, Error> { + pub(crate) fn fulfill_sender_requirement(&mut self, address: &Address) -> Result<(), TransactionBuilderError> { match address { Address::Ed25519(_) => { log::debug!("Treating {address:?} sender requirement as an ed25519 requirement"); - match self.fulfill_ed25519_requirement(address) { - Ok(res) => Ok(res), - Err(Error::UnfulfillableRequirement(Requirement::Ed25519(_))) => { - Err(Error::UnfulfillableRequirement(Requirement::Sender(address.clone()))) + self.fulfill_ed25519_requirement(address).map_err(|e| match e { + TransactionBuilderError::UnfulfillableRequirement(Requirement::Ed25519(_)) => { + TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(address.clone())) } - Err(e) => Err(e), - } + e => e, + }) } Address::Account(account_address) => { log::debug!("Treating {address:?} sender requirement as an account requirement"); // A state transition is required to unlock the account address. - match self.fulfill_account_requirement(account_address.into_account_id()) { - Ok(res) => Ok(res), - Err(Error::UnfulfillableRequirement(Requirement::Account(_))) => { - Err(Error::UnfulfillableRequirement(Requirement::Sender(address.clone()))) - } - Err(e) => Err(e), - } + self.fulfill_account_requirement(account_address.into_account_id()) + .map_err(|e| match e { + TransactionBuilderError::UnfulfillableRequirement(Requirement::Account(_)) => { + TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(address.clone())) + } + e => e, + }) } Address::Nft(nft_address) => { log::debug!("Treating {address:?} sender requirement as an nft requirement"); - match self.fulfill_nft_requirement(nft_address.into_nft_id()) { - Ok(res) => Ok(res), - Err(Error::UnfulfillableRequirement(Requirement::Nft(_))) => { - Err(Error::UnfulfillableRequirement(Requirement::Sender(address.clone()))) - } - Err(e) => Err(e), - } + self.fulfill_nft_requirement(nft_address.into_nft_id()) + .map_err(|e| match e { + TransactionBuilderError::UnfulfillableRequirement(Requirement::Nft(_)) => { + TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(address.clone())) + } + e => e, + }) } // TODO https://github.com/iotaledger/iota-sdk/issues/1721 Address::Multi(multi_address) => { @@ -68,9 +67,11 @@ impl InputSelection { } if cumulative_weight < multi_address.threshold() { - Err(Error::UnfulfillableRequirement(Requirement::Sender(address.clone()))) + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender( + address.clone(), + ))) } else { - Ok(Vec::new()) + Ok(()) } } Address::Restricted(restricted_address) => { @@ -78,7 +79,7 @@ impl InputSelection { self.fulfill_sender_requirement(restricted_address.address()) } - _ => Err(Error::UnsupportedAddressType(address.kind())), + _ => Err(TransactionBuilderError::UnsupportedAddressType(address.kind())), } } } diff --git a/sdk/src/client/api/block_builder/input_selection/transition.rs b/sdk/src/client/api/block_builder/transaction_builder/transition.rs similarity index 76% rename from sdk/src/client/api/block_builder/input_selection/transition.rs rename to sdk/src/client/api/block_builder/transaction_builder/transition.rs index 39089dcb48..1999cf5174 100644 --- a/sdk/src/client/api/block_builder/input_selection/transition.rs +++ b/sdk/src/client/api/block_builder/transaction_builder/transition.rs @@ -3,23 +3,26 @@ use super::{ requirement::{account::is_account_with_id_non_null, foundry::is_foundry_with_id, nft::is_nft_with_id_non_null}, - Error, InputSelection, + TransactionBuilder, TransactionBuilderError, }; use crate::{ client::secret::types::InputSigningData, - types::block::output::{ - AccountOutput, AccountOutputBuilder, FoundryOutput, FoundryOutputBuilder, NftOutput, NftOutputBuilder, Output, - OutputId, + types::block::{ + output::{ + AccountOutput, AccountOutputBuilder, FoundryOutput, FoundryOutputBuilder, NftOutput, NftOutputBuilder, + Output, OutputId, + }, + payload::signed_transaction::TransactionCapabilityFlag, }, }; -impl InputSelection { +impl TransactionBuilder { /// Transitions an account input by creating a new account output if required. fn transition_account_input( &mut self, input: &AccountOutput, output_id: &OutputId, - ) -> Result, Error> { + ) -> Result, TransactionBuilderError> { let account_id = input.account_id_non_null(output_id); // Do not create an account output if the account input is to be burned. @@ -55,16 +58,22 @@ impl InputSelection { let features = input.features().iter().filter(|feature| !feature.is_sender()).cloned(); let mut builder = AccountOutputBuilder::from(input) + .with_amount_or_minimum(input.amount(), self.protocol_parameters.storage_score_parameters()) .with_account_id(account_id) .with_foundry_counter(u32::max(highest_foundry_serial_number, input.foundry_counter())) .with_features(features); if input.is_block_issuer() { - builder = builder.with_mana(Output::from(input.clone()).available_mana( - &self.protocol_parameters, - output_id.transaction_id().slot_index(), - self.creation_slot, - )?) + if !self.burn.as_ref().map_or(false, |b| b.generated_mana()) { + builder = builder.with_mana(input.available_mana( + &self.protocol_parameters, + output_id.transaction_id().slot_index(), + self.creation_slot, + )?) + } else { + self.transaction_capabilities + .add_capability(TransactionCapabilityFlag::BurnMana); + } } let output = builder.finish_output()?; @@ -75,7 +84,11 @@ impl InputSelection { } /// Transitions an nft input by creating a new nft output if required. - fn transition_nft_input(&mut self, input: &NftOutput, output_id: &OutputId) -> Result, Error> { + fn transition_nft_input( + &mut self, + input: &NftOutput, + output_id: &OutputId, + ) -> Result, TransactionBuilderError> { let nft_id = input.nft_id_non_null(output_id); // Do not create an nft output if the nft input is to be burned. @@ -102,6 +115,7 @@ impl InputSelection { let features = input.features().iter().filter(|feature| !feature.is_sender()).cloned(); let output = NftOutputBuilder::from(input) + .with_amount_or_minimum(input.amount(), self.protocol_parameters.storage_score_parameters()) .with_nft_id(nft_id) .with_features(features) .finish_output()?; @@ -116,7 +130,7 @@ impl InputSelection { &mut self, input: &FoundryOutput, output_id: &OutputId, - ) -> Result, Error> { + ) -> Result, TransactionBuilderError> { let foundry_id = input.id(); // Do not create a foundry output if the foundry input is to be burned. @@ -139,7 +153,9 @@ impl InputSelection { return Ok(None); } - let output = FoundryOutputBuilder::from(input).finish_output()?; + let output = FoundryOutputBuilder::from(input) + .with_amount_or_minimum(input.amount(), self.protocol_parameters.storage_score_parameters()) + .finish_output()?; log::debug!("Automatic transition of {output_id:?}/{foundry_id:?}"); @@ -148,7 +164,10 @@ impl InputSelection { /// Transitions an input by creating a new output if required. /// If no `account_transition` is provided, assumes a state transition. - pub(crate) fn transition_input(&mut self, input: &InputSigningData) -> Result, Error> { + pub(crate) fn transition_input( + &mut self, + input: &InputSigningData, + ) -> Result, TransactionBuilderError> { match &input.output { Output::Account(account_input) => self.transition_account_input(account_input, input.output_id()), Output::Foundry(foundry_input) => self.transition_foundry_input(foundry_input, input.output_id()), diff --git a/sdk/src/client/api/high_level.rs b/sdk/src/client/api/high_level.rs index da1b275537..dec4a84ccd 100644 --- a/sdk/src/client/api/high_level.rs +++ b/sdk/src/client/api/high_level.rs @@ -7,33 +7,35 @@ use futures::{StreamExt, TryStreamExt}; use crate::{ client::{ - api::input_selection::Error as InputSelectionError, - constants::FIVE_MINUTES_IN_NANOSECONDS, - error::{Error, Result}, - node_api::indexer::query_parameters::BasicOutputQueryParameters, - unix_timestamp_now, Client, + api::transaction_builder::TransactionBuilderError, constants::FIVE_MINUTES_IN_NANOSECONDS, + node_api::indexer::query_parameters::BasicOutputQueryParameters, unix_timestamp_now, Client, ClientError, }, - types::block::{ - address::Bech32Address, - core::{BasicBlockBody, Block, BlockBody}, - input::{Input, UtxoInput, INPUT_COUNT_MAX}, - output::OutputWithMetadata, - payload::{signed_transaction::TransactionId, Payload}, - slot::SlotIndex, - BlockId, + types::{ + api::core::OutputWithMetadataResponse, + block::{ + address::Bech32Address, + core::{BasicBlockBody, Block, BlockBody}, + input::{Input, UtxoInput, INPUT_COUNT_MAX}, + payload::{signed_transaction::TransactionId, Payload}, + slot::SlotIndex, + BlockId, + }, }, }; impl Client { /// Get the inputs of a transaction for the given transaction id. - pub async fn inputs_from_transaction_id(&self, transaction_id: &TransactionId) -> Result> { + pub async fn get_transaction_inputs( + &self, + transaction_id: &TransactionId, + ) -> Result, ClientError> { let block = self.get_included_block(transaction_id).await?; if let BlockBody::Basic(basic_block_body) = block.body() { let inputs = if let Some(Payload::SignedTransaction(t)) = basic_block_body.payload() { t.transaction().inputs() } else { - return Err(Error::MissingTransactionPayload); + return Err(ClientError::MissingTransactionPayload); }; let input_ids = inputs @@ -45,7 +47,7 @@ impl Client { self.get_outputs_with_metadata(&input_ids).await } else { - Err(Error::UnexpectedBlockBodyKind { + Err(ClientError::UnexpectedBlockBodyKind { expected: BasicBlockBody::KIND, actual: block.body().kind(), }) @@ -53,7 +55,7 @@ impl Client { } /// Find all blocks by provided block IDs. - pub async fn find_blocks(&self, block_ids: &[BlockId]) -> Result> { + pub async fn find_blocks(&self, block_ids: &[BlockId]) -> Result, ClientError> { // Use a `HashSet` to prevent duplicate block_ids. let block_ids = block_ids.iter().copied().collect::>(); futures::future::try_join_all(block_ids.iter().map(|block_id| self.get_block(block_id))).await @@ -61,7 +63,7 @@ impl Client { /// Function to find inputs from addresses for a provided amount (useful for offline signing), ignoring outputs with /// additional unlock conditions - pub async fn find_inputs(&self, addresses: Vec, amount: u64) -> Result> { + pub async fn find_inputs(&self, addresses: Vec, amount: u64) -> Result, ClientError> { // Get outputs from node and select inputs let available_outputs = futures::stream::iter(addresses) .then(|address| self.basic_output_ids(BasicOutputQueryParameters::only_address_unlock_condition(address))) @@ -81,7 +83,7 @@ impl Client { output_with_meta.output().amount(), )) }) - .collect::>>()?; + .collect::, ClientError>>()?; basic_outputs.sort_by(|l, r| r.1.cmp(&l.1)); let mut total_already_spent = 0; @@ -100,7 +102,7 @@ impl Client { } if total_already_spent < amount { - return Err(InputSelectionError::InsufficientAmount { + return Err(TransactionBuilderError::InsufficientAmount { found: total_already_spent, required: amount, })?; @@ -110,7 +112,7 @@ impl Client { } // Returns the slot index corresponding to the current timestamp. - pub async fn get_slot_index(&self) -> Result { + pub async fn get_slot_index(&self) -> Result { let unix_timestamp = unix_timestamp_now(); let current_time_nanos = unix_timestamp.as_nanos() as u64; @@ -121,7 +123,7 @@ impl Client { if !(tangle_time - FIVE_MINUTES_IN_NANOSECONDS..tangle_time + FIVE_MINUTES_IN_NANOSECONDS) .contains(¤t_time_nanos) { - return Err(Error::TimeNotSynced { + return Err(ClientError::TimeNotSynced { current_time: current_time_nanos, tangle_time, }); diff --git a/sdk/src/client/api/mod.rs b/sdk/src/client/api/mod.rs index 45d2073fea..33f6d16a3e 100644 --- a/sdk/src/client/api/mod.rs +++ b/sdk/src/client/api/mod.rs @@ -7,6 +7,7 @@ mod address; mod block_builder; mod high_level; mod types; +mod wait_for_tx_acceptance; pub use self::{address::*, block_builder::*, types::*}; diff --git a/sdk/src/client/api/types.rs b/sdk/src/client/api/types.rs index 08598b0a44..09db52fa87 100644 --- a/sdk/src/client/api/types.rs +++ b/sdk/src/client/api/types.rs @@ -17,10 +17,9 @@ use crate::{ dto::{SignedTransactionPayloadDto, TransactionDto}, Transaction, }, - SignedTransactionPayload, + PayloadError, SignedTransactionPayload, }, protocol::ProtocolParameters, - Error, }, TryFromDto, }, @@ -68,15 +67,14 @@ impl From<&PreparedTransactionData> for PreparedTransactionDataDto { } impl TryFromDto for PreparedTransactionData { - type Error = Error; + type Error = PayloadError; fn try_from_dto_with_params_inner( dto: PreparedTransactionDataDto, params: Option<&ProtocolParameters>, ) -> Result { Ok(Self { - transaction: Transaction::try_from_dto_with_params_inner(dto.transaction, params) - .map_err(|_| Error::InvalidField("transaction"))?, + transaction: Transaction::try_from_dto_with_params_inner(dto.transaction, params)?, inputs_data: dto.inputs_data, remainders: dto.remainders, mana_rewards: dto.mana_rewards, @@ -128,15 +126,14 @@ impl From<&SignedTransactionData> for SignedTransactionDataDto { } impl TryFromDto for SignedTransactionData { - type Error = Error; + type Error = PayloadError; fn try_from_dto_with_params_inner( dto: SignedTransactionDataDto, params: Option<&ProtocolParameters>, ) -> Result { Ok(Self { - payload: SignedTransactionPayload::try_from_dto_with_params_inner(dto.payload, params) - .map_err(|_| Error::InvalidField("transaction_payload"))?, + payload: SignedTransactionPayload::try_from_dto_with_params_inner(dto.payload, params)?, inputs_data: dto.inputs_data, mana_rewards: dto.mana_rewards, }) diff --git a/sdk/src/client/api/wait_for_tx_acceptance.rs b/sdk/src/client/api/wait_for_tx_acceptance.rs new file mode 100644 index 0000000000..a12c71eb60 --- /dev/null +++ b/sdk/src/client/api/wait_for_tx_acceptance.rs @@ -0,0 +1,47 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::time::Duration; + +use crate::{ + client::{Client, ClientError}, + types::{api::core::TransactionState, block::payload::signed_transaction::TransactionId}, +}; + +pub(crate) const DEFAULT_WAIT_FOR_TX_ACCEPTANCE_INTERVAL: Duration = Duration::from_millis(500); +pub(crate) const DEFAULT_WAIT_FOR_TX_ACCEPTANCE_MAX_ATTEMPTS: u64 = 80; + +impl Client { + /// Checks the transaction state for a provided transaction id until it's accepted. Interval in milliseconds. + pub async fn wait_for_transaction_acceptance( + &self, + transaction_id: &TransactionId, + interval: Option, + max_attempts: Option, + ) -> Result<(), ClientError> { + log::debug!("[wait_for_transaction_acceptance]"); + + let duration = interval + .map(std::time::Duration::from_millis) + .unwrap_or(DEFAULT_WAIT_FOR_TX_ACCEPTANCE_INTERVAL); + + for _ in 0..max_attempts.unwrap_or(DEFAULT_WAIT_FOR_TX_ACCEPTANCE_MAX_ATTEMPTS) { + let transaction_metadata = self.get_transaction_metadata(transaction_id).await?; + + match transaction_metadata.transaction_state { + TransactionState::Accepted | TransactionState::Confirmed | TransactionState::Finalized => { + return Ok(()); + } + TransactionState::Failed => return Err(ClientError::TransactionAcceptance(transaction_id.to_string())), + TransactionState::Pending => {} // Just need to wait longer + }; + + #[cfg(target_family = "wasm")] + gloo_timers::future::TimeoutFuture::new(duration.as_millis() as u32).await; + #[cfg(not(target_family = "wasm"))] + tokio::time::sleep(duration).await; + } + + Err(ClientError::TransactionAcceptance(transaction_id.to_string())) + } +} diff --git a/sdk/src/client/builder.rs b/sdk/src/client/builder.rs index c18998754c..f9d810549e 100644 --- a/sdk/src/client/builder.rs +++ b/sdk/src/client/builder.rs @@ -12,12 +12,11 @@ use crate::client::node_api::mqtt::{BrokerOptions, MqttEvent}; use crate::{ client::{ constants::DEFAULT_API_TIMEOUT, - error::Result, node_manager::{ builder::validate_url, node::{Node, NodeAuth}, }, - Client, + Client, ClientError, }, types::block::protocol::ProtocolParameters, }; @@ -33,11 +32,11 @@ pub struct ClientBuilder { /// Options for the MQTT broker #[cfg(feature = "mqtt")] #[cfg_attr(docsrs, doc(cfg(feature = "mqtt")))] - #[serde(flatten)] + #[serde(default)] pub broker_options: BrokerOptions, - /// Data related to the used network - #[serde(flatten, default)] - pub network_info: NetworkInfo, + /// Protocol parameters + #[serde(default, skip_serializing_if = "Option::is_none")] + pub protocol_parameters: Option, /// Timeout for API requests #[serde(default = "default_api_timeout")] pub api_timeout: Duration, @@ -62,7 +61,7 @@ impl Default for ClientBuilder { node_manager_builder: crate::client::node_manager::NodeManager::builder(), #[cfg(feature = "mqtt")] broker_options: Default::default(), - network_info: NetworkInfo::default(), + protocol_parameters: None, api_timeout: DEFAULT_API_TIMEOUT, #[cfg(not(target_family = "wasm"))] max_parallel_api_requests: super::constants::MAX_PARALLEL_API_REQUESTS, @@ -78,7 +77,7 @@ impl ClientBuilder { /// Set the fields from a client JSON config #[allow(unused_assignments)] - pub fn from_json(mut self, client_config: &str) -> Result { + pub fn from_json(mut self, client_config: &str) -> Result { self = serde_json::from_str::(client_config)?; // validate URLs for node_dto in &self.node_manager_builder.primary_nodes { @@ -93,31 +92,31 @@ impl ClientBuilder { } /// Adds an IOTA node by its URL. - pub fn with_node(mut self, url: &str) -> Result { + pub fn with_node(mut self, url: &str) -> Result { self.node_manager_builder = self.node_manager_builder.with_node(url)?; Ok(self) } // Adds a node as primary node. - pub fn with_primary_node(mut self, node: Node) -> Result { + pub fn with_primary_node(mut self, node: Node) -> Result { self.node_manager_builder = self.node_manager_builder.with_primary_node(node)?; Ok(self) } /// Adds a list of IOTA nodes by their URLs to the primary nodes list. - pub fn with_primary_nodes(mut self, urls: &[&str]) -> Result { + pub fn with_primary_nodes(mut self, urls: &[&str]) -> Result { self.node_manager_builder = self.node_manager_builder.with_primary_nodes(urls)?; Ok(self) } /// Adds an IOTA node by its URL with optional jwt and or basic authentication - pub fn with_node_auth(mut self, url: &str, auth: impl Into>) -> Result { + pub fn with_node_auth(mut self, url: &str, auth: impl Into>) -> Result { self.node_manager_builder = self.node_manager_builder.with_node_auth(url, auth)?; Ok(self) } /// Adds a list of IOTA nodes by their URLs. - pub fn with_nodes(mut self, urls: &[&str]) -> Result { + pub fn with_nodes(mut self, urls: &[&str]) -> Result { self.node_manager_builder = self.node_manager_builder.with_nodes(urls)?; Ok(self) } @@ -182,14 +181,20 @@ impl ClientBuilder { self } + /// Set the protocol parameters. + pub fn with_protocol_parameters(mut self, protocol_parameters: ProtocolParameters) -> Self { + self.protocol_parameters.replace(protocol_parameters); + self + } + /// Build the Client instance. #[cfg(not(target_family = "wasm"))] - pub async fn finish(self) -> Result { + pub async fn finish(self) -> Result { use tokio::sync::RwLock; let node_sync_interval = self.node_manager_builder.node_sync_interval; let ignore_node_health = self.node_manager_builder.ignore_node_health; - let nodes = self + let nodes: HashSet = self .node_manager_builder .primary_nodes .iter() @@ -201,8 +206,9 @@ impl ClientBuilder { let (mqtt_event_tx, mqtt_event_rx) = tokio::sync::watch::channel(MqttEvent::Connected); let client_inner = Arc::new(ClientInner { - node_manager: RwLock::new(self.node_manager_builder.build(HashSet::new())), - network_info: RwLock::new(self.network_info), + // Initially assume all nodes are healthy, so `fetch_network_info()` works. `sync_nodes()` will afterwards + // update the healthy nodes. + node_manager: RwLock::new(self.node_manager_builder.build(nodes.clone())), api_timeout: RwLock::new(self.api_timeout), #[cfg(feature = "mqtt")] mqtt: super::MqttInner { @@ -215,46 +221,66 @@ impl ClientBuilder { request_pool: crate::client::request_pool::RequestPool::new(self.max_parallel_api_requests), }); - client_inner.sync_nodes(&nodes, ignore_node_health).await?; - let client_clone = client_inner.clone(); + let network_info = match self.protocol_parameters { + Some(protocol_parameters) => NetworkInfo { + protocol_parameters, + tangle_time: None, + }, + None => client_inner.fetch_network_info().await?, + }; + + let client = Client { + inner: client_inner, + network_info: Arc::new(RwLock::new(network_info)), + _sync_handle: Arc::new(RwLock::new(super::SyncHandle(None))), + }; + + client.sync_nodes(&nodes, ignore_node_health).await?; + let client_clone = client.clone(); let sync_handle = tokio::spawn(async move { client_clone .start_sync_process(nodes, node_sync_interval, ignore_node_health) .await }); - - let client = Client { - inner: client_inner, - _sync_handle: Arc::new(RwLock::new(super::SyncHandle(Some(sync_handle)))), - }; + *client._sync_handle.write().await = super::SyncHandle(Some(sync_handle)); Ok(client) } /// Build the Client instance. #[cfg(target_family = "wasm")] - pub async fn finish(self) -> Result { + pub async fn finish(self) -> Result { use tokio::sync::RwLock; #[cfg(feature = "mqtt")] let (mqtt_event_tx, mqtt_event_rx) = tokio::sync::watch::channel(MqttEvent::Connected); + let client_inner = ClientInner { + node_manager: RwLock::new(self.node_manager_builder.build(HashSet::new())), + api_timeout: RwLock::new(self.api_timeout), + #[cfg(feature = "mqtt")] + mqtt: super::MqttInner { + client: Default::default(), + topic_handlers: Default::default(), + broker_options: RwLock::new(self.broker_options), + sender: RwLock::new(mqtt_event_tx), + receiver: RwLock::new(mqtt_event_rx), + }, + last_sync: tokio::sync::Mutex::new(None), + }; + + let network_info = match self.protocol_parameters { + Some(protocol_parameters) => NetworkInfo { + protocol_parameters, + tangle_time: None, + }, + None => client_inner.fetch_network_info().await?, + }; + let client = Client { - inner: Arc::new(ClientInner { - node_manager: RwLock::new(self.node_manager_builder.build(HashSet::new())), - network_info: RwLock::new(self.network_info), - api_timeout: RwLock::new(self.api_timeout), - #[cfg(feature = "mqtt")] - mqtt: super::MqttInner { - client: Default::default(), - topic_handlers: Default::default(), - broker_options: RwLock::new(self.broker_options), - sender: RwLock::new(mqtt_event_tx), - receiver: RwLock::new(mqtt_event_rx), - }, - last_sync: tokio::sync::Mutex::new(None), - }), + inner: Arc::new(client_inner), + network_info: Arc::new(RwLock::new(network_info)), }; Ok(client) @@ -265,7 +291,7 @@ impl ClientBuilder { node_manager_builder: NodeManagerBuilder::from(&*client.node_manager.read().await), #[cfg(feature = "mqtt")] broker_options: *client.mqtt.broker_options.read().await, - network_info: client.network_info.read().await.clone(), + protocol_parameters: Some(client.network_info.read().await.protocol_parameters.clone()), api_timeout: client.get_timeout().await, #[cfg(not(target_family = "wasm"))] max_parallel_api_requests: client.request_pool.size().await, @@ -274,12 +300,10 @@ impl ClientBuilder { } /// Struct containing network related information -// TODO do we really want a default? -#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NetworkInfo { /// Protocol parameters. - #[serde(default)] pub protocol_parameters: ProtocolParameters, /// The current tangle time. #[serde(skip)] diff --git a/sdk/src/client/core.rs b/sdk/src/client/core.rs index e1e623e7d8..e44ce64229 100644 --- a/sdk/src/client/core.rs +++ b/sdk/src/client/core.rs @@ -16,13 +16,12 @@ use { #[cfg(not(target_family = "wasm"))] use super::request_pool::RequestPool; #[cfg(target_family = "wasm")] -use crate::{client::constants::CACHE_NETWORK_INFO_TIMEOUT_IN_SECONDS, types::block::PROTOCOL_VERSION}; +use crate::client::constants::CACHE_NETWORK_INFO_TIMEOUT_IN_SECONDS; use crate::{ client::{ builder::{ClientBuilder, NetworkInfo}, - error::Result, node_manager::NodeManager, - Error, + ClientError, }, types::block::{address::Hrp, output::StorageScoreParameters, protocol::ProtocolParameters}, }; @@ -31,6 +30,7 @@ use crate::{ #[derive(Clone)] pub struct Client { pub(crate) inner: Arc, + pub(crate) network_info: Arc>, #[cfg(not(target_family = "wasm"))] pub(crate) _sync_handle: Arc>, } @@ -46,7 +46,6 @@ impl core::ops::Deref for Client { pub struct ClientInner { /// Node manager pub(crate) node_manager: RwLock, - pub(crate) network_info: RwLock, /// HTTP request timeout. pub(crate) api_timeout: RwLock, #[cfg(feature = "mqtt")] @@ -57,12 +56,13 @@ pub struct ClientInner { pub(crate) request_pool: RequestPool, } +#[cfg(not(target_family = "wasm"))] #[derive(Default)] pub(crate) struct SyncHandle(pub(crate) Option>); +#[cfg(not(target_family = "wasm"))] impl Drop for SyncHandle { fn drop(&mut self) { - #[cfg(not(target_family = "wasm"))] if let Some(sync_handle) = self.0.take() { sync_handle.abort(); } @@ -97,12 +97,10 @@ impl Client { pub fn builder() -> ClientBuilder { ClientBuilder::new() } -} -impl ClientInner { /// Gets the network related information such as network_id and if it's the default one, sync it first and set the /// NetworkInfo. - pub async fn get_network_info(&self) -> Result { + pub async fn get_network_info(&self) -> Result { // For WASM we don't have the node syncing process, which updates the network_info every 60 seconds, so we // request the node info every time, so we don't create invalid transactions/blocks. #[cfg(target_family = "wasm")] @@ -113,47 +111,45 @@ impl ClientInner { return Ok(self.network_info.read().await.clone()); } } - let info = self.get_info().await?.node_info; - let mut client_network_info = self.network_info.write().await; - client_network_info.protocol_parameters = info - .protocol_parameters_by_version(PROTOCOL_VERSION) - .expect("missing v3 protocol parameters") - .parameters - .clone(); + let network_info = self.fetch_network_info().await?; + *self.network_info.write().await = network_info.clone(); *self.last_sync.lock().await = Some(current_time + CACHE_NETWORK_INFO_TIMEOUT_IN_SECONDS); + + Ok(network_info) } + #[cfg(not(target_family = "wasm"))] Ok(self.network_info.read().await.clone()) } /// Gets the protocol parameters of the node we're connecting to. - pub async fn get_protocol_parameters(&self) -> Result { + pub async fn get_protocol_parameters(&self) -> Result { Ok(self.get_network_info().await?.protocol_parameters) } /// Gets the protocol version of the node we're connecting to. - pub async fn get_protocol_version(&self) -> Result { + pub async fn get_protocol_version(&self) -> Result { Ok(self.get_network_info().await?.protocol_parameters.version()) } /// Gets the network name of the node we're connecting to. - pub async fn get_network_name(&self) -> Result { + pub async fn get_network_name(&self) -> Result { Ok(self.get_network_info().await?.protocol_parameters.network_name().into()) } /// Gets the network id of the node we're connecting to. - pub async fn get_network_id(&self) -> Result { + pub async fn get_network_id(&self) -> Result { Ok(self.get_network_info().await?.protocol_parameters.network_id()) } /// Gets the bech32 HRP of the node we're connecting to. - pub async fn get_bech32_hrp(&self) -> Result { + pub async fn get_bech32_hrp(&self) -> Result { Ok(self.get_network_info().await?.protocol_parameters.bech32_hrp()) } /// Gets the storage score parameters of the node we're connecting to. - pub async fn get_storage_score_parameters(&self) -> Result { + pub async fn get_storage_score_parameters(&self) -> Result { Ok(self .get_network_info() .await? @@ -162,25 +158,42 @@ impl ClientInner { } /// Gets the token supply of the node we're connecting to. - pub async fn get_token_supply(&self) -> Result { + pub async fn get_token_supply(&self) -> Result { Ok(self.get_network_info().await?.protocol_parameters.token_supply()) } - pub(crate) async fn get_timeout(&self) -> Duration { - *self.api_timeout.read().await - } - /// Validates if a bech32 HRP matches the one from the connected network. - pub async fn bech32_hrp_matches(&self, bech32_hrp: &Hrp) -> Result<()> { + pub async fn bech32_hrp_matches(&self, bech32_hrp: &Hrp) -> Result<(), ClientError> { let expected = self.get_bech32_hrp().await?; if bech32_hrp != &expected { - return Err(Error::Bech32HrpMismatch { + return Err(ClientError::Bech32HrpMismatch { provided: bech32_hrp.to_string(), expected: expected.to_string(), }); }; Ok(()) } +} + +impl ClientInner { + pub(crate) async fn fetch_network_info(&self) -> Result { + let info = self.get_node_info().await?.info; + let protocol_parameters = info + .protocol_parameters_by_version(crate::types::block::PROTOCOL_VERSION) + .expect("missing v3 protocol parameters") + .parameters + .clone(); + let network_info = NetworkInfo { + protocol_parameters, + tangle_time: info.status.relative_accepted_tangle_time, + }; + + Ok(network_info) + } + + pub(crate) async fn get_timeout(&self) -> Duration { + *self.api_timeout.read().await + } /// Resize the client's request pool #[cfg(not(target_family = "wasm"))] diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs index 9db4610d5c..12b202d576 100644 --- a/sdk/src/client/error.rs +++ b/sdk/src/client/error.rs @@ -9,17 +9,30 @@ use packable::error::UnexpectedEOF; use serde::{ser::Serializer, Serialize}; use crate::{ - client::api::input_selection::Error as InputSelectionError, types::block::semantic::TransactionFailureReason, + client::api::transaction_builder::TransactionBuilderError, + types::block::{ + address::AddressError, + context_input::ContextInputError, + input::InputError, + mana::ManaError, + output::{ + feature::FeatureError, unlock_condition::UnlockConditionError, NativeTokenError, OutputError, + TokenSchemeError, + }, + payload::PayloadError, + semantic::TransactionFailureReason, + signature::SignatureError, + unlock::UnlockError, + BlockError, + }, + utils::ConversionError, }; -/// Type alias of `Result` in iota-client -pub type Result = std::result::Result; - /// Error type of the iota client crate. #[derive(Debug, thiserror::Error, strum::AsRefStr)] #[strum(serialize_all = "camelCase")] #[non_exhaustive] -pub enum Error { +pub enum ClientError { /// Invalid bech32 HRP, should match the one from the used network #[error("invalid bech32 hrp for the connected network: {provided}, expected: {expected}")] Bech32HrpMismatch { @@ -33,7 +46,10 @@ pub enum Error { Blake2b256(&'static str), /// Block types error #[error("{0}")] - Block(#[from] crate::types::block::Error), + Block(#[from] BlockError), + /// Address types error + #[error("{0}")] + Address(#[from] AddressError), /// Crypto.rs error #[error("{0}")] Crypto(#[from] crypto::Error), @@ -130,10 +146,10 @@ pub enum Error { }, /// The semantic validation of a transaction failed. #[error("the semantic validation of a transaction failed with conflict reason: {} - {0:?}", *.0 as u8)] - TransactionSemantic(TransactionFailureReason), + TransactionSemantic(#[from] TransactionFailureReason), /// Unpack error #[error("{0}")] - Unpack(#[from] packable::error::UnpackError), + Unpack(#[from] packable::error::UnpackError), /// URL auth error #[error("can't set {0} to URL")] UrlAuth(&'static str), @@ -143,9 +159,9 @@ pub enum Error { /// URL validation error #[error("{0}")] UrlValidation(String), - /// Input selection error. + /// Transaction builder error. #[error("{0}")] - InputSelection(#[from] InputSelectionError), + TransactionBuilder(#[from] TransactionBuilderError), /// Missing BIP32 chain to sign with. #[error("missing BIP32 chain to sign with")] MissingBip32Chain, @@ -169,7 +185,7 @@ pub enum Error { #[cfg(feature = "ledger_nano")] #[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))] #[error("{0}")] - Ledger(#[from] crate::client::secret::ledger_nano::Error), + Ledger(Box), /// MQTT error #[cfg(feature = "mqtt")] @@ -182,10 +198,12 @@ pub enum Error { #[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))] #[error("{0}")] Stronghold(#[from] crate::client::stronghold::Error), + #[error("{0}")] + Convert(#[from] ConversionError), } // Serialize type with Display error -impl Serialize for Error { +impl Serialize for ClientError { fn serialize(&self, serializer: S) -> std::result::Result where S: Serializer, @@ -204,3 +222,24 @@ impl Serialize for Error { .serialize(serializer) } } + +crate::impl_from_error_via!(ClientError via BlockError: + PayloadError, + OutputError, + InputError, + NativeTokenError, + ManaError, + UnlockConditionError, + FeatureError, + TokenSchemeError, + ContextInputError, + UnlockError, + SignatureError, +); + +#[cfg(feature = "ledger_nano")] +impl From for ClientError { + fn from(value: crate::client::secret::ledger_nano::Error) -> Self { + Self::Ledger(value.into()) + } +} diff --git a/sdk/src/client/mod.rs b/sdk/src/client/mod.rs index 297757809d..8b1835c4d5 100644 --- a/sdk/src/client/mod.rs +++ b/sdk/src/client/mod.rs @@ -63,7 +63,6 @@ pub use self::{ builder::{ClientBuilder, NetworkInfo}, core::*, error::*, - node_api::core::routes::NodeInfoWrapper, utils::*, }; diff --git a/sdk/src/client/node_api/core/mod.rs b/sdk/src/client/node_api/core/mod.rs index 9dd7987017..72b6a11161 100644 --- a/sdk/src/client/node_api/core/mod.rs +++ b/sdk/src/client/node_api/core/mod.rs @@ -6,45 +6,64 @@ pub mod routes; use crate::{ - client::{node_api::error::Error as NodeApiError, Client, Error, Result}, + client::{node_api::error::Error as NodeApiError, Client, ClientError}, types::{ - api::core::OutputResponse, - block::output::{OutputId, OutputMetadata, OutputWithMetadata}, + api::core::{OutputResponse, OutputWithMetadataResponse}, + block::output::{OutputId, OutputMetadata}, }, }; impl Client { /// Requests outputs by their output ID in parallel. - pub async fn get_outputs(&self, output_ids: &[OutputId]) -> Result> { + pub async fn get_outputs(&self, output_ids: &[OutputId]) -> Result, ClientError> { futures::future::try_join_all(output_ids.iter().map(|id| self.get_output(id))).await } /// Requests outputs by their output ID in parallel, ignoring outputs not found. /// Useful to get data about spent outputs, that might not be pruned yet. - pub async fn get_outputs_ignore_not_found(&self, output_ids: &[OutputId]) -> Result> { + pub async fn get_outputs_ignore_not_found( + &self, + output_ids: &[OutputId], + ) -> Result, ClientError> { futures::future::join_all(output_ids.iter().map(|id| self.get_output(id))) .await .into_iter() - .filter(|res| !matches!(res, Err(Error::Node(NodeApiError::NotFound(_))))) + .filter(|res| { + !matches!( + res, + Err(ClientError::Node(NodeApiError::NotFound(_))) | Err(ClientError::Node(NodeApiError::ResponseError { .. })) + ) + }) .collect() } /// Requests metadata for outputs by their output ID in parallel. - pub async fn get_outputs_metadata(&self, output_ids: &[OutputId]) -> Result> { + pub async fn get_outputs_metadata(&self, output_ids: &[OutputId]) -> Result, ClientError> { futures::future::try_join_all(output_ids.iter().map(|id| self.get_output_metadata(id))).await } /// Requests metadata for outputs by their output ID in parallel, ignoring outputs not found. - pub async fn get_outputs_metadata_ignore_not_found(&self, output_ids: &[OutputId]) -> Result> { + pub async fn get_outputs_metadata_ignore_not_found( + &self, + output_ids: &[OutputId], + ) -> Result, ClientError> { futures::future::join_all(output_ids.iter().map(|id| self.get_output_metadata(id))) .await .into_iter() - .filter(|res| !matches!(res, Err(Error::Node(NodeApiError::NotFound(_))))) + .filter(|res| { + !matches!( + res, + Err(ClientError::Node(NodeApiError::NotFound(_))) | Err(ClientError::Node(NodeApiError::ResponseError { .. })) + ) + }) .collect() } /// Requests outputs and their metadata by their output ID in parallel. - pub async fn get_outputs_with_metadata(&self, output_ids: &[OutputId]) -> Result> { + pub async fn get_outputs_with_metadata( + &self, + output_ids: &[OutputId], + ) -> Result, ClientError> { futures::future::try_join_all(output_ids.iter().map(|id| self.get_output_with_metadata(id))).await } @@ -53,11 +72,16 @@ impl Client { pub async fn get_outputs_with_metadata_ignore_not_found( &self, output_ids: &[OutputId], - ) -> Result> { + ) -> Result, ClientError> { futures::future::join_all(output_ids.iter().map(|id| self.get_output_with_metadata(id))) .await .into_iter() - .filter(|res| !matches!(res, Err(Error::Node(NodeApiError::NotFound(_))))) + .filter(|res| { + !matches!( + res, + Err(ClientError::Node(NodeApiError::NotFound(_))) | Err(ClientError::Node(NodeApiError::ResponseError { .. })) + ) + }) .collect() } } diff --git a/sdk/src/client/node_api/core/routes.rs b/sdk/src/client/node_api/core/routes.rs index e50617f142..c7b09ce4da 100644 --- a/sdk/src/client/node_api/core/routes.rs +++ b/sdk/src/client/node_api/core/routes.rs @@ -12,18 +12,19 @@ use crate::{ constants::{DEFAULT_API_TIMEOUT, DEFAULT_USER_AGENT}, node_api::query_tuples_to_query_string, node_manager::node::{Node, NodeAuth}, - Client, ClientInner, Result, + Client, ClientError, ClientInner, }, types::{ api::core::{ BlockMetadataResponse, BlockWithMetadataResponse, CommitteeResponse, CongestionResponse, InfoResponse, - IssuanceBlockHeaderResponse, ManaRewardsResponse, OutputResponse, PermanodeInfoResponse, RoutesResponse, - SubmitBlockResponse, TransactionMetadataResponse, UtxoChangesFullResponse, UtxoChangesResponse, - ValidatorResponse, ValidatorsResponse, + IssuanceBlockHeaderResponse, ManaRewardsResponse, NetworkMetricsResponse, OutputResponse, + OutputWithMetadataResponse, PermanodeInfoResponse, RoutesResponse, SubmitBlockResponse, + TransactionMetadataResponse, UtxoChangesFullResponse, UtxoChangesResponse, ValidatorResponse, + ValidatorsResponse, }, block::{ address::ToBech32Ext, - output::{AccountId, OutputId, OutputMetadata, OutputWithMetadata}, + output::{AccountId, OutputId, OutputMetadata}, payload::signed_transaction::TransactionId, slot::{EpochIndex, SlotCommitment, SlotCommitmentId, SlotIndex}, Block, BlockDto, BlockId, @@ -35,13 +36,13 @@ use crate::{ /// Info path is the exact path extension for node APIs to request their info. pub(crate) static INFO_PATH: &str = "api/core/v3/info"; -/// NodeInfo wrapper which contains the node info and the url from the node (useful when multiple nodes are used) -#[derive(Debug, Serialize, Deserialize, PartialEq)] +/// Contains the info and the url from the node (useful when multiple nodes are used) +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NodeInfoWrapper { - /// The returned node info - pub node_info: InfoResponse, - /// The url from the node which returned the node info +pub struct NodeInfoResponse { + /// The returned info + pub info: InfoResponse, + /// The url from the node which returned the info pub url: String, } @@ -50,7 +51,7 @@ impl ClientInner { /// Returns the health of the node. /// GET /health - pub async fn get_health(&self, url: &str) -> Result { + pub async fn get_health(&self, url: &str) -> Result { const PATH: &str = "health"; let mut url = Url::parse(url)?; @@ -76,7 +77,7 @@ impl ClientInner { /// Returns the available API route groups of the node. /// GET /api/routes - pub async fn get_routes(&self) -> Result { + pub async fn get_routes(&self) -> Result { const PATH: &str = "api/routes"; self.get_request(PATH, None, false).await @@ -84,20 +85,32 @@ impl ClientInner { /// Returns general information about the node. /// GET /api/core/v3/info - pub async fn get_info(&self) -> Result { + pub async fn get_node_info(&self) -> Result { self.get_request(INFO_PATH, None, false).await } + /// Returns network metrics. + /// GET /api/core/v3/network/metrics + pub async fn get_network_metrics(&self) -> Result { + const PATH: &str = "api/core/v3/network/metrics"; + + self.get_request(PATH, None, false).await + } +} + +impl Client { + // Accounts routes. + /// Checks if the account is ready to issue a block. /// GET /api/core/v3/accounts/{bech32Address}/congestion pub async fn get_account_congestion( &self, account_id: &AccountId, work_score: impl Into> + Send, - ) -> Result { + ) -> Result { let bech32_address = account_id.to_bech32(self.get_bech32_hrp().await?); let path = &format!("api/core/v3/accounts/{bech32_address}/congestion"); - let query = query_tuples_to_query_string([work_score.into().map(|i| ("workScore", i.to_string()))]); + let query = query_tuples_to_query_string([work_score.into().map(|s| ("workScore", s.to_string()))]); self.get_request(path, query.as_deref(), false).await } @@ -115,65 +128,69 @@ impl ClientInner { &self, output_id: &OutputId, slot_index: impl Into> + Send, - ) -> Result { + ) -> Result { let path = &format!("api/core/v3/rewards/{output_id}"); let query = query_tuples_to_query_string([slot_index.into().map(|i| ("slotIndex", i.to_string()))]); self.get_request(path, query.as_deref(), false).await } - // Committee routes. - - /// Returns the information of committee members at the given epoch index. If epoch index is not provided, the - /// current committee members are returned. - /// GET /api/core/v3/committee/?epochIndex - pub async fn get_committee(&self, epoch_index: impl Into> + Send) -> Result { - const PATH: &str = "api/core/v3/committee"; - let query = query_tuples_to_query_string([epoch_index.into().map(|i| ("epochIndex", i.to_string()))]); - - self.get_request(PATH, query.as_deref(), false).await - } - // Validators routes. - /// Returns information of all registered validators and if they are active. - /// GET JSON to /api/core/v3/validators + /// Returns information of all stakers (registered validators) and if they are active, ordered by their holding + /// stake. + /// GET /api/core/v3/validators pub async fn get_validators( &self, page_size: impl Into> + Send, cursor: impl Into> + Send, - ) -> Result { + ) -> Result { const PATH: &str = "api/core/v3/validators"; let query = query_tuples_to_query_string([ - page_size.into().map(|i| ("pageSize", i.to_string())), - cursor.into().map(|i| ("cursor", i)), + page_size.into().map(|n| ("pageSize", n.to_string())), + cursor.into().map(|c| ("cursor", c)), ]); self.get_request(PATH, query.as_deref(), false).await } - /// Return information about a validator. + /// Return information about a staker (registered validator). /// GET /api/core/v3/validators/{bech32Address} - pub async fn get_validator(&self, account_id: &AccountId) -> Result { + pub async fn get_validator(&self, account_id: &AccountId) -> Result { let bech32_address = account_id.to_bech32(self.get_bech32_hrp().await?); let path = &format!("api/core/v3/validators/{bech32_address}"); self.get_request(path, None, false).await } + // Committee routes. + + /// Returns the information of committee members at the given epoch index. If epoch index is not provided, the + /// current committee members are returned. + /// GET /api/core/v3/committee/?epochIndex + pub async fn get_committee( + &self, + epoch_index: impl Into> + Send, + ) -> Result { + const PATH: &str = "api/core/v3/committee"; + let query = query_tuples_to_query_string([epoch_index.into().map(|i| ("epochIndex", i.to_string()))]); + + self.get_request(PATH, query.as_deref(), false).await + } + // Blocks routes. /// Returns information that is ideal for attaching a block in the network. /// GET /api/core/v3/blocks/issuance - pub async fn get_issuance(&self) -> Result { + pub async fn get_issuance(&self) -> Result { const PATH: &str = "api/core/v3/blocks/issuance"; self.get_request(PATH, None, false).await } /// Returns the BlockId of the submitted block. - /// POST JSON to /api/core/v3/blocks - pub async fn post_block(&self, block: &Block) -> Result { + /// POST /api/core/v3/blocks + pub async fn post_block(&self, block: &Block) -> Result { const PATH: &str = "api/core/v3/blocks"; let block_dto = BlockDto::from(block); @@ -187,7 +204,7 @@ impl ClientInner { /// Returns the BlockId of the submitted block. /// POST /api/core/v3/blocks - pub async fn post_block_raw(&self, block: &Block) -> Result { + pub async fn post_block_raw(&self, block: &Block) -> Result { const PATH: &str = "api/core/v3/blocks"; let response = self @@ -199,7 +216,7 @@ impl ClientInner { /// Finds a block by its ID and returns it as object. /// GET /api/core/v3/blocks/{blockId} - pub async fn get_block(&self, block_id: &BlockId) -> Result { + pub async fn get_block(&self, block_id: &BlockId) -> Result { let path = &format!("api/core/v3/blocks/{block_id}"); let dto = self.get_request::(path, None, false).await?; @@ -212,7 +229,7 @@ impl ClientInner { /// Finds a block by its ID and returns it as raw bytes. /// GET /api/core/v3/blocks/{blockId} - pub async fn get_block_raw(&self, block_id: &BlockId) -> Result> { + pub async fn get_block_raw(&self, block_id: &BlockId) -> Result, ClientError> { let path = &format!("api/core/v3/blocks/{block_id}"); self.get_request_bytes(path, None).await @@ -220,7 +237,7 @@ impl ClientInner { /// Returns the metadata of a block. /// GET /api/core/v3/blocks/{blockId}/metadata - pub async fn get_block_metadata(&self, block_id: &BlockId) -> Result { + pub async fn get_block_metadata(&self, block_id: &BlockId) -> Result { let path = &format!("api/core/v3/blocks/{block_id}/metadata"); self.get_request(path, None, true).await @@ -228,7 +245,7 @@ impl ClientInner { /// Returns a block with its metadata. /// GET /api/core/v3/blocks/{blockId}/full - pub async fn get_block_with_metadata(&self, block_id: &BlockId) -> Result { + pub async fn get_block_with_metadata(&self, block_id: &BlockId) -> Result { let path = &format!("api/core/v3/blocks/{block_id}/full"); self.get_request(path, None, true).await @@ -238,7 +255,7 @@ impl ClientInner { /// Finds an output by its ID and returns it as object. /// GET /api/core/v3/outputs/{outputId} - pub async fn get_output(&self, output_id: &OutputId) -> Result { + pub async fn get_output(&self, output_id: &OutputId) -> Result { let path = &format!("api/core/v3/outputs/{output_id}"); self.get_request(path, None, false).await @@ -246,7 +263,7 @@ impl ClientInner { /// Finds an output by its ID and returns it as raw bytes. /// GET /api/core/v3/outputs/{outputId} - pub async fn get_output_raw(&self, output_id: &OutputId) -> Result> { + pub async fn get_output_raw(&self, output_id: &OutputId) -> Result, ClientError> { let path = &format!("api/core/v3/outputs/{output_id}"); self.get_request_bytes(path, None).await @@ -254,7 +271,7 @@ impl ClientInner { /// Finds output metadata by output ID. /// GET /api/core/v3/outputs/{outputId}/metadata - pub async fn get_output_metadata(&self, output_id: &OutputId) -> Result { + pub async fn get_output_metadata(&self, output_id: &OutputId) -> Result { let path = &format!("api/core/v3/outputs/{output_id}/metadata"); self.get_request(path, None, false).await @@ -262,7 +279,10 @@ impl ClientInner { /// Finds an output with its metadata by output ID. /// GET /api/core/v3/outputs/{outputId}/full - pub async fn get_output_with_metadata(&self, output_id: &OutputId) -> Result { + pub async fn get_output_with_metadata( + &self, + output_id: &OutputId, + ) -> Result { let path = &format!("api/core/v3/outputs/{output_id}/full"); self.get_request(path, None, false).await @@ -270,7 +290,7 @@ impl ClientInner { /// Returns the earliest confirmed block containing the transaction with the given ID. /// GET /api/core/v3/transactions/{transactionId}/included-block - pub async fn get_included_block(&self, transaction_id: &TransactionId) -> Result { + pub async fn get_included_block(&self, transaction_id: &TransactionId) -> Result { let path = &format!("api/core/v3/transactions/{transaction_id}/included-block"); let dto = self.get_request::(path, None, true).await?; @@ -283,7 +303,7 @@ impl ClientInner { /// Returns the earliest confirmed block containing the transaction with the given ID, as raw bytes. /// GET /api/core/v3/transactions/{transactionId}/included-block - pub async fn get_included_block_raw(&self, transaction_id: &TransactionId) -> Result> { + pub async fn get_included_block_raw(&self, transaction_id: &TransactionId) -> Result, ClientError> { let path = &format!("api/core/v3/transactions/{transaction_id}/included-block"); self.get_request_bytes(path, None).await @@ -291,7 +311,10 @@ impl ClientInner { /// Returns the metadata of the earliest block containing the tx that was confirmed. /// GET /api/core/v3/transactions/{transactionId}/included-block/metadata - pub async fn get_included_block_metadata(&self, transaction_id: &TransactionId) -> Result { + pub async fn get_included_block_metadata( + &self, + transaction_id: &TransactionId, + ) -> Result { let path = &format!("api/core/v3/transactions/{transaction_id}/included-block/metadata"); self.get_request(path, None, true).await @@ -302,7 +325,7 @@ impl ClientInner { pub async fn get_transaction_metadata( &self, transaction_id: &TransactionId, - ) -> Result { + ) -> Result { let path = &format!("api/core/v3/transactions/{transaction_id}/metadata"); self.get_request(path, None, true).await @@ -310,80 +333,69 @@ impl ClientInner { // Commitments routes. - // TODO: rename this to `get_commitment` - // https://github.com/iotaledger/iota-sdk/issues/1921 /// Finds a slot commitment by its ID and returns it as object. /// GET /api/core/v3/commitments/{commitmentId} - pub async fn get_slot_commitment_by_id(&self, slot_commitment_id: &SlotCommitmentId) -> Result { - let path = &format!("api/core/v3/commitments/{slot_commitment_id}"); + pub async fn get_commitment(&self, commitment_id: &SlotCommitmentId) -> Result { + let path = &format!("api/core/v3/commitments/{commitment_id}"); self.get_request(path, None, false).await } - // TODO: rename this to `get_commitment_raw` - // https://github.com/iotaledger/iota-sdk/issues/1921 /// Finds a slot commitment by its ID and returns it as raw bytes. /// GET /api/core/v3/commitments/{commitmentId} - pub async fn get_slot_commitment_by_id_raw(&self, slot_commitment_id: &SlotCommitmentId) -> Result> { - let path = &format!("api/core/v3/commitments/{slot_commitment_id}"); + pub async fn get_commitment_raw(&self, commitment_id: &SlotCommitmentId) -> Result, ClientError> { + let path = &format!("api/core/v3/commitments/{commitment_id}"); self.get_request_bytes(path, None).await } - // TODO: rename this to `get_utxo_changes` - // https://github.com/iotaledger/iota-sdk/issues/1921 /// Get all UTXO changes of a given slot by slot commitment ID. /// GET /api/core/v3/commitments/{commitmentId}/utxo-changes - pub async fn get_utxo_changes_by_slot_commitment_id( - &self, - slot_commitment_id: &SlotCommitmentId, - ) -> Result { - let path = &format!("api/core/v3/commitments/{slot_commitment_id}/utxo-changes"); + pub async fn get_utxo_changes(&self, commitment_id: &SlotCommitmentId) -> Result { + let path = &format!("api/core/v3/commitments/{commitment_id}/utxo-changes"); self.get_request(path, None, false).await } - // TODO: rename this to `get_utxo_changes_full` - // https://github.com/iotaledger/iota-sdk/issues/1921 /// Get all full UTXO changes of a given slot by slot commitment ID. /// GET /api/core/v3/commitments/{commitmentId}/utxo-changes/full - pub async fn get_utxo_changes_full_by_slot_commitment_id( + pub async fn get_utxo_changes_full( &self, - slot_commitment_id: &SlotCommitmentId, - ) -> Result { - let path = &format!("api/core/v3/commitments/{slot_commitment_id}/utxo-changes/full"); + commitment_id: &SlotCommitmentId, + ) -> Result { + let path = &format!("api/core/v3/commitments/{commitment_id}/utxo-changes/full"); self.get_request(path, None, false).await } /// Finds a slot commitment by slot index and returns it as object. /// GET /api/core/v3/commitments/by-slot/{slot} - pub async fn get_slot_commitment_by_slot(&self, slot_index: SlotIndex) -> Result { - let path = &format!("api/core/v3/commitments/by-slot/{slot_index}"); + pub async fn get_commitment_by_slot(&self, slot: SlotIndex) -> Result { + let path = &format!("api/core/v3/commitments/by-slot/{slot}"); self.get_request(path, None, false).await } /// Finds a slot commitment by slot index and returns it as raw bytes. /// GET /api/core/v3/commitments/by-slot/{slot} - pub async fn get_slot_commitment_by_slot_raw(&self, slot_index: SlotIndex) -> Result> { - let path = &format!("api/core/v3/commitments/by-slot/{slot_index}"); + pub async fn get_commitment_by_slot_raw(&self, slot: SlotIndex) -> Result, ClientError> { + let path = &format!("api/core/v3/commitments/by-slot/{slot}"); self.get_request_bytes(path, None).await } /// Get all UTXO changes of a given slot by its index. /// GET /api/core/v3/commitments/by-slot/{slot}/utxo-changes - pub async fn get_utxo_changes_by_slot(&self, slot_index: SlotIndex) -> Result { - let path = &format!("api/core/v3/commitments/by-slot/{slot_index}/utxo-changes"); + pub async fn get_utxo_changes_by_slot(&self, slot: SlotIndex) -> Result { + let path = &format!("api/core/v3/commitments/by-slot/{slot}/utxo-changes"); self.get_request(path, None, false).await } /// Get all full UTXO changes of a given slot by its index. /// GET /api/core/v3/commitments/by-slot/{slot}/utxo-changes/full - pub async fn get_utxo_changes_full_by_slot(&self, slot_index: SlotIndex) -> Result { - let path = &format!("api/core/v3/commitments/by-slot/{slot_index}/utxo-changes/full"); + pub async fn get_utxo_changes_full_by_slot(&self, slot: SlotIndex) -> Result { + let path = &format!("api/core/v3/commitments/by-slot/{slot}/utxo-changes/full"); self.get_request(path, None, false).await } @@ -391,44 +403,13 @@ impl ClientInner { impl Client { /// GET /api/core/v3/info endpoint - pub(crate) async fn get_permanode_info(mut node: Node) -> Result { - log::debug!("get_permanode_info"); - if let Some(auth) = &node.auth { - if let Some((name, password)) = &auth.basic_auth_name_pwd { - node.url - .set_username(name) - .map_err(|_| crate::client::Error::UrlAuth("username"))?; - node.url - .set_password(Some(password)) - .map_err(|_| crate::client::Error::UrlAuth("password"))?; - } - } - - if node.url.path().ends_with('/') { - node.url.set_path(&format!("{}{}", node.url.path(), INFO_PATH)); - } else { - node.url.set_path(&format!("{}/{}", node.url.path(), INFO_PATH)); - } - - let resp: PermanodeInfoResponse = - crate::client::node_manager::http_client::HttpClient::new(DEFAULT_USER_AGENT.to_string()) - .get(&node, DEFAULT_API_TIMEOUT) - .await? - .into_json() - .await?; - - Ok(resp) - } - - /// GET /api/core/v3/info endpoint - pub async fn get_node_info(url: &str, auth: Option) -> Result { + pub async fn get_info(url: &str, auth: Option) -> Result { let mut url = crate::client::node_manager::builder::validate_url(Url::parse(url)?)?; if let Some(auth) = &auth { if let Some((name, password)) = &auth.basic_auth_name_pwd { - url.set_username(name) - .map_err(|_| crate::client::Error::UrlAuth("username"))?; + url.set_username(name).map_err(|_| ClientError::UrlAuth("username"))?; url.set_password(Some(password)) - .map_err(|_| crate::client::Error::UrlAuth("password"))?; + .map_err(|_| ClientError::UrlAuth("password"))?; } } @@ -455,4 +436,34 @@ impl Client { Ok(resp) } + + /// GET /api/core/v3/info endpoint + pub(crate) async fn get_permanode_info(mut node: Node) -> Result { + log::debug!("get_permanode_info"); + if let Some(auth) = &node.auth { + if let Some((name, password)) = &auth.basic_auth_name_pwd { + node.url + .set_username(name) + .map_err(|_| ClientError::UrlAuth("username"))?; + node.url + .set_password(Some(password)) + .map_err(|_| ClientError::UrlAuth("password"))?; + } + } + + if node.url.path().ends_with('/') { + node.url.set_path(&format!("{}{}", node.url.path(), INFO_PATH)); + } else { + node.url.set_path(&format!("{}/{}", node.url.path(), INFO_PATH)); + } + + let resp: PermanodeInfoResponse = + crate::client::node_manager::http_client::HttpClient::new(DEFAULT_USER_AGENT.to_string()) + .get(&node, DEFAULT_API_TIMEOUT) + .await? + .into_json() + .await?; + + Ok(resp) + } } diff --git a/sdk/src/client/node_api/error.rs b/sdk/src/client/node_api/error.rs index 15dc9267eb..4dafb34c03 100644 --- a/sdk/src/client/node_api/error.rs +++ b/sdk/src/client/node_api/error.rs @@ -1,9 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -/// Type alias of `Result` in Node errors -pub type Result = std::result::Result; - /// Node errors. #[derive(Debug, thiserror::Error)] #[non_exhaustive] diff --git a/sdk/src/client/node_api/indexer/mod.rs b/sdk/src/client/node_api/indexer/mod.rs index dd7da84207..a1af203b2e 100644 --- a/sdk/src/client/node_api/indexer/mod.rs +++ b/sdk/src/client/node_api/indexer/mod.rs @@ -8,7 +8,7 @@ pub mod routes; use self::query_parameters::QueryParameter; use crate::{ - client::{ClientInner, Result}, + client::{ClientError, ClientInner}, types::api::plugins::indexer::OutputIdsResponse, }; @@ -20,7 +20,7 @@ impl ClientInner { route: &str, mut query_parameters: impl QueryParameter, need_quorum: bool, - ) -> Result { + ) -> Result { let mut merged_output_ids_response = OutputIdsResponse { committed_slot: 0, page_size: 1000, diff --git a/sdk/src/client/node_api/indexer/query_parameters.rs b/sdk/src/client/node_api/indexer/query_parameters.rs index 8027cea872..d9feb8fa92 100644 --- a/sdk/src/client/node_api/indexer/query_parameters.rs +++ b/sdk/src/client/node_api/indexer/query_parameters.rs @@ -16,17 +16,21 @@ pub trait QueryParameter: Serialize + Send + Sync { for (field, v) in value.as_object().unwrap().iter() { if !v.is_null() { - if let Some(v_str) = v.as_str() { + if let Some(v_bool) = v.as_bool() { if !query_string.is_empty() { query_string.push('&'); } - query_string.push_str(&format!("{}={}", field, v_str)); - } - if let Some(v_u64) = v.as_u64() { + query_string.push_str(&format!("{field}={v_bool}")); + } else if let Some(v_str) = v.as_str() { if !query_string.is_empty() { query_string.push('&'); } - query_string.push_str(&format!("{}={}", field, v_u64)); + query_string.push_str(&format!("{field}={v_str}")); + } else if let Some(v_u64) = v.as_u64() { + if !query_string.is_empty() { + query_string.push('&'); + } + query_string.push_str(&format!("{field}={v_u64}")); } } } @@ -322,16 +326,21 @@ mod tests { Bech32Address::try_from_str("atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r") .unwrap(), ) + .created_after(5.into()) + .has_timelock(true) .cursor("".into()); assert_eq!( basic_outputs_query_parameters.to_query_string(), - Some("address=atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r&cursor=".into()) + Some( + "address=atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r&createdAfter=5&cursor=&hasTimelock=true" + .into() + ) ); basic_outputs_query_parameters.replace_cursor("newCursor".into()); assert_eq!( basic_outputs_query_parameters.to_query_string(), - Some("address=atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r&cursor=newCursor".into()) + Some("address=atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r&createdAfter=5&cursor=newCursor&hasTimelock=true".into()) ); } } diff --git a/sdk/src/client/node_api/indexer/routes.rs b/sdk/src/client/node_api/indexer/routes.rs index 1cc529cec1..295c8e81be 100644 --- a/sdk/src/client/node_api/indexer/routes.rs +++ b/sdk/src/client/node_api/indexer/routes.rs @@ -10,7 +10,7 @@ use crate::{ DelegationOutputQueryParameters, FoundryOutputQueryParameters, NftOutputQueryParameters, OutputQueryParameters, }, - ClientInner, Error, Result, + Client, ClientError, }, types::{ api::plugins::indexer::OutputIdsResponse, @@ -21,12 +21,12 @@ use crate::{ }, }; -impl ClientInner { +impl Client { /// Get account, anchor, basic, delegation, nft and foundry outputs filtered by the given parameters. /// GET with query parameter returns all outputIDs that fit these filter criteria. /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs - pub async fn output_ids(&self, query_parameters: OutputQueryParameters) -> Result { + pub async fn output_ids(&self, query_parameters: OutputQueryParameters) -> Result { let route = "api/indexer/v2/outputs"; self.get_output_ids(route, query_parameters, true).await @@ -36,7 +36,10 @@ impl ClientInner { /// GET with query parameter returns all outputIDs that fit these filter criteria. /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs/basic - pub async fn basic_output_ids(&self, query_parameters: BasicOutputQueryParameters) -> Result { + pub async fn basic_output_ids( + &self, + query_parameters: BasicOutputQueryParameters, + ) -> Result { let route = "api/indexer/v2/outputs/basic"; self.get_output_ids(route, query_parameters, true).await @@ -49,7 +52,7 @@ impl ClientInner { pub async fn account_output_ids( &self, query_parameters: AccountOutputQueryParameters, - ) -> Result { + ) -> Result { let route = "api/indexer/v2/outputs/account"; self.get_output_ids(route, query_parameters, true).await @@ -57,7 +60,7 @@ impl ClientInner { /// Get account output by its accountID. /// api/indexer/v2/outputs/account/{bech32Address} - pub async fn account_output_id(&self, account_id: AccountId) -> Result { + pub async fn account_output_id(&self, account_id: AccountId) -> Result { let bech32_address = account_id.to_bech32(self.get_bech32_hrp().await?); let route = format!("api/indexer/v2/outputs/account/{bech32_address}"); @@ -65,14 +68,17 @@ impl ClientInner { .get_output_ids(&route, AccountOutputQueryParameters::new(), true) .await? .first() - .ok_or_else(|| Error::NoOutput(format!("{account_id:?}")))?)) + .ok_or_else(|| ClientError::NoOutput(format!("{account_id:?}")))?)) } /// Get anchor outputs filtered by the given parameters. /// GET with query parameter returns all outputIDs that fit these filter criteria. /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs/anchor - pub async fn anchor_output_ids(&self, query_parameters: AnchorOutputQueryParameters) -> Result { + pub async fn anchor_output_ids( + &self, + query_parameters: AnchorOutputQueryParameters, + ) -> Result { let route = "api/indexer/v2/outputs/anchor"; self.get_output_ids(route, query_parameters, true).await @@ -80,7 +86,7 @@ impl ClientInner { /// Get anchor output by its anchorID. /// api/indexer/v2/outputs/anchor/{bech32Address} - pub async fn anchor_output_id(&self, anchor_id: AnchorId) -> Result { + pub async fn anchor_output_id(&self, anchor_id: AnchorId) -> Result { let bech32_address = anchor_id.to_bech32(self.get_bech32_hrp().await?); let route = format!("api/indexer/v2/outputs/anchor/{bech32_address}"); @@ -88,7 +94,7 @@ impl ClientInner { .get_output_ids(&route, AnchorOutputQueryParameters::new(), true) .await? .first() - .ok_or_else(|| Error::NoOutput(format!("{anchor_id:?}")))?)) + .ok_or_else(|| ClientError::NoOutput(format!("{anchor_id:?}")))?)) } /// Get delegation outputs filtered by the given parameters. @@ -98,7 +104,7 @@ impl ClientInner { pub async fn delegation_output_ids( &self, query_parameters: DelegationOutputQueryParameters, - ) -> Result { + ) -> Result { let route = "api/indexer/v2/outputs/delegation"; self.get_output_ids(route, query_parameters, true).await @@ -106,14 +112,14 @@ impl ClientInner { /// Get delegation output by its delegationID. /// api/indexer/v2/outputs/delegation/:{DelegationId} - pub async fn delegation_output_id(&self, delegation_id: DelegationId) -> Result { + pub async fn delegation_output_id(&self, delegation_id: DelegationId) -> Result { let route = format!("api/indexer/v2/outputs/delegation/{delegation_id}"); Ok(*(self .get_output_ids(&route, DelegationOutputQueryParameters::new(), true) .await? .first() - .ok_or_else(|| Error::NoOutput(format!("{delegation_id:?}")))?)) + .ok_or_else(|| ClientError::NoOutput(format!("{delegation_id:?}")))?)) } /// Get foundry outputs filtered by the given parameters. @@ -123,7 +129,7 @@ impl ClientInner { pub async fn foundry_output_ids( &self, query_parameters: FoundryOutputQueryParameters, - ) -> Result { + ) -> Result { let route = "api/indexer/v2/outputs/foundry"; self.get_output_ids(route, query_parameters, true).await @@ -131,20 +137,23 @@ impl ClientInner { /// Get foundry output by its foundryID. /// api/indexer/v2/outputs/foundry/:{FoundryID} - pub async fn foundry_output_id(&self, foundry_id: FoundryId) -> Result { + pub async fn foundry_output_id(&self, foundry_id: FoundryId) -> Result { let route = format!("api/indexer/v2/outputs/foundry/{foundry_id}"); Ok(*(self .get_output_ids(&route, FoundryOutputQueryParameters::new(), true) .await? .first() - .ok_or_else(|| Error::NoOutput(format!("{foundry_id:?}")))?)) + .ok_or_else(|| ClientError::NoOutput(format!("{foundry_id:?}")))?)) } /// Get NFT outputs filtered by the given parameters. /// Returns Err(Node(NotFound) if no results are found. /// api/indexer/v2/outputs/nft - pub async fn nft_output_ids(&self, query_parameters: NftOutputQueryParameters) -> Result { + pub async fn nft_output_ids( + &self, + query_parameters: NftOutputQueryParameters, + ) -> Result { let route = "api/indexer/v2/outputs/nft"; self.get_output_ids(route, query_parameters, true).await @@ -152,7 +161,7 @@ impl ClientInner { /// Get NFT output by its nftID. /// api/indexer/v2/outputs/nft/{bech32Address} - pub async fn nft_output_id(&self, nft_id: NftId) -> Result { + pub async fn nft_output_id(&self, nft_id: NftId) -> Result { let bech32_address = nft_id.to_bech32(self.get_bech32_hrp().await?); let route = format!("api/indexer/v2/outputs/nft/{bech32_address}"); @@ -160,6 +169,6 @@ impl ClientInner { .get_output_ids(&route, NftOutputQueryParameters::new(), true) .await? .first() - .ok_or_else(|| Error::NoOutput(format!("{nft_id:?}")))?)) + .ok_or_else(|| ClientError::NoOutput(format!("{nft_id:?}")))?)) } } diff --git a/sdk/src/client/node_api/mod.rs b/sdk/src/client/node_api/mod.rs index b88a53afae..6526a8a167 100644 --- a/sdk/src/client/node_api/mod.rs +++ b/sdk/src/client/node_api/mod.rs @@ -19,8 +19,8 @@ pub(crate) fn query_tuples_to_query_string( ) -> Option { let query = tuples .into_iter() - .filter_map(|tuple| tuple.map(|(key, value)| format!("{}={}", key, value))) + .filter_map(|tuple| tuple.map(|(key, value)| format!("{key}={value}"))) .collect::>(); - if query.is_empty() { None } else { Some(query.join("&")) } + (!query.is_empty()).then_some(query.join("&")) } diff --git a/sdk/src/client/node_api/mqtt/mod.rs b/sdk/src/client/node_api/mqtt/mod.rs index 3d8d34dcfa..7e80b04afb 100644 --- a/sdk/src/client/node_api/mqtt/mod.rs +++ b/sdk/src/client/node_api/mqtt/mod.rs @@ -193,11 +193,12 @@ fn poll_mqtt(client: &Client, mut event_loop: EventLoop) { let event = { if p.topic.contains("blocks") || p.topic.contains("included-block") { let payload = &*p.payload; - let protocol_parameters = &client.network_info.read().await.protocol_parameters; + let protocol_parameters = + client.network_info.read().await.protocol_parameters.clone(); match serde_json::from_slice::(payload) { Ok(block_dto) => { - match Block::try_from_dto_with_params(block_dto, protocol_parameters) { + match Block::try_from_dto_with_params(block_dto, &protocol_parameters) { Ok(block) => Ok(TopicEvent { topic: p.topic.clone(), payload: MqttPayload::Block((&block).into()), diff --git a/sdk/src/client/node_api/participation.rs b/sdk/src/client/node_api/participation.rs index f86a198109..a5ef4e5322 100644 --- a/sdk/src/client/node_api/participation.rs +++ b/sdk/src/client/node_api/participation.rs @@ -6,7 +6,7 @@ //! use crate::{ - client::{node_api::query_tuples_to_query_string, ClientInner, Result}, + client::{node_api::query_tuples_to_query_string, ClientError, ClientInner}, types::{ api::plugins::participation::{ responses::{AddressOutputsResponse, EventsResponse, OutputStatusResponse}, @@ -22,7 +22,7 @@ use crate::{ impl ClientInner { /// RouteParticipationEvents is the route to list all events, returning their ID, the event name and status. - pub async fn events(&self, event_type: Option) -> Result { + pub async fn events(&self, event_type: Option) -> Result { let route = "api/participation/v1/events"; let query = query_tuples_to_query_string([event_type.map(|t| ("type", (t as u8).to_string()))]); @@ -31,7 +31,7 @@ impl ClientInner { } /// RouteParticipationEvent is the route to access a single participation by its ID. - pub async fn event(&self, event_id: &ParticipationEventId) -> Result { + pub async fn event(&self, event_id: &ParticipationEventId) -> Result { let route = format!("api/participation/v1/events/{event_id}"); self.get_request(&route, None, false).await @@ -42,7 +42,7 @@ impl ClientInner { &self, event_id: &ParticipationEventId, milestone_index: Option, - ) -> Result { + ) -> Result { let route = format!("api/participation/v1/events/{event_id}/status"); let query = query_tuples_to_query_string([milestone_index.map(|i| ("milestoneIndex", i.to_string()))]); @@ -51,7 +51,7 @@ impl ClientInner { } /// RouteOutputStatus is the route to get the vote status for a given output ID. - pub async fn output_status(&self, output_id: &OutputId) -> Result { + pub async fn output_status(&self, output_id: &OutputId) -> Result { let route = format!("api/participation/v1/outputs/{output_id}"); self.get_request(&route, None, false).await @@ -61,7 +61,7 @@ impl ClientInner { pub async fn address_staking_status( &self, bech32_address: impl ConvertTo, - ) -> Result { + ) -> Result { let route = format!("api/participation/v1/addresses/{}", bech32_address.convert()?); self.get_request(&route, None, false).await @@ -71,7 +71,7 @@ impl ClientInner { pub async fn address_participation_output_ids( &self, bech32_address: impl ConvertTo, - ) -> Result { + ) -> Result { let route = format!("api/participation/v1/addresses/{}/outputs", bech32_address.convert()?); self.get_request(&route, None, false).await diff --git a/sdk/src/client/node_api/plugin/mod.rs b/sdk/src/client/node_api/plugin/mod.rs index ee0574a94a..6ed25675a3 100644 --- a/sdk/src/client/node_api/plugin/mod.rs +++ b/sdk/src/client/node_api/plugin/mod.rs @@ -7,7 +7,7 @@ use core::str::FromStr; use reqwest::Method; -use crate::client::{ClientInner, Result}; +use crate::client::{ClientError, ClientInner}; impl ClientInner { /// Extension method which provides request methods for plugins. @@ -18,7 +18,7 @@ impl ClientInner { endpoint: &str, query_params: Vec, request_object: Option, - ) -> Result + ) -> Result where T: serde::de::DeserializeOwned + std::fmt::Debug + serde::Serialize, { @@ -32,9 +32,9 @@ impl ClientInner { match req_method { Ok(Method::GET) => self.get_request(&path, None, false).await, Ok(Method::POST) => self.post_request(&path, request_object.into()).await, - _ => Err(crate::client::Error::Node( - crate::client::node_api::error::Error::NotSupported(method.to_string()), - )), + _ => Err(ClientError::Node(crate::client::node_api::error::Error::NotSupported( + method.to_string(), + ))), } } } diff --git a/sdk/src/client/node_manager/builder.rs b/sdk/src/client/node_manager/builder.rs index 4652756636..64757d8a30 100644 --- a/sdk/src/client/node_manager/builder.rs +++ b/sdk/src/client/node_manager/builder.rs @@ -10,7 +10,7 @@ use url::Url; use crate::client::{ constants::{DEFAULT_MIN_QUORUM_SIZE, DEFAULT_QUORUM_THRESHOLD, DEFAULT_USER_AGENT, NODE_SYNC_INTERVAL}, - error::{Error, Result}, + error::ClientError, node_manager::{ http_client::HttpClient, node::{Node, NodeAuth, NodeDto}, @@ -71,21 +71,20 @@ impl NodeManagerBuilder { Default::default() } - pub(crate) fn with_primary_node(mut self, mut node: Node) -> Result { + pub(crate) fn with_primary_node(mut self, mut node: Node) -> Result { let mut url = validate_url(node.url.clone())?; if let Some(auth) = &node.auth { if let Some((name, password)) = &auth.basic_auth_name_pwd { - url.set_username(name) - .map_err(|_| crate::client::Error::UrlAuth("username"))?; + url.set_username(name).map_err(|_| ClientError::UrlAuth("username"))?; url.set_password(Some(password)) - .map_err(|_| crate::client::Error::UrlAuth("password"))?; + .map_err(|_| ClientError::UrlAuth("password"))?; } node.url = url; } self.primary_nodes.push(NodeDto::Node(node)); Ok(self) } - pub(crate) fn with_primary_nodes(mut self, urls: &[&str]) -> Result { + pub(crate) fn with_primary_nodes(mut self, urls: &[&str]) -> Result { for url in urls { let url = validate_url(Url::parse(url)?)?; self.primary_nodes.push(NodeDto::Node(Node { @@ -98,7 +97,7 @@ impl NodeManagerBuilder { Ok(self) } - pub(crate) fn with_node(mut self, url: &str) -> Result { + pub(crate) fn with_node(mut self, url: &str) -> Result { let url = validate_url(Url::parse(url)?)?; self.nodes.insert(NodeDto::Node(Node { url, @@ -109,15 +108,14 @@ impl NodeManagerBuilder { Ok(self) } - pub(crate) fn with_node_auth(mut self, url: &str, auth: impl Into>) -> Result { + pub(crate) fn with_node_auth(mut self, url: &str, auth: impl Into>) -> Result { let mut url = validate_url(Url::parse(url)?)?; let auth = auth.into(); if let Some(auth) = &auth { if let Some((name, password)) = &auth.basic_auth_name_pwd { - url.set_username(name) - .map_err(|_| crate::client::Error::UrlAuth("username"))?; + url.set_username(name).map_err(|_| ClientError::UrlAuth("username"))?; url.set_password(Some(password)) - .map_err(|_| crate::client::Error::UrlAuth("password"))?; + .map_err(|_| ClientError::UrlAuth("password"))?; } } self.nodes.insert(NodeDto::Node(Node { @@ -129,7 +127,7 @@ impl NodeManagerBuilder { Ok(self) } - pub(crate) fn with_nodes(mut self, urls: &[&str]) -> Result { + pub(crate) fn with_nodes(mut self, urls: &[&str]) -> Result { for url in urls { let url = validate_url(Url::parse(url)?)?; self.nodes.insert(NodeDto::Node(Node { @@ -203,9 +201,9 @@ impl Default for NodeManagerBuilder { } /// Validates if the url starts with http or https -pub fn validate_url(url: Url) -> Result { +pub fn validate_url(url: Url) -> Result { if url.scheme() != "http" && url.scheme() != "https" { - return Err(Error::UrlValidation(format!("invalid scheme: {}", url.scheme()))); + return Err(ClientError::UrlValidation(format!("invalid scheme: {}", url.scheme()))); } Ok(url) } diff --git a/sdk/src/client/node_manager/http_client.rs b/sdk/src/client/node_manager/http_client.rs index b85e591969..37f37357a5 100644 --- a/sdk/src/client/node_manager/http_client.rs +++ b/sdk/src/client/node_manager/http_client.rs @@ -9,10 +9,7 @@ use reqwest::RequestBuilder; use serde::de::DeserializeOwned; use serde_json::Value; -use crate::client::{ - node_api::error::{Error, Result}, - node_manager::node::Node, -}; +use crate::client::{node_api::error::Error, node_manager::node::Node}; pub(crate) struct Response(reqwest::Response); impl Response { @@ -20,16 +17,16 @@ impl Response { self.0.status().as_u16() } - pub(crate) async fn into_json(self) -> Result { + pub(crate) async fn into_json(self) -> Result { self.0.json().await.map_err(Into::into) } #[cfg(not(target_family = "wasm"))] - pub(crate) async fn into_text(self) -> Result { + pub(crate) async fn into_text(self) -> Result { self.0.text().await.map_err(Into::into) } - pub(crate) async fn into_bytes(self) -> Result> { + pub(crate) async fn into_bytes(self) -> Result, Error> { self.0.bytes().await.map(|b| b.to_vec()).map_err(Into::into) } } @@ -48,7 +45,7 @@ impl HttpClient { } } - async fn parse_response(response: reqwest::Response, url: &url::Url) -> Result { + async fn parse_response(response: reqwest::Response, url: &url::Url) -> Result { let status = response.status(); if status.is_success() { Ok(Response(response)) @@ -82,7 +79,7 @@ impl HttpClient { request_builder } - pub(crate) async fn get(&self, node: &Node, timeout: Duration) -> Result { + pub(crate) async fn get(&self, node: &Node, timeout: Duration) -> Result { let mut request_builder = self.client.get(node.url.clone()); request_builder = self.build_request(request_builder, node, timeout); let start_time = instant::Instant::now(); @@ -97,7 +94,7 @@ impl HttpClient { } // Get with header: "accept", "application/vnd.iota.serializer-v2" - pub(crate) async fn get_bytes(&self, node: Node, timeout: Duration) -> Result { + pub(crate) async fn get_bytes(&self, node: Node, timeout: Duration) -> Result { let mut request_builder = self.client.get(node.url.clone()); request_builder = self.build_request(request_builder, &node, timeout); request_builder = request_builder.header("accept", "application/vnd.iota.serializer-v2"); @@ -105,13 +102,13 @@ impl HttpClient { Self::parse_response(resp, &node.url).await } - pub(crate) async fn post_json(&self, node: Node, timeout: Duration, json: Value) -> Result { + pub(crate) async fn post_json(&self, node: Node, timeout: Duration, json: Value) -> Result { let mut request_builder = self.client.post(node.url.clone()); request_builder = self.build_request(request_builder, &node, timeout); Self::parse_response(request_builder.json(&json).send().await?, &node.url).await } - pub(crate) async fn post_bytes(&self, node: Node, timeout: Duration, body: &[u8]) -> Result { + pub(crate) async fn post_bytes(&self, node: Node, timeout: Duration, body: &[u8]) -> Result { let mut request_builder = self.client.post(node.url.clone()); request_builder = self.build_request(request_builder, &node, timeout); request_builder = request_builder.header("Content-Type", "application/vnd.iota.serializer-v2"); diff --git a/sdk/src/client/node_manager/mod.rs b/sdk/src/client/node_manager/mod.rs index d44799d60a..3f61d8619a 100644 --- a/sdk/src/client/node_manager/mod.rs +++ b/sdk/src/client/node_manager/mod.rs @@ -24,10 +24,7 @@ use super::ClientInner; #[cfg(not(target_family = "wasm"))] use crate::client::request_pool::RateLimitExt; use crate::{ - client::{ - error::{Error, Result}, - node_manager::builder::NodeManagerBuilder, - }, + client::{error::ClientError, node_api::core::routes::NodeInfoResponse, node_manager::builder::NodeManagerBuilder}, types::api::core::InfoResponse, }; @@ -65,7 +62,7 @@ impl ClientInner { path: &str, query: Option<&str>, need_quorum: bool, - ) -> Result { + ) -> Result { let node_manager = self.node_manager.read().await; let request = node_manager.get_request(path, query, self.get_timeout().await, need_quorum); #[cfg(not(target_family = "wasm"))] @@ -73,7 +70,7 @@ impl ClientInner { request.await } - pub(crate) async fn get_request_bytes(&self, path: &str, query: Option<&str>) -> Result> { + pub(crate) async fn get_request_bytes(&self, path: &str, query: Option<&str>) -> Result, ClientError> { let node_manager = self.node_manager.read().await; let request = node_manager.get_request_bytes(path, query, self.get_timeout().await); #[cfg(not(target_family = "wasm"))] @@ -81,7 +78,7 @@ impl ClientInner { request.await } - pub(crate) async fn post_request(&self, path: &str, json: Value) -> Result { + pub(crate) async fn post_request(&self, path: &str, json: Value) -> Result { let node_manager = self.node_manager.read().await; let request = node_manager.post_request(path, self.get_timeout().await, json); #[cfg(not(target_family = "wasm"))] @@ -89,7 +86,11 @@ impl ClientInner { request.await } - pub(crate) async fn post_request_bytes(&self, path: &str, body: &[u8]) -> Result { + pub(crate) async fn post_request_bytes( + &self, + path: &str, + body: &[u8], + ) -> Result { let node_manager = self.node_manager.read().await; let request = node_manager.post_request_bytes(path, self.get_timeout().await, body); #[cfg(not(target_family = "wasm"))] @@ -103,7 +104,7 @@ impl NodeManager { NodeManagerBuilder::new() } - fn get_nodes(&self, path: &str, query: Option<&str>) -> Result> { + fn get_nodes(&self, path: &str, query: Option<&str>) -> Result, ClientError> { let mut nodes_with_modified_url: Vec = Vec::new(); // Set primary nodes first, so they will also be used first for requests. @@ -119,7 +120,7 @@ impl NodeManager { { self.healthy_nodes .read() - .map_err(|_| crate::client::Error::PoisonError)? + .map_err(|_| ClientError::PoisonError)? .iter() .cloned() .collect() @@ -143,7 +144,7 @@ impl NodeManager { nodes_with_modified_url.retain(|n| !n.disabled); if nodes_with_modified_url.is_empty() { - return Err(crate::client::Error::HealthyNodePoolEmpty); + return Err(ClientError::HealthyNodePoolEmpty); } // Set path and query parameters @@ -158,10 +159,10 @@ impl NodeManager { if let Some((name, password)) = &auth.basic_auth_name_pwd { node.url .set_username(name) - .map_err(|_| crate::client::Error::UrlAuth("username"))?; + .map_err(|_| ClientError::UrlAuth("username"))?; node.url .set_password(Some(password)) - .map_err(|_| crate::client::Error::UrlAuth("password"))?; + .map_err(|_| ClientError::UrlAuth("password"))?; } } } @@ -175,12 +176,12 @@ impl NodeManager { query: Option<&str>, timeout: Duration, need_quorum: bool, - ) -> Result { + ) -> Result { let mut result: HashMap = HashMap::new(); // Get node urls and set path let nodes = self.get_nodes(path, query)?; if self.quorum && need_quorum && nodes.len() < self.min_quorum_size { - return Err(Error::QuorumPoolSizeError { + return Err(ClientError::QuorumPoolSizeError { available_nodes: nodes.len(), minimum_threshold: self.min_quorum_size, }); @@ -188,7 +189,7 @@ impl NodeManager { // Track amount of results for quorum let mut result_counter = 0; - let mut error: Option = None; + let mut error: Option = None; // Send requests parallel for quorum #[cfg(target_family = "wasm")] let wasm = true; @@ -229,12 +230,12 @@ impl NodeManager { Ok(res) => { // Handle node_info extra because we also want to return the url if path == crate::client::node_api::core::routes::INFO_PATH { - let node_info: InfoResponse = res.into_json().await?; - let wrapper = crate::client::node_api::core::routes::NodeInfoWrapper { - node_info, + let info: InfoResponse = res.into_json().await?; + let node_info = NodeInfoResponse { + info, url: format!("{}://{}", node.url.scheme(), node.url.host_str().unwrap_or("")), }; - let serde_res = serde_json::to_string(&wrapper)?; + let serde_res = serde_json::to_string(&node_info)?; return Ok(serde_json::from_str(&serde_res)?); } @@ -278,7 +279,7 @@ impl NodeManager { { Ok(serde_json::from_str(&res.0)?) } else { - Err(Error::QuorumThresholdError { + Err(ClientError::QuorumThresholdError { quorum_size: res.1, minimum_threshold: self.min_quorum_size, }) @@ -291,7 +292,7 @@ impl NodeManager { path: &str, query: Option<&str>, timeout: Duration, - ) -> Result> { + ) -> Result, ClientError> { // Get node urls and set path let nodes = self.get_nodes(path, query)?; let mut error = None; @@ -319,7 +320,7 @@ impl NodeManager { path: &str, timeout: Duration, body: &[u8], - ) -> Result { + ) -> Result { let nodes = self.get_nodes(path, None)?; let mut error = None; // Send requests @@ -332,7 +333,7 @@ impl NodeManager { }; } Err(e) => { - error.replace(Error::Node(e)); + error.replace(ClientError::Node(e)); } } } @@ -346,7 +347,7 @@ impl NodeManager { path: &str, timeout: Duration, json: Value, - ) -> Result { + ) -> Result { let nodes = self.get_nodes(path, None)?; let mut error = None; // Send requests @@ -359,7 +360,7 @@ impl NodeManager { }; } Err(e) => { - error.replace(Error::Node(e)); + error.replace(ClientError::Node(e)); } } } diff --git a/sdk/src/client/node_manager/syncing.rs b/sdk/src/client/node_manager/syncing.rs index 583dd32b9d..100582eddf 100644 --- a/sdk/src/client/node_manager/syncing.rs +++ b/sdk/src/client/node_manager/syncing.rs @@ -3,27 +3,24 @@ #[cfg(not(target_family = "wasm"))] use { - crate::types::block::PROTOCOL_VERSION, + crate::{client::NetworkInfo, types::block::PROTOCOL_VERSION}, std::{collections::HashSet, time::Duration}, tokio::time::sleep, }; use super::{Node, NodeManager}; -use crate::{ - client::{Client, ClientInner, Error, Result}, - types::block::protocol::ProtocolParameters, -}; +use crate::client::{Client, ClientError, ClientInner}; impl ClientInner { /// Get a node candidate from the healthy node pool. - pub async fn get_node(&self) -> Result { + pub async fn get_node(&self) -> Result { if let Some(primary_node) = self.node_manager.read().await.primary_nodes.first() { return Ok(primary_node.clone()); } let pool = self.node_manager.read().await.nodes.clone(); - pool.into_iter().next().ok_or(Error::HealthyNodePoolEmpty) + pool.into_iter().next().ok_or(ClientError::HealthyNodePoolEmpty) } /// returns the unhealthy nodes. @@ -46,7 +43,7 @@ impl ClientInner { } #[cfg(not(target_family = "wasm"))] -impl ClientInner { +impl Client { /// Sync the node lists per node_sync_interval milliseconds pub(crate) async fn start_sync_process( &self, @@ -64,17 +61,17 @@ impl ClientInner { } } - pub(crate) async fn sync_nodes(&self, nodes: &HashSet, ignore_node_health: bool) -> Result<()> { + pub(crate) async fn sync_nodes(&self, nodes: &HashSet, ignore_node_health: bool) -> Result<(), ClientError> { use std::collections::HashMap; log::debug!("sync_nodes"); let mut healthy_nodes = HashSet::new(); - let mut network_nodes: HashMap)>> = HashMap::new(); + let mut network_nodes: HashMap)>> = HashMap::new(); for node in nodes { // Put the healthy node url into the network_nodes if node.permanode { - match crate::client::Client::get_permanode_info(node.clone()).await { + match Self::get_permanode_info(node.clone()).await { Ok(info) => { if info.is_healthy || ignore_node_health { // Unwrap: We should always have parameters for this version. If we don't we can't recover. @@ -104,7 +101,7 @@ impl ClientInner { } } } else { - match crate::client::Client::get_node_info(node.url.as_ref(), node.auth.clone()).await { + match Self::get_info(node.url.as_ref(), node.auth.clone()).await { Ok(info) => { if info.status.is_healthy || ignore_node_health { // Unwrap: We should always have parameters for this version. If we don't we can't recover. @@ -149,11 +146,11 @@ impl ClientInner { // Set the protocol_parameters to the parameters that most nodes have in common and only use these nodes as // healthy_nodes if let Some((parameters, _node_url, tangle_time)) = nodes.first() { - let mut network_info = self.network_info.write().await; - - network_info.tangle_time = *tangle_time; // Unwrap: We should always have parameters for this version. If we don't we can't recover. - network_info.protocol_parameters = parameters.clone(); + *self.network_info.write().await = NetworkInfo { + protocol_parameters: parameters.clone(), + tangle_time: *tangle_time, + }; } healthy_nodes.extend(nodes.iter().map(|(_info, node_url, _time)| node_url).cloned()) @@ -166,15 +163,12 @@ impl ClientInner { .await .healthy_nodes .write() - .map_err(|_| crate::client::Error::PoisonError)? = healthy_nodes; + .map_err(|_| ClientError::PoisonError)? = healthy_nodes; Ok(()) } -} -impl Client { - #[cfg(not(target_family = "wasm"))] - pub async fn update_node_manager(&self, node_manager: NodeManager) -> Result<()> { + pub async fn update_node_manager(&self, node_manager: NodeManager) -> Result<(), ClientError> { let node_sync_interval = node_manager.node_sync_interval; let ignore_node_health = node_manager.ignore_node_health; let nodes = node_manager @@ -198,9 +192,11 @@ impl Client { *self._sync_handle.write().await = crate::client::SyncHandle(Some(sync_handle)); Ok(()) } +} - #[cfg(target_family = "wasm")] - pub async fn update_node_manager(&self, node_manager: NodeManager) -> Result<()> { +#[cfg(target_family = "wasm")] +impl Client { + pub async fn update_node_manager(&self, node_manager: NodeManager) -> Result<(), ClientError> { *self.node_manager.write().await = node_manager; Ok(()) } diff --git a/sdk/src/client/secret/ledger_nano.rs b/sdk/src/client/secret/ledger_nano.rs index a54a0863b2..2792ab8cfb 100644 --- a/sdk/src/client/secret/ledger_nano.rs +++ b/sdk/src/client/secret/ledger_nano.rs @@ -20,23 +20,29 @@ use iota_ledger_nano::{ get_app_config, get_buffer_size, get_ledger, get_opened_app, LedgerBIP32Index, Packable as LedgerNanoPackable, TransportTypes, }; -use packable::{error::UnexpectedEOF, unpacker::SliceUnpacker, Packable, PackableExt}; +use packable::{ + error::{UnexpectedEOF, UnpackErrorExt}, + PackableExt, +}; use tokio::sync::Mutex; use super::{GenerateAddressOptions, SecretManage, SecretManagerConfig}; use crate::{ - client::secret::{ - types::{LedgerApp, LedgerDeviceType}, - LedgerNanoStatus, PreparedTransactionData, + client::{ + secret::{ + types::{LedgerApp, LedgerDeviceType}, + LedgerNanoStatus, PreparedTransactionData, + }, + ClientError, }, types::block::{ address::{AccountAddress, Address, NftAddress}, - output::Output, + output::{Output, OutputError}, payload::signed_transaction::SignedTransactionPayload, protocol::ProtocolParameters, - signature::{Ed25519Signature, Signature}, - unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, - Error as BlockError, + signature::{Ed25519Signature, Signature, SignatureError}, + unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, UnlockError, Unlocks}, + BlockError, }, }; @@ -64,7 +70,7 @@ pub enum Error { UnsupportedOperation, /// Block error #[error("{0}")] - Block(Box), + Block(#[from] BlockError), /// Missing input with ed25519 address #[error("missing input with ed25519 address")] MissingInputWithEd25519Address, @@ -76,7 +82,7 @@ pub enum Error { Bip32ChainMismatch, /// Unpack error #[error("{0}")] - Unpack(#[from] packable::error::UnpackError), + Unpack(#[from] packable::error::UnpackError), /// No available inputs provided #[error("No available inputs provided")] NoAvailableInputsProvided, @@ -85,11 +91,7 @@ pub enum Error { ExpirationDeadzone, } -impl From for Error { - fn from(error: crate::types::block::Error) -> Self { - Self::Block(Box::new(error)) - } -} +crate::impl_from_error_via!(Error via BlockError: OutputError, UnlockError, SignatureError); // map most errors to a single error but there are some errors that // need special care. @@ -137,7 +139,7 @@ impl TryFrom for LedgerDeviceType { #[async_trait] impl SecretManage for LedgerSecretManager { - type Error = crate::client::Error; + type Error = ClientError; async fn generate_ed25519_public_keys( &self, @@ -191,7 +193,7 @@ impl SecretManage for LedgerSecretManager { /// Ledger only allows signing messages of 32 bytes, anything else is unsupported and will result in an error. async fn sign_ed25519(&self, msg: &[u8], chain: Bip44) -> Result { - if msg.len() != 32 { + if msg.len() != 32 && msg.len() != 64 { return Err(Error::UnsupportedOperation.into()); } @@ -230,10 +232,8 @@ impl SecretManage for LedgerSecretManager { drop(ledger); drop(lock); - let mut unpacker = SliceUnpacker::new(&signature_bytes); - // Unpack and return signature. - return match Unlock::unpack::<_, true>(&mut unpacker, &())? { + return match Unlock::unpack_bytes_verified(signature_bytes, &()).coerce()? { Unlock::Signature(s) => match *s { SignatureUnlock(Signature::Ed25519(signature)) => Ok(signature), }, @@ -388,12 +388,11 @@ impl SecretManage for LedgerSecretManager { let signature_bytes = ledger.sign(input_len as u16).map_err(Error::from)?; drop(ledger); drop(lock); - let mut unpacker = SliceUnpacker::new(&signature_bytes); // unpack signature to unlocks let mut unlocks = Vec::new(); for _ in 0..input_len { - let unlock = Unlock::unpack::<_, true>(&mut unpacker, &())?; + let unlock = Unlock::unpack_bytes_verified(&signature_bytes, &()).coerce()?; // The ledger nano can return the same SignatureUnlocks multiple times, so only insert it once match unlock { Unlock::Signature(_) => { diff --git a/sdk/src/client/secret/mnemonic.rs b/sdk/src/client/secret/mnemonic.rs index 0c460ec640..4c7ac50523 100644 --- a/sdk/src/client/secret/mnemonic.rs +++ b/sdk/src/client/secret/mnemonic.rs @@ -17,7 +17,7 @@ use zeroize::Zeroizing; use super::{GenerateAddressOptions, SecretManage}; use crate::{ - client::{api::PreparedTransactionData, Client, Error}, + client::{api::PreparedTransactionData, Client, ClientError}, types::block::{ payload::signed_transaction::SignedTransactionPayload, protocol::ProtocolParameters, signature::Ed25519Signature, unlock::Unlocks, @@ -37,7 +37,7 @@ impl std::fmt::Debug for MnemonicSecretManager { #[async_trait] impl SecretManage for MnemonicSecretManager { - type Error = Error; + type Error = ClientError; async fn generate_ed25519_public_keys( &self, @@ -60,9 +60,9 @@ impl SecretManage for MnemonicSecretManager { .secret_key() .public_key(); - crate::client::Result::Ok(public_key) + Ok(public_key) }) - .collect::>()?) + .collect::>()?) } async fn generate_evm_addresses( @@ -86,9 +86,9 @@ impl SecretManage for MnemonicSecretManager { .secret_key() .public_key(); - crate::client::Result::Ok(public_key.evm_address()) + Ok(public_key.evm_address()) }) - .collect::>()?) + .collect::>()?) } async fn sign_ed25519(&self, msg: &[u8], chain: Bip44) -> Result { @@ -136,12 +136,12 @@ impl MnemonicSecretManager { /// Create a new [`MnemonicSecretManager`] from a BIP-39 mnemonic in English. /// /// For more information, see . - pub fn try_from_mnemonic(mnemonic: impl Into) -> Result { + pub fn try_from_mnemonic(mnemonic: impl Into) -> Result { Ok(Self(Client::mnemonic_to_seed(mnemonic.into())?.into())) } /// Create a new [`MnemonicSecretManager`] from a hex-encoded raw seed string. - pub fn try_from_hex_seed(hex: impl Into>) -> Result { + pub fn try_from_hex_seed(hex: impl Into>) -> Result { let hex = hex.into(); let bytes = Zeroizing::new(prefix_hex::decode::>(hex.as_str())?); let seed = Seed::from_bytes(bytes.as_ref()); diff --git a/sdk/src/client/secret/mod.rs b/sdk/src/client/secret/mod.rs index c9213bff69..1ff12b4b86 100644 --- a/sdk/src/client/secret/mod.rs +++ b/sdk/src/client/secret/mod.rs @@ -48,11 +48,8 @@ pub use self::types::{GenerateAddressOptions, LedgerNanoStatus}; use crate::client::secret::types::StrongholdDto; use crate::{ client::{ - api::{ - input_selection::Error as InputSelectionError, transaction::validate_signed_transaction_payload_length, - verify_semantic, PreparedTransactionData, - }, - Error, + api::{transaction_builder::TransactionBuilderError, PreparedTransactionData, SignedTransactionData}, + ClientError, }, types::block::{ address::{Address, Ed25519Address}, @@ -62,7 +59,7 @@ use crate::{ protocol::ProtocolParameters, signature::{Ed25519Signature, Signature}, unlock::{AccountUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, - Block, Error as BlockError, + Block, BlockError, }, }; @@ -239,9 +236,9 @@ impl fmt::Display for SecretManager { } impl FromStr for SecretManager { - type Err = Error; + type Err = ClientError; - fn from_str(s: &str) -> crate::client::Result { + fn from_str(s: &str) -> Result { Self::try_from(serde_json::from_str::(s)?) } } @@ -277,9 +274,9 @@ pub enum SecretManagerDto { } impl TryFrom for SecretManager { - type Error = Error; + type Error = ClientError; - fn try_from(value: SecretManagerDto) -> crate::client::Result { + fn try_from(value: SecretManagerDto) -> Result { Ok(match value { #[cfg(feature = "stronghold")] SecretManagerDto::Stronghold(stronghold_dto) => { @@ -351,7 +348,7 @@ impl From<&SecretManager> for SecretManagerDto { #[async_trait] impl SecretManage for SecretManager { - type Error = Error; + type Error = ClientError; async fn generate_ed25519_public_keys( &self, @@ -380,7 +377,7 @@ impl SecretManage for SecretManager { .generate_ed25519_public_keys(coin_type, account_index, address_indexes, options) .await } - Self::Placeholder => Err(Error::PlaceholderSecretManager), + Self::Placeholder => Err(ClientError::PlaceholderSecretManager), } } @@ -411,11 +408,11 @@ impl SecretManage for SecretManager { .generate_evm_addresses(coin_type, account_index, address_indexes, options) .await } - Self::Placeholder => Err(Error::PlaceholderSecretManager), + Self::Placeholder => Err(ClientError::PlaceholderSecretManager), } } - async fn sign_ed25519(&self, msg: &[u8], chain: Bip44) -> crate::client::Result { + async fn sign_ed25519(&self, msg: &[u8], chain: Bip44) -> Result { match self { #[cfg(feature = "stronghold")] Self::Stronghold(secret_manager) => Ok(secret_manager.sign_ed25519(msg, chain).await?), @@ -424,7 +421,7 @@ impl SecretManage for SecretManager { Self::Mnemonic(secret_manager) => secret_manager.sign_ed25519(msg, chain).await, #[cfg(feature = "private_key_secret_manager")] Self::PrivateKey(secret_manager) => secret_manager.sign_ed25519(msg, chain).await, - Self::Placeholder => Err(Error::PlaceholderSecretManager), + Self::Placeholder => Err(ClientError::PlaceholderSecretManager), } } @@ -441,7 +438,7 @@ impl SecretManage for SecretManager { Self::Mnemonic(secret_manager) => secret_manager.sign_secp256k1_ecdsa(msg, chain).await, #[cfg(feature = "private_key_secret_manager")] Self::PrivateKey(secret_manager) => secret_manager.sign_secp256k1_ecdsa(msg, chain).await, - Self::Placeholder => Err(Error::PlaceholderSecretManager), + Self::Placeholder => Err(ClientError::PlaceholderSecretManager), } } @@ -470,7 +467,7 @@ impl SecretManage for SecretManager { .transaction_unlocks(prepared_transaction_data, protocol_parameters) .await } - Self::Placeholder => Err(Error::PlaceholderSecretManager), + Self::Placeholder => Err(ClientError::PlaceholderSecretManager), } } @@ -499,7 +496,7 @@ impl SecretManage for SecretManager { .sign_transaction(prepared_transaction_data, protocol_parameters) .await } - Self::Placeholder => Err(Error::PlaceholderSecretManager), + Self::Placeholder => Err(ClientError::PlaceholderSecretManager), } } } @@ -553,12 +550,12 @@ impl SecretManagerConfig for SecretManager { impl SecretManager { /// Tries to create a [`SecretManager`] from a mnemonic string. - pub fn try_from_mnemonic(mnemonic: impl Into) -> crate::client::Result { + pub fn try_from_mnemonic(mnemonic: impl Into) -> Result { Ok(Self::Mnemonic(MnemonicSecretManager::try_from_mnemonic(mnemonic)?)) } /// Tries to create a [`SecretManager`] from a seed hex string. - pub fn try_from_hex_seed(seed: impl Into>) -> crate::client::Result { + pub fn try_from_hex_seed(seed: impl Into>) -> Result { Ok(Self::Mnemonic(MnemonicSecretManager::try_from_hex_seed(seed)?)) } } @@ -567,9 +564,9 @@ pub(crate) async fn default_transaction_unlocks( secret_manager: &M, prepared_transaction_data: &PreparedTransactionData, protocol_parameters: &ProtocolParameters, -) -> crate::client::Result +) -> Result where - crate::client::Error: From, + ClientError: From, { let transaction_signing_hash = prepared_transaction_data.transaction.signing_hash(); let mut blocks = Vec::new(); @@ -586,7 +583,7 @@ where let required_address = input .output .required_address(commitment_slot_index, protocol_parameters.committable_age_range())? - .ok_or(crate::client::Error::ExpirationDeadzone)?; + .ok_or(ClientError::ExpirationDeadzone)?; // Convert restricted and implicit addresses to Ed25519 address, so they're the same entry in `block_indexes`. let required_address = match required_address { @@ -612,10 +609,10 @@ where // than the current block index match &required_address { Address::Ed25519(_) | Address::ImplicitAccountCreation(_) => {} - _ => Err(InputSelectionError::MissingInputWithEd25519Address)?, + _ => Err(TransactionBuilderError::MissingInputWithEd25519Address)?, } - let chain = input.chain.ok_or(Error::MissingBip32Chain)?; + let chain = input.chain.ok_or(ClientError::MissingBip32Chain)?; let block = secret_manager .signature_unlock(&transaction_signing_hash, chain) @@ -651,9 +648,9 @@ pub(crate) async fn default_sign_transaction( secret_manager: &M, prepared_transaction_data: PreparedTransactionData, protocol_parameters: &ProtocolParameters, -) -> crate::client::Result +) -> Result where - crate::client::Error: From, + ClientError: From, { log::debug!("[sign_transaction] {:?}", prepared_transaction_data); @@ -669,30 +666,33 @@ where } = prepared_transaction_data; let tx_payload = SignedTransactionPayload::new(transaction, unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic(&inputs_data, &tx_payload, mana_rewards, protocol_parameters.clone())?; + let data = SignedTransactionData { + payload: tx_payload, + inputs_data, + mana_rewards, + }; - if let Some(conflict) = conflict { - log::debug!("[sign_transaction] conflict: {conflict:?} for {:#?}", tx_payload); - return Err(Error::TransactionSemantic(conflict)); - } + data.verify_semantic(protocol_parameters).inspect_err(|e| { + log::debug!("[sign_transaction] conflict: {e:?} for {:#?}", data.payload); + })?; - Ok(tx_payload) + Ok(data.payload) } #[async_trait] pub trait SignBlock { - async fn sign_ed25519(self, secret_manager: &S, chain: Bip44) -> crate::client::Result + async fn sign_ed25519(self, secret_manager: &S, chain: Bip44) -> Result where - crate::client::Error: From; + ClientError: From; } #[async_trait] impl SignBlock for UnsignedBlock { - async fn sign_ed25519(self, secret_manager: &S, chain: Bip44) -> crate::client::Result + async fn sign_ed25519(self, secret_manager: &S, chain: Bip44) -> Result where - crate::client::Error: From, + ClientError: From, { let msg = self.signing_input(); Ok(self.finish(secret_manager.sign_ed25519(&msg, chain).await?)?) diff --git a/sdk/src/client/secret/private_key.rs b/sdk/src/client/secret/private_key.rs index 4a07392e14..a6d8d04d09 100644 --- a/sdk/src/client/secret/private_key.rs +++ b/sdk/src/client/secret/private_key.rs @@ -17,7 +17,7 @@ use zeroize::{Zeroize, Zeroizing}; use super::{GenerateAddressOptions, SecretManage}; use crate::{ - client::{api::PreparedTransactionData, Error}, + client::{api::PreparedTransactionData, ClientError}, types::block::{ payload::signed_transaction::SignedTransactionPayload, protocol::ProtocolParameters, signature::Ed25519Signature, unlock::Unlocks, @@ -35,7 +35,7 @@ impl std::fmt::Debug for PrivateKeySecretManager { #[async_trait] impl SecretManage for PrivateKeySecretManager { - type Error = Error; + type Error = ClientError; async fn generate_ed25519_public_keys( &self, @@ -44,7 +44,7 @@ impl SecretManage for PrivateKeySecretManager { _address_indexes: Range, _options: impl Into> + Send, ) -> Result, Self::Error> { - crate::client::Result::Ok(vec![self.0.public_key()]) + Ok(vec![self.0.public_key()]) } async fn generate_evm_addresses( @@ -55,7 +55,7 @@ impl SecretManage for PrivateKeySecretManager { _options: impl Into> + Send, ) -> Result, Self::Error> { // TODO replace with a more fitting variant. - Err(Error::SecretManagerMismatch) + Err(ClientError::SecretManagerMismatch) } async fn sign_ed25519(&self, msg: &[u8], _chain: Bip44) -> Result { @@ -71,7 +71,7 @@ impl SecretManage for PrivateKeySecretManager { _chain: Bip44, ) -> Result<(secp256k1_ecdsa::PublicKey, secp256k1_ecdsa::RecoverableSignature), Self::Error> { // TODO replace with a more fitting variant. - Err(Error::SecretManagerMismatch) + Err(ClientError::SecretManagerMismatch) } async fn transaction_unlocks( @@ -93,7 +93,7 @@ impl SecretManage for PrivateKeySecretManager { impl PrivateKeySecretManager { /// Create a new [`PrivateKeySecretManager`] from a base 58 encoded private key. - pub fn try_from_b58>(b58: T) -> Result { + pub fn try_from_b58>(b58: T) -> Result { let mut bytes = [0u8; ed25519::SecretKey::LENGTH]; // TODO replace with a more fitting variant. @@ -114,7 +114,7 @@ impl PrivateKeySecretManager { } /// Create a new [`PrivateKeySecretManager`] from an hex encoded private key. - pub fn try_from_hex(hex: impl Into>) -> Result { + pub fn try_from_hex(hex: impl Into>) -> Result { let mut bytes = prefix_hex::decode(hex.into())?; let private_key = Self(ed25519::SecretKey::from_bytes(&bytes)); diff --git a/sdk/src/client/secret/types.rs b/sdk/src/client/secret/types.rs index 7bf5366017..9492fecf12 100644 --- a/sdk/src/client/secret/types.rs +++ b/sdk/src/client/secret/types.rs @@ -39,6 +39,7 @@ impl core::fmt::Debug for StrongholdDto { #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", default)] pub struct GenerateAddressOptions { + /// Whether to generate an internal address. pub internal: bool, /// Display the address on ledger devices. pub ledger_nano_prompt: bool, diff --git a/sdk/src/client/stronghold/secret.rs b/sdk/src/client/stronghold/secret.rs index 8878069aa9..202da7f5d0 100644 --- a/sdk/src/client/stronghold/secret.rs +++ b/sdk/src/client/stronghold/secret.rs @@ -33,6 +33,7 @@ use crate::{ api::PreparedTransactionData, secret::{types::StrongholdDto, GenerateAddressOptions, SecretManage, SecretManagerConfig}, stronghold::Error, + ClientError, }, types::block::{ payload::signed_transaction::SignedTransactionPayload, protocol::ProtocolParameters, @@ -42,7 +43,7 @@ use crate::{ #[async_trait] impl SecretManage for StrongholdAdapter { - type Error = crate::client::Error; + type Error = ClientError; async fn generate_ed25519_public_keys( &self, diff --git a/sdk/src/client/utils.rs b/sdk/src/client/utils.rs index 00805b66bb..fc705f0137 100644 --- a/sdk/src/client/utils.rs +++ b/sdk/src/client/utils.rs @@ -14,40 +14,22 @@ use crypto::{ use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; -use super::{Client, ClientInner}; +use super::Client; use crate::{ - client::{Error, Result}, + client::ClientError, types::block::{ address::{Address, Bech32Address, Ed25519Address, Hrp, ToBech32Ext}, - output::{AccountId, NftId}, payload::TaggedDataPayload, Block, BlockId, }, utils::ConvertTo, }; -/// Transforms bech32 to hex -pub fn bech32_to_hex(bech32: impl ConvertTo) -> Result { - Ok(match bech32.convert()?.inner() { - Address::Ed25519(ed) => ed.to_string(), - Address::Account(account) => account.to_string(), - Address::Nft(nft) => nft.to_string(), - Address::Anchor(anchor) => anchor.to_string(), - Address::ImplicitAccountCreation(implicit) => implicit.to_string(), - Address::Multi(multi) => multi.to_string(), - Address::Restricted(restricted) => restricted.to_string(), - }) -} - -/// Transforms a hex encoded address to a bech32 encoded address -pub fn hex_to_bech32(hex: &str, bech32_hrp: impl ConvertTo) -> Result { - let address = hex.parse::()?; - - Ok(Address::Ed25519(address).try_to_bech32(bech32_hrp)?) -} - /// Transforms a prefix hex encoded public key to a bech32 encoded address -pub fn hex_public_key_to_bech32_address(hex: &str, bech32_hrp: impl ConvertTo) -> Result { +pub fn hex_public_key_to_bech32_address( + hex: &str, + bech32_hrp: impl ConvertTo, +) -> Result { let public_key: [u8; Ed25519Address::LENGTH] = prefix_hex::decode(hex)?; let address = Ed25519Address::new(Blake2b256::digest(public_key).into()); @@ -55,22 +37,22 @@ pub fn hex_public_key_to_bech32_address(hex: &str, bech32_hrp: impl ConvertTo


Result { +pub fn generate_mnemonic() -> Result { let mut entropy = [0u8; 32]; utils::rand::fill(&mut entropy)?; let mnemonic = wordlist::encode(&entropy, &crypto::keys::bip39::wordlist::ENGLISH) - .map_err(|e| crate::client::Error::InvalidMnemonic(format!("{e:?}")))?; + .map_err(|e| ClientError::InvalidMnemonic(format!("{e:?}")))?; entropy.zeroize(); Ok(mnemonic) } /// Returns a hex encoded seed for a mnemonic. -pub fn mnemonic_to_hex_seed(mnemonic: impl Borrow) -> Result { +pub fn mnemonic_to_hex_seed(mnemonic: impl Borrow) -> Result { Ok(prefix_hex::encode(mnemonic_to_seed(mnemonic)?.as_ref())) } /// Returns a seed for a mnemonic. -pub fn mnemonic_to_seed(mnemonic: impl Borrow) -> Result { +pub fn mnemonic_to_seed(mnemonic: impl Borrow) -> Result { // first we check if the mnemonic is valid to give meaningful errors verify_mnemonic(mnemonic.borrow())?; Ok(crypto::keys::bip39::mnemonic_to_seed( @@ -80,14 +62,14 @@ pub fn mnemonic_to_seed(mnemonic: impl Borrow) -> Result { } /// Verifies that a &str is a valid mnemonic. -pub fn verify_mnemonic(mnemonic: impl Borrow) -> Result<()> { +pub fn verify_mnemonic(mnemonic: impl Borrow) -> Result<(), ClientError> { crypto::keys::bip39::wordlist::verify(mnemonic.borrow(), &crypto::keys::bip39::wordlist::ENGLISH) - .map_err(|e| crate::client::Error::InvalidMnemonic(format!("{e:?}")))?; + .map_err(|e| ClientError::InvalidMnemonic(format!("{e:?}")))?; Ok(()) } /// Requests funds from a faucet -pub async fn request_funds_from_faucet(url: &str, bech32_address: &Bech32Address) -> Result { +pub async fn request_funds_from_faucet(url: &str, bech32_address: &Bech32Address) -> Result { let mut map = HashMap::new(); map.insert("address", bech32_address.to_string()); @@ -97,47 +79,23 @@ pub async fn request_funds_from_faucet(url: &str, bech32_address: &Bech32Address .json(&map) .send() .await - .map_err(|err| Error::Node(err.into()))? + .map_err(|err| ClientError::Node(err.into()))? .text() .await - .map_err(|err| Error::Node(err.into()))?; + .map_err(|err| ClientError::Node(err.into()))?; Ok(faucet_response) } -impl ClientInner { - /// Transforms a hex encoded address to a bech32 encoded address - pub async fn hex_to_bech32( - &self, - hex: &str, - bech32_hrp: Option>, - ) -> crate::client::Result { - match bech32_hrp { - Some(hrp) => Ok(hex_to_bech32(hex, hrp)?), - None => Ok(hex_to_bech32(hex, self.get_bech32_hrp().await?)?), - } - } - - /// Transforms an account id to a bech32 encoded address - pub async fn account_id_to_bech32( - &self, - account_id: AccountId, - bech32_hrp: Option>, - ) -> crate::client::Result { - match bech32_hrp { - Some(hrp) => Ok(account_id.to_bech32(hrp.convert()?)), - None => Ok(account_id.to_bech32(self.get_bech32_hrp().await?)), - } - } - - /// Transforms an nft id to a bech32 encoded address - pub async fn nft_id_to_bech32( +impl Client { + /// Converts an address to its bech32 representation + pub async fn address_to_bech32( &self, - nft_id: NftId, + address: Address, bech32_hrp: Option>, - ) -> crate::client::Result { + ) -> Result { match bech32_hrp { - Some(hrp) => Ok(nft_id.to_bech32(hrp.convert()?)), - None => Ok(nft_id.to_bech32(self.get_bech32_hrp().await?)), + Some(hrp) => Ok(address.to_bech32(hrp.convert()?)), + None => Ok(address.to_bech32(self.get_bech32_hrp().await?)), } } @@ -146,51 +104,46 @@ impl ClientInner { &self, hex: &str, bech32_hrp: Option>, - ) -> crate::client::Result { + ) -> Result { match bech32_hrp { Some(hrp) => Ok(hex_public_key_to_bech32_address(hex, hrp)?), None => Ok(hex_public_key_to_bech32_address(hex, self.get_bech32_hrp().await?)?), } } -} - -impl Client { - /// Transforms bech32 to hex - pub fn bech32_to_hex(bech32: impl ConvertTo) -> crate::client::Result { - bech32_to_hex(bech32) - } /// Generates a new mnemonic. - pub fn generate_mnemonic() -> Result { + pub fn generate_mnemonic() -> Result { generate_mnemonic() } /// Returns a seed for a mnemonic. - pub fn mnemonic_to_seed(mnemonic: impl Borrow) -> Result { + pub fn mnemonic_to_seed(mnemonic: impl Borrow) -> Result { mnemonic_to_seed(mnemonic) } /// Returns a hex encoded seed for a mnemonic. - pub fn mnemonic_to_hex_seed(mnemonic: impl Borrow) -> Result { + pub fn mnemonic_to_hex_seed(mnemonic: impl Borrow) -> Result { mnemonic_to_hex_seed(mnemonic) } /// UTF-8 encodes the `tag` of a given TaggedDataPayload. - pub fn tag_to_utf8(payload: &TaggedDataPayload) -> Result { - String::from_utf8(payload.tag().to_vec()).map_err(|_| Error::TaggedData("found invalid UTF-8".to_string())) + pub fn tag_to_utf8(payload: &TaggedDataPayload) -> Result { + String::from_utf8(payload.tag().to_vec()) + .map_err(|_| ClientError::TaggedData("found invalid UTF-8".to_string())) } /// UTF-8 encodes the `data` of a given TaggedDataPayload. - pub fn data_to_utf8(payload: &TaggedDataPayload) -> Result { - String::from_utf8(payload.data().to_vec()).map_err(|_| Error::TaggedData("found invalid UTF-8".to_string())) + pub fn data_to_utf8(payload: &TaggedDataPayload) -> Result { + String::from_utf8(payload.data().to_vec()) + .map_err(|_| ClientError::TaggedData("found invalid UTF-8".to_string())) } /// UTF-8 encodes both the `tag` and `data` of a given TaggedDataPayload. - pub fn tagged_data_to_utf8(payload: &TaggedDataPayload) -> Result<(String, String)> { + pub fn tagged_data_to_utf8(payload: &TaggedDataPayload) -> Result<(String, String), ClientError> { Ok((Self::tag_to_utf8(payload)?, Self::data_to_utf8(payload)?)) } - pub async fn block_id(&self, block: &Block) -> Result { + pub async fn block_id(&self, block: &Block) -> Result { Ok(block.id(&self.get_protocol_parameters().await?)) } } diff --git a/sdk/src/types/api/core.rs b/sdk/src/types/api/core.rs index aee9a6e31c..c43a4fdfdd 100644 --- a/sdk/src/types/api/core.rs +++ b/sdk/src/types/api/core.rs @@ -14,16 +14,24 @@ use crate::{ types::block::{ address::Bech32Address, core::Parents, - output::{Output, OutputId, OutputIdProof, OutputMetadata, OutputWithMetadata}, + output::{Output, OutputId, OutputIdProof, OutputMetadata}, payload::signed_transaction::TransactionId, protocol::{ProtocolParameters, ProtocolParametersHash}, semantic::TransactionFailureReason, slot::{EpochIndex, SlotCommitment, SlotCommitmentId, SlotIndex}, - BlockDto, BlockId, + BlockDto, BlockError, BlockId, }, utils::serde::{option_string, string}, }; +/// Response of GET /api/routes. +/// The available API route groups of the node. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RoutesResponse { + pub routes: Vec, +} + /// Response of GET /api/core/v3/info. /// General information about the node. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -32,7 +40,6 @@ pub struct InfoResponse { pub name: String, pub version: String, pub status: StatusResponse, - pub metrics: MetricsResponse, pub protocol_parameters: ProtocolParametersMap, pub base_token: BaseTokenResponse, } @@ -97,11 +104,10 @@ pub struct StatusResponse { pub pruning_epoch: EpochIndex, } -/// Returned in [`InfoResponse`]. -/// Metric information about the node. +/// Metrics information about the network. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MetricsResponse { +pub struct NetworkMetricsResponse { #[serde(with = "string")] pub blocks_per_second: f64, #[serde(with = "string")] @@ -197,24 +203,24 @@ pub struct BaseTokenResponse { #[serde(rename_all = "camelCase")] pub struct ValidatorResponse { /// Account address of the validator. - address: Bech32Address, + pub address: Bech32Address, /// The epoch index until which the validator registered to stake. - staking_end_epoch: EpochIndex, + pub staking_end_epoch: EpochIndex, /// The total stake of the pool, including delegators. #[serde(with = "string")] - pool_stake: u64, + pub pool_stake: u64, /// The stake of a validator. #[serde(with = "string")] - validator_stake: u64, + pub validator_stake: u64, /// The fixed cost of the validator, which it receives as part of its Mana rewards. #[serde(with = "string")] - fixed_cost: u64, + pub fixed_cost: u64, /// Shows whether the validator was active recently. - active: bool, + pub active: bool, /// The latest protocol version the validator supported. - latest_supported_protocol_version: u8, + pub latest_supported_protocol_version: u8, /// The protocol hash of the latest supported protocol of the validator. - latest_supported_protocol_hash: ProtocolParametersHash, + pub latest_supported_protocol_hash: ProtocolParametersHash, } /// Response of GET /api/core/v3/blocks/validators. @@ -224,13 +230,13 @@ pub struct ValidatorResponse { #[serde(rename_all = "camelCase")] pub struct ValidatorsResponse { /// List of registered validators ready for the next epoch. - stakers: Vec, + pub validators: Vec, /// The number of validators returned per one API request with pagination. - page_size: u32, + pub page_size: u32, /// The cursor that needs to be provided as cursor query parameter to request the next page. If empty, this was the /// last page. #[serde(default, skip_serializing_if = "Option::is_none")] - cursor: Option, + pub cursor: Option, } /// Response of GET /api/core/v3/rewards/{outputId}. @@ -312,19 +318,15 @@ pub struct IssuanceBlockHeaderResponse { } impl IssuanceBlockHeaderResponse { - pub fn strong_parents( - &self, - ) -> Result, crate::types::block::Error> { + pub fn strong_parents(&self) -> Result, BlockError> { Parents::from_set(self.strong_parents.clone()) } - pub fn weak_parents(&self) -> Result, crate::types::block::Error> { + pub fn weak_parents(&self) -> Result, BlockError> { Parents::from_set(self.weak_parents.clone()) } - pub fn shallow_like_parents( - &self, - ) -> Result, crate::types::block::Error> { + pub fn shallow_like_parents(&self) -> Result, BlockError> { Parents::from_set(self.shallow_like_parents.clone()) } } @@ -484,38 +486,85 @@ pub struct BlockWithMetadataResponse { pub metadata: BlockMetadataResponse, } -// TODO: needs to be aligned with TIP-48. -// https://github.com/iotaledger/iota-sdk/issues/1921 /// Response of GET /api/core/v3/outputs/{output_id}. -/// An output and its metadata. +/// Contains the generic [`Output`] with associated [`OutputIdProof`]. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct OutputWithMetadataResponse { +pub struct OutputResponse { pub output: Output, - pub metadata: OutputMetadata, + pub output_id_proof: OutputIdProof, } -impl From<&OutputWithMetadata> for OutputWithMetadataResponse { - fn from(value: &OutputWithMetadata) -> Self { +impl From<&OutputWithMetadataResponse> for OutputResponse { + fn from(value: &OutputWithMetadataResponse) -> Self { Self { output: value.output().clone(), - metadata: value.metadata, + output_id_proof: value.output_id_proof().clone(), } } } -impl From for OutputWithMetadataResponse { - fn from(value: OutputWithMetadata) -> Self { - Self::from(&value) +impl From for OutputResponse { + fn from(value: OutputWithMetadataResponse) -> Self { + Self { + output: value.output, + output_id_proof: value.output_id_proof, + } } } -/// Response of GET /api/routes. -/// The available API route groups of the node. -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RoutesResponse { - pub routes: Vec, +/// Contains the generic [`Output`] with associated [`OutputIdProof`] and [`OutputMetadata`]. +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "camelCase") +)] +pub struct OutputWithMetadataResponse { + pub output: Output, + pub output_id_proof: OutputIdProof, + pub metadata: OutputMetadata, +} + +impl OutputWithMetadataResponse { + /// Creates a new [`OutputWithMetadataResponse`]. + pub fn new(output: Output, output_id_proof: OutputIdProof, metadata: OutputMetadata) -> Self { + Self { + output, + output_id_proof, + metadata, + } + } + + /// Returns the [`Output`]. + pub fn output(&self) -> &Output { + &self.output + } + + /// Consumes self and returns the [`Output`]. + pub fn into_output(self) -> Output { + self.output + } + + /// Returns the [`OutputIdProof`]. + pub fn output_id_proof(&self) -> &OutputIdProof { + &self.output_id_proof + } + + /// Consumes self and returns the [`OutputIdProof`]. + pub fn into_output_id_proof(self) -> OutputIdProof { + self.output_id_proof + } + + /// Returns the [`OutputMetadata`]. + pub fn metadata(&self) -> &OutputMetadata { + &self.metadata + } + + /// Consumes self and returns the [`OutputMetadata`]. + pub fn into_metadata(self) -> OutputMetadata { + self.metadata + } } /// Response of @@ -549,11 +598,3 @@ pub struct OutputWithId { pub output: Output, pub output_id: OutputId, } - -/// Contains the generic [`Output`] with associated [`OutputIdProof`]. -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct OutputResponse { - pub output: Output, - pub output_id_proof: OutputIdProof, -} diff --git a/sdk/src/types/block/address/account.rs b/sdk/src/types/block/address/account.rs index 0e075b7a82..5021e6df06 100644 --- a/sdk/src/types/block/address/account.rs +++ b/sdk/src/types/block/address/account.rs @@ -6,8 +6,8 @@ use core::str::FromStr; use derive_more::{AsRef, Deref, Display, From}; use crate::types::block::{ + address::AddressError, output::{AccountId, OutputId, StorageScore}, - Error, }; /// An [`Address`](super::Address) derived from an account ID which can be unlocked by unlocking the corresponding @@ -47,7 +47,7 @@ impl AccountAddress { impl StorageScore for AccountAddress {} impl FromStr for AccountAddress { - type Err = Error; + type Err = AddressError; fn from_str(s: &str) -> Result { Ok(Self::new(AccountId::from_str(s)?)) diff --git a/sdk/src/types/block/address/anchor.rs b/sdk/src/types/block/address/anchor.rs index 4444602c98..48878e06fd 100644 --- a/sdk/src/types/block/address/anchor.rs +++ b/sdk/src/types/block/address/anchor.rs @@ -6,8 +6,8 @@ use core::str::FromStr; use derive_more::{AsRef, Deref, Display, From}; use crate::types::block::{ + address::AddressError, output::{AnchorId, OutputId, StorageScore}, - Error, }; /// An [`Address`](super::Address) derived from an anchor ID which can be unlocked by unlocking the corresponding @@ -47,7 +47,7 @@ impl AnchorAddress { impl StorageScore for AnchorAddress {} impl FromStr for AnchorAddress { - type Err = Error; + type Err = AddressError; fn from_str(s: &str) -> Result { Ok(Self::new(AnchorId::from_str(s)?)) diff --git a/sdk/src/types/block/address/bech32.rs b/sdk/src/types/block/address/bech32.rs index 36cbe7bbea..e461315e1f 100644 --- a/sdk/src/types/block/address/bech32.rs +++ b/sdk/src/types/block/address/bech32.rs @@ -17,11 +17,8 @@ use packable::{ }; use crate::{ - types::block::{ - address::{Address, MultiAddress}, - Error, - }, - utils::ConvertTo, + types::block::address::{Address, AddressError, MultiAddress}, + utils::{ConversionError, ConvertTo}, }; #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, Display)] @@ -46,7 +43,7 @@ impl Hrp { } impl FromStr for Hrp { - type Err = Error; + type Err = AddressError; fn from_str(hrp: &str) -> Result { Ok(Self(bech32::Hrp::parse(hrp)?)) @@ -54,31 +51,30 @@ impl FromStr for Hrp { } impl Packable for Hrp { - type UnpackError = Error; + type UnpackError = AddressError; type UnpackVisitor = (); #[inline] fn pack(&self, packer: &mut P) -> Result<(), P::Error> { (self.0.len() as u8).pack(packer)?; - // TODO revisit when/if bech32 adds a way to get the bytes without iteration to avoid collecting - packer.pack_bytes(&self.0.byte_iter().collect::>())?; + packer.pack_bytes(self.0.as_bytes())?; Ok(()) } #[inline] - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { - let len = u8::unpack::<_, VERIFY>(unpacker, visitor).coerce()? as usize; + let len = u8::unpack(unpacker, visitor).coerce()? as usize; let mut bytes = alloc::vec![0u8; len]; unpacker.unpack_bytes(&mut bytes)?; Ok(Self( bech32::Hrp::parse(&String::from_utf8_lossy(&bytes)) - .map_err(|e| UnpackError::Packable(Error::InvalidBech32Hrp(e)))?, + .map_err(|e| UnpackError::Packable(AddressError::Bech32Hrp(e)))?, )) } } @@ -105,8 +101,8 @@ impl PartialEq for Hrp { crate::string_serde_impl!(Hrp); impl + Send> ConvertTo for T { - fn convert(self) -> Result { - Hrp::from_str(self.as_ref()) + fn convert(self) -> Result { + Hrp::from_str(self.as_ref()).map_err(ConversionError::new) } fn convert_unchecked(self) -> Hrp { @@ -124,17 +120,20 @@ pub struct Bech32Address { } impl FromStr for Bech32Address { - type Err = Error; + type Err = AddressError; fn from_str(address: &str) -> Result { match bech32::decode(address) { - Ok((hrp, bytes)) => Address::unpack_verified(bytes.as_slice(), &()) - .map_err(|_| Error::InvalidAddress) + Ok((hrp, bytes)) => Address::unpack_bytes_verified(bytes.as_slice(), &()) + .map_err(|e| match e { + UnpackError::Packable(e) => e, + UnpackError::Unpacker(_) => AddressError::Length(bytes.len()), + }) .map(|address| Self { hrp: Hrp(hrp), inner: address, }), - Err(_) => Err(Error::InvalidAddress), + Err(e) => Err(AddressError::Bech32Encoding(e)), } } } @@ -149,7 +148,7 @@ impl Bech32Address { } /// Creates a new address wrapper by parsing a string HRP. - pub fn try_new(hrp: impl ConvertTo, inner: impl Into
) -> Result { + pub fn try_new(hrp: impl ConvertTo, inner: impl Into
) -> Result { Ok(Self { hrp: hrp.convert()?, inner: inner.into(), @@ -172,7 +171,7 @@ impl Bech32Address { } /// Parses a bech32 address string. - pub fn try_from_str(address: impl AsRef) -> Result { + pub fn try_from_str(address: impl AsRef) -> Result { Self::from_str(address.as_ref()) } } @@ -226,7 +225,7 @@ impl> From for Address { crate::string_serde_impl!(Bech32Address); impl + Send> ConvertTo for T { - fn convert(self) -> Result { - Bech32Address::try_from_str(self) + fn convert(self) -> Result { + Bech32Address::try_from_str(self).map_err(ConversionError::new) } } diff --git a/sdk/src/types/block/address/ed25519.rs b/sdk/src/types/block/address/ed25519.rs index 6cd9b572ed..e844c4cb47 100644 --- a/sdk/src/types/block/address/ed25519.rs +++ b/sdk/src/types/block/address/ed25519.rs @@ -10,7 +10,7 @@ use crypto::{ use derive_more::{AsRef, Deref, From}; use packable::Packable; -use crate::types::block::{output::StorageScore, Error}; +use crate::types::block::{address::AddressError, output::StorageScore}; /// An [`Address`](super::Address) derived from an Ed25519 public key. #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Deref, Packable)] @@ -45,10 +45,10 @@ impl Ed25519Address { impl StorageScore for Ed25519Address {} impl FromStr for Ed25519Address { - type Err = Error; + type Err = AddressError; fn from_str(s: &str) -> Result { - Ok(Self::new(prefix_hex::decode(s).map_err(Error::Hex)?)) + Ok(Self::new(prefix_hex::decode(s).map_err(AddressError::Hex)?)) } } diff --git a/sdk/src/types/block/address/error.rs b/sdk/src/types/block/address/error.rs new file mode 100644 index 0000000000..7fb7c1fc0e --- /dev/null +++ b/sdk/src/types/block/address/error.rs @@ -0,0 +1,55 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use crate::{ + types::block::{ + address::{AddressCapabilityFlag, WeightedAddressCount}, + capabilities::CapabilityError, + IdentifierError, + }, + utils::ConversionError, +}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum AddressError { + #[display(fmt = "invalid address kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid address weight: {_0}")] + Weight(u8), + #[display(fmt = "invalid address length: {_0}")] + Length(usize), + #[display(fmt = "invalid multi address threshold: {_0}")] + MultiAddressThreshold(u16), + #[display(fmt = "invalid multi address cumulative weight {cumulative_weight} < threshold {threshold}")] + MultiAddressCumulativeWeight { cumulative_weight: u16, threshold: u16 }, + #[display(fmt = "invalid weighted address count: {_0}")] + WeightedAddressCount(>::Error), + #[display(fmt = "weighted addresses are not unique and/or sorted")] + WeightedAddressesNotUniqueSorted, + #[display(fmt = "restricted address capability: {_0:?}")] + RestrictedAddressCapability(AddressCapabilityFlag), + #[from] + Bech32Encoding(::bech32::DecodeError), + #[from] + Bech32Hrp(::bech32::primitives::hrp::Error), + #[from] + Hex(prefix_hex::Error), + #[from] + Identifier(IdentifierError), + #[from] + Convert(ConversionError), + #[from] + Capability(CapabilityError), +} + +#[cfg(feature = "std")] +impl std::error::Error for AddressError {} + +impl From for AddressError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/address/mod.rs b/sdk/src/types/block/address/mod.rs index bf75c401fa..a7c1a10a89 100644 --- a/sdk/src/types/block/address/mod.rs +++ b/sdk/src/types/block/address/mod.rs @@ -5,6 +5,7 @@ mod account; mod anchor; mod bech32; mod ed25519; +mod error; mod implicit_account_creation; mod multi; mod nft; @@ -21,23 +22,21 @@ pub use self::{ anchor::AnchorAddress, bech32::{Bech32Address, Hrp}, ed25519::Ed25519Address, + error::AddressError, implicit_account_creation::ImplicitAccountCreationAddress, multi::{MultiAddress, WeightedAddress}, nft::NftAddress, restricted::{AddressCapabilities, AddressCapabilityFlag, RestrictedAddress}, }; use crate::{ - types::block::{ - output::{StorageScore, StorageScoreParameters}, - Error, - }, + types::block::output::{StorageScore, StorageScoreParameters}, utils::ConvertTo, }; /// A generic address supporting different address kinds. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From, Display, Packable)] -#[packable(tag_type = u8, with_error = Error::InvalidAddressKind)] -#[packable(unpack_error = Error)] +#[packable(tag_type = u8, with_error = AddressError::Kind)] +#[packable(unpack_error = AddressError)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] pub enum Address { /// An Ed25519 address. @@ -133,7 +132,7 @@ impl Address { } /// Tries to create an [`Address`] from a bech32 encoded string. - pub fn try_from_bech32(address: impl AsRef) -> Result { + pub fn try_from_bech32(address: impl AsRef) -> Result { Bech32Address::try_from_str(address).map(|res| res.inner) } @@ -160,7 +159,7 @@ impl StorageScore for Address { pub trait ToBech32Ext: Sized { /// Try to encode this address to a bech32 string with the given Human Readable Part as prefix. - fn try_to_bech32(self, hrp: impl ConvertTo) -> Result; + fn try_to_bech32(self, hrp: impl ConvertTo) -> Result; /// Encodes this address to a bech32 string with the given Human Readable Part as prefix. fn to_bech32(self, hrp: Hrp) -> Bech32Address; @@ -171,7 +170,7 @@ pub trait ToBech32Ext: Sized { } impl> ToBech32Ext for T { - fn try_to_bech32(self, hrp: impl ConvertTo) -> Result { + fn try_to_bech32(self, hrp: impl ConvertTo) -> Result { Bech32Address::try_new(hrp, self) } diff --git a/sdk/src/types/block/address/multi.rs b/sdk/src/types/block/address/multi.rs index 3db33a7d79..d46e0c5257 100644 --- a/sdk/src/types/block/address/multi.rs +++ b/sdk/src/types/block/address/multi.rs @@ -9,7 +9,10 @@ use derive_more::{AsRef, Deref, Display, From}; use iterator_sorted::is_unique_sorted; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable, PackableExt}; -use crate::types::block::{address::Address, output::StorageScore, Error}; +use crate::types::block::{ + address::{Address, AddressError}, + output::StorageScore, +}; /// An [`Address`] with an assigned weight. #[derive(Clone, Debug, Display, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Deref, Packable)] @@ -27,11 +30,11 @@ pub struct WeightedAddress { impl WeightedAddress { /// Creates a new [`WeightedAddress`]. - pub fn new(address: impl Into
, weight: u8) -> Result { + pub fn new(address: impl Into
, weight: u8) -> Result { let address = address.into(); - verify_address::(&address)?; - verify_weight::(&weight)?; + verify_address(&address)?; + verify_weight(&weight)?; Ok(Self { address, weight }) } @@ -47,22 +50,20 @@ impl WeightedAddress { } } -fn verify_address(address: &Address) -> Result<(), Error> { - if VERIFY - && !matches!( - address, - Address::Ed25519(_) | Address::Account(_) | Address::Nft(_) | Address::Anchor(_) - ) - { - Err(Error::InvalidAddressKind(address.kind())) +fn verify_address(address: &Address) -> Result<(), AddressError> { + if !matches!( + address, + Address::Ed25519(_) | Address::Account(_) | Address::Nft(_) | Address::Anchor(_) + ) { + Err(AddressError::Kind(address.kind())) } else { Ok(()) } } -fn verify_weight(weight: &u8) -> Result<(), Error> { - if VERIFY && *weight == 0 { - Err(Error::InvalidAddressWeight(*weight)) +fn verify_weight(weight: &u8) -> Result<(), AddressError> { + if *weight == 0 { + Err(AddressError::Weight(*weight)) } else { Ok(()) } @@ -74,13 +75,13 @@ pub(crate) type WeightedAddressCount = /// An [`Address`] that consists of addresses with weights and a threshold value. /// It can be unlocked if the cumulative weight of all unlocked addresses is equal to or exceeds the threshold. #[derive(Clone, Debug, Deref, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = AddressError)] #[packable(verify_with = verify_multi_address)] pub struct MultiAddress { /// The weighted unlocked addresses. #[deref] #[packable(verify_with = verify_addresses)] - #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidWeightedAddressCount(p.into())))] + #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| AddressError::WeightedAddressCount(p.into())))] addresses: BoxedSlicePrefix, /// The threshold that needs to be reached by the unlocked addresses in order to unlock the multi address. #[packable(verify_with = verify_threshold)] @@ -95,7 +96,7 @@ impl MultiAddress { /// Creates a new [`MultiAddress`]. #[inline(always)] - pub fn new(addresses: impl IntoIterator, threshold: u16) -> Result { + pub fn new(addresses: impl IntoIterator, threshold: u16) -> Result { // Using an intermediate BTreeMap to sort the addresses without having to repeatedly packing them. let addresses = addresses .into_iter() @@ -104,15 +105,15 @@ impl MultiAddress { .into_values() .collect::>(); - verify_addresses::(&addresses)?; - verify_threshold::(&threshold)?; + verify_addresses(&addresses)?; + verify_threshold(&threshold)?; let addresses = BoxedSlicePrefix::::try_from(addresses) - .map_err(Error::InvalidWeightedAddressCount)?; + .map_err(AddressError::WeightedAddressCount)?; let multi_address = Self { addresses, threshold }; - verify_multi_address::(&multi_address)?; + verify_multi_address(&multi_address)?; Ok(multi_address) } @@ -141,32 +142,30 @@ impl MultiAddress { } } -fn verify_addresses(addresses: &[WeightedAddress]) -> Result<(), Error> { - if VERIFY && !is_unique_sorted(addresses.iter().map(|a| a.address.pack_to_vec())) { - Err(Error::WeightedAddressesNotUniqueSorted) +fn verify_addresses(addresses: &[WeightedAddress]) -> Result<(), AddressError> { + if !is_unique_sorted(addresses.iter().map(|a| a.address.pack_to_vec())) { + Err(AddressError::WeightedAddressesNotUniqueSorted) } else { Ok(()) } } -fn verify_threshold(threshold: &u16) -> Result<(), Error> { - if VERIFY && *threshold == 0 { - Err(Error::InvalidMultiAddressThreshold(*threshold)) +fn verify_threshold(threshold: &u16) -> Result<(), AddressError> { + if *threshold == 0 { + Err(AddressError::MultiAddressThreshold(*threshold)) } else { Ok(()) } } -fn verify_multi_address(address: &MultiAddress) -> Result<(), Error> { - if VERIFY { - let cumulative_weight = address.iter().map(|address| address.weight as u16).sum::(); +fn verify_multi_address(address: &MultiAddress) -> Result<(), AddressError> { + let cumulative_weight = address.iter().map(|address| address.weight as u16).sum::(); - if cumulative_weight < address.threshold { - return Err(Error::InvalidMultiAddressCumulativeWeight { - cumulative_weight, - threshold: address.threshold, - }); - } + if cumulative_weight < address.threshold { + return Err(AddressError::MultiAddressCumulativeWeight { + cumulative_weight, + threshold: address.threshold, + }); } Ok(()) @@ -206,7 +205,7 @@ mod dto { } impl TryFrom for MultiAddress { - type Error = Error; + type Error = AddressError; fn try_from(value: MultiAddressDto) -> Result { Self::new(value.addresses, value.threshold) diff --git a/sdk/src/types/block/address/nft.rs b/sdk/src/types/block/address/nft.rs index d3e1b6d14b..05938731ff 100644 --- a/sdk/src/types/block/address/nft.rs +++ b/sdk/src/types/block/address/nft.rs @@ -6,8 +6,8 @@ use core::str::FromStr; use derive_more::{AsRef, Deref, Display, From}; use crate::types::block::{ + address::AddressError, output::{NftId, OutputId, StorageScore}, - Error, }; /// An [`Address`](super::Address) derived from an NFT ID which can be unlocked by unlocking the corresponding NFT. @@ -46,7 +46,7 @@ impl NftAddress { impl StorageScore for NftAddress {} impl FromStr for NftAddress { - type Err = Error; + type Err = AddressError; fn from_str(s: &str) -> Result { Ok(Self::from(NftId::from_str(s)?)) diff --git a/sdk/src/types/block/address/restricted.rs b/sdk/src/types/block/address/restricted.rs index d52c316371..f92975d078 100644 --- a/sdk/src/types/block/address/restricted.rs +++ b/sdk/src/types/block/address/restricted.rs @@ -10,10 +10,9 @@ use getset::Getters; use packable::{Packable, PackableExt}; use crate::types::block::{ - address::Address, + address::{Address, AddressError}, capabilities::{Capabilities, CapabilityFlag}, output::{StorageScore, StorageScoreParameters}, - Error, }; /// An [`Address`] that contains another address and allows for configuring its capabilities. @@ -31,10 +30,10 @@ impl RestrictedAddress { /// Creates a new [`RestrictedAddress`] address from an [`Address`] with default allowed capabilities. #[inline(always)] - pub fn new(address: impl Into
) -> Result { + pub fn new(address: impl Into
) -> Result { let address = address.into(); - verify_address::(&address)?; + verify_address(&address)?; Ok(Self { address, @@ -69,7 +68,7 @@ impl StorageScore for RestrictedAddress { } impl TryFrom
for RestrictedAddress { - type Error = Error; + type Error = AddressError; fn try_from(value: Address) -> Result { Self::new(value) @@ -82,14 +81,12 @@ impl core::fmt::Display for RestrictedAddress { } } -fn verify_address(address: &Address) -> Result<(), Error> { - if VERIFY - && !matches!( - address, - Address::Ed25519(_) | Address::Account(_) | Address::Nft(_) | Address::Anchor(_) | Address::Multi(_) - ) - { - Err(Error::InvalidAddressKind(address.kind())) +fn verify_address(address: &Address) -> Result<(), AddressError> { + if !matches!( + address, + Address::Ed25519(_) | Address::Account(_) | Address::Nft(_) | Address::Anchor(_) | Address::Multi(_) + ) { + Err(AddressError::Kind(address.kind())) } else { Ok(()) } @@ -217,7 +214,7 @@ pub(crate) mod dto { } impl TryFrom for RestrictedAddress { - type Error = Error; + type Error = AddressError; fn try_from(value: RestrictedAddressDto) -> Result { Ok(Self::new(value.address)?.with_allowed_capabilities(value.allowed_capabilities)) diff --git a/sdk/src/types/block/capabilities.rs b/sdk/src/types/block/capabilities.rs index 3ba8f8359c..6c8f06d740 100644 --- a/sdk/src/types/block/capabilities.rs +++ b/sdk/src/types/block/capabilities.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use alloc::boxed::Box; -use core::marker::PhantomData; +use core::{convert::Infallible, marker::PhantomData}; use derive_more::Deref; use packable::{ @@ -11,7 +11,25 @@ use packable::{ Packable, }; -use crate::types::block::Error; +#[derive(Debug, PartialEq, Eq, derive_more::Display)] +#[allow(missing_docs)] +pub enum CapabilityError { + #[display(fmt = "invalid capabilities count: {_0}")] + InvalidCount(>::Error), + #[display(fmt = "invalid capability byte at index {index}: {byte:x}")] + InvalidByte { index: usize, byte: u8 }, + #[display(fmt = "capability bytes have trailing zeroes")] + TrailingBytes, +} + +#[cfg(feature = "std")] +impl std::error::Error for CapabilityError {} + +impl From for CapabilityError { + fn from(error: Infallible) -> Self { + match error {} + } +} /// A list of bitflags that represent capabilities. #[derive(Debug, Deref)] @@ -42,15 +60,15 @@ impl Capabilities { impl Capabilities { /// Try to create capabilities from serialized bytes. Bytes with trailing zeroes are invalid. - pub fn from_bytes(bytes: impl Into>) -> Result { - Self::from_prefix_box_slice(bytes.into().try_into().map_err(Error::InvalidCapabilitiesCount)?) + pub fn from_bytes(bytes: impl Into>) -> Result { + Self::from_prefix_box_slice(bytes.into().try_into().map_err(CapabilityError::InvalidCount)?) } /// Try to create capabilities from serialized bytes. Bytes with trailing zeroes are invalid. - pub(crate) fn from_prefix_box_slice(bytes: BoxedSlicePrefix) -> Result { + pub(crate) fn from_prefix_box_slice(bytes: BoxedSlicePrefix) -> Result { // Check if there is a trailing zero. if bytes.last().map(|b| *b == 0).unwrap_or_default() { - return Err(Error::TrailingCapabilityBytes); + return Err(CapabilityError::TrailingBytes); } // Check if the bytes are valid instances of the flag type. for (index, &byte) in bytes.iter().enumerate() { @@ -61,7 +79,7 @@ impl Capabilities { } // Check whether the byte contains erroneous bits by using the max value as a mask if b | byte != b { - return Err(Error::InvalidCapabilityByte { index, byte }); + return Err(CapabilityError::InvalidByte { index, byte }); } } Ok(Self { @@ -193,7 +211,7 @@ impl, Flag: CapabilityFlag> From for Capabilitie } impl Packable for Capabilities { - type UnpackError = crate::types::block::Error; + type UnpackError = CapabilityError; type UnpackVisitor = (); fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -201,12 +219,12 @@ impl Packable for Capabilities { Ok(()) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { Self::from_prefix_box_slice( - BoxedSlicePrefix::unpack::<_, VERIFY>(unpacker, visitor) + BoxedSlicePrefix::unpack(unpacker, visitor) .map_packable_err(|e| match e { UnpackPrefixError::Item(i) | UnpackPrefixError::Prefix(i) => i, }) @@ -354,7 +372,7 @@ mod test { let capability_bytes = [TestFlag::VAL_1 | TestFlag::VAL_4, TestFlag::VAL_9, TestFlag::VAL_3]; assert_eq!( Capabilities::::from_bytes(capability_bytes), - Err(Error::InvalidCapabilityByte { + Err(CapabilityError::InvalidByte { index: 2, byte: TestFlag::VAL_3 }) @@ -366,13 +384,13 @@ mod test { let capability_bytes = [0, 0]; assert_eq!( Capabilities::::from_bytes(capability_bytes), - Err(Error::TrailingCapabilityBytes) + Err(CapabilityError::TrailingBytes) ); let capability_bytes = [TestFlag::VAL_1 | TestFlag::VAL_4, 0]; assert_eq!( Capabilities::::from_bytes(capability_bytes), - Err(Error::TrailingCapabilityBytes) + Err(CapabilityError::TrailingBytes) ); } @@ -381,7 +399,7 @@ mod test { let capability_bytes = [TestFlag::VAL_1 | TestFlag::VAL_3, TestFlag::VAL_9 | TestFlag::VAL_2]; assert_eq!( Capabilities::::from_bytes(capability_bytes), - Err(Error::InvalidCapabilityByte { + Err(CapabilityError::InvalidByte { index: 1, byte: TestFlag::VAL_9 | TestFlag::VAL_2 }) diff --git a/sdk/src/types/block/context_input/error.rs b/sdk/src/types/block/context_input/error.rs new file mode 100644 index 0000000000..9f824c14f5 --- /dev/null +++ b/sdk/src/types/block/context_input/error.rs @@ -0,0 +1,33 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use crate::types::block::{ + context_input::{reward::RewardContextInputIndex, ContextInputCount}, + IdentifierError, +}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum ContextInputError { + #[display(fmt = "invalid context input kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid context input count: {_0}")] + Count(>::Error), + #[display(fmt = "context inputs are not unique and/or sorted")] + NotUniqueSorted, + #[display(fmt = "invalid reward input index: {_0}")] + RewardIndex(>::Error), + #[from] + Identifier(IdentifierError), +} + +#[cfg(feature = "std")] +impl std::error::Error for ContextInputError {} + +impl From for ContextInputError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/context_input/mod.rs b/sdk/src/types/block/context_input/mod.rs index d3b6af8c6c..28d8501847 100644 --- a/sdk/src/types/block/context_input/mod.rs +++ b/sdk/src/types/block/context_input/mod.rs @@ -3,6 +3,7 @@ mod block_issuance_credit; mod commitment; +mod error; mod reward; use alloc::{boxed::Box, vec::Vec}; @@ -12,15 +13,11 @@ use derive_more::{Deref, Display, From}; use iterator_sorted::is_unique_sorted_by; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; -pub(crate) use self::reward::RewardContextInputIndex; pub use self::{ block_issuance_credit::BlockIssuanceCreditContextInput, commitment::CommitmentContextInput, - reward::RewardContextInput, -}; -use crate::types::block::{ - protocol::{WorkScore, WorkScoreParameters}, - Error, + error::ContextInputError, reward::RewardContextInput, }; +use crate::types::block::protocol::{WorkScore, WorkScoreParameters}; /// The maximum number of context inputs of a transaction. pub const CONTEXT_INPUT_COUNT_MAX: u16 = 128; @@ -30,8 +27,8 @@ pub const CONTEXT_INPUT_COUNT_RANGE: RangeInclusive = 0..=CONTEXT_INPUT_COU /// A Context Input provides additional contextual information for the execution of a transaction, such as for different /// functionality related to accounts, commitments, or Mana rewards. A Context Input does not need to be unlocked. #[derive(Clone, Eq, Display, PartialEq, Hash, Ord, PartialOrd, From, packable::Packable)] -#[packable(unpack_error = Error)] -#[packable(tag_type = u8, with_error = Error::InvalidContextInputKind)] +#[packable(unpack_error = ContextInputError)] +#[packable(tag_type = u8, with_error = ContextInputError::Kind)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] pub enum ContextInput { /// A [`CommitmentContextInput`]. @@ -85,13 +82,13 @@ pub(crate) type ContextInputCount = BoundedU16<{ *CONTEXT_INPUT_COUNT_RANGE.start() }, { *CONTEXT_INPUT_COUNT_RANGE.end() }>; #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, Packable)] -#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidContextInputCount(p.into())))] +#[packable(unpack_error = ContextInputError, with = |e| e.unwrap_item_err_or_else(|p| ContextInputError::Count(p.into())))] pub struct ContextInputs( - #[packable(verify_with = verify_context_inputs_packable)] BoxedSlicePrefix, + #[packable(verify_with = verify_context_inputs)] BoxedSlicePrefix, ); impl TryFrom> for ContextInputs { - type Error = Error; + type Error = ContextInputError; #[inline(always)] fn try_from(features: Vec) -> Result { @@ -110,10 +107,10 @@ impl IntoIterator for ContextInputs { impl ContextInputs { /// Creates a new [`ContextInputs`] from a vec. - pub fn from_vec(features: Vec) -> Result { + pub fn from_vec(features: Vec) -> Result { let mut context_inputs = BoxedSlicePrefix::::try_from(features.into_boxed_slice()) - .map_err(Error::InvalidContextInputCount)?; + .map_err(ContextInputError::Count)?; context_inputs.sort_by(context_inputs_cmp); // Sort is obviously fine now but uniqueness still needs to be checked. @@ -138,13 +135,6 @@ impl ContextInputs { } } -fn verify_context_inputs_packable(context_inputs: &[ContextInput]) -> Result<(), Error> { - if VERIFY { - verify_context_inputs(context_inputs)?; - } - Ok(()) -} - fn context_inputs_cmp(a: &ContextInput, b: &ContextInput) -> Ordering { a.kind().cmp(&b.kind()).then_with(|| match (a, b) { (ContextInput::Commitment(_), ContextInput::Commitment(_)) => Ordering::Equal, @@ -157,9 +147,9 @@ fn context_inputs_cmp(a: &ContextInput, b: &ContextInput) -> Ordering { }) } -fn verify_context_inputs(context_inputs: &[ContextInput]) -> Result<(), Error> { +fn verify_context_inputs(context_inputs: &[ContextInput]) -> Result<(), ContextInputError> { if !is_unique_sorted_by(context_inputs.iter(), |a, b| context_inputs_cmp(a, b)) { - return Err(Error::ContextInputsNotUniqueSorted); + return Err(ContextInputError::NotUniqueSorted); } Ok(()) diff --git a/sdk/src/types/block/context_input/reward.rs b/sdk/src/types/block/context_input/reward.rs index 347cdaa350..b6577e43b4 100644 --- a/sdk/src/types/block/context_input/reward.rs +++ b/sdk/src/types/block/context_input/reward.rs @@ -5,8 +5,8 @@ use packable::bounded::BoundedU16; use super::CONTEXT_INPUT_COUNT_RANGE; use crate::types::block::{ + context_input::ContextInputError, protocol::{WorkScore, WorkScoreParameters}, - Error, }; pub(crate) type RewardContextInputIndex = @@ -14,16 +14,16 @@ pub(crate) type RewardContextInputIndex = /// A Reward Context Input indicates which transaction Input is claiming Mana rewards. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, packable::Packable)] -#[packable(unpack_error = Error)] -pub struct RewardContextInput(#[packable(unpack_error_with = Error::InvalidRewardInputIndex)] RewardContextInputIndex); +#[packable(unpack_error = ContextInputError)] +pub struct RewardContextInput(#[packable(unpack_error_with = ContextInputError::RewardIndex)] RewardContextInputIndex); impl RewardContextInput { /// The context input kind of a [`RewardContextInput`]. pub const KIND: u8 = 2; /// Creates a new [`RewardContextInput`]. - pub fn new(index: u16) -> Result { - index.try_into().map(Self).map_err(Error::InvalidRewardInputIndex) + pub fn new(index: u16) -> Result { + index.try_into().map(Self).map_err(ContextInputError::RewardIndex) } /// Returns the index of a [`RewardContextInput`]. @@ -68,7 +68,7 @@ mod dto { } impl TryFrom for RewardContextInput { - type Error = Error; + type Error = ContextInputError; fn try_from(value: RewardContextInputDto) -> Result { Self::new(value.index) diff --git a/sdk/src/types/block/core/basic.rs b/sdk/src/types/block/core/basic.rs index f9fe9849c5..c951412adb 100644 --- a/sdk/src/types/block/core/basic.rs +++ b/sdk/src/types/block/core/basic.rs @@ -4,10 +4,9 @@ use packable::Packable; use crate::types::block::{ - core::{parent::verify_parents_sets, BlockBody, Parents}, + core::{parent::verify_parents_sets, BlockBody, BlockError, Parents}, payload::{OptionalPayload, Payload}, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - Error, }; pub type StrongParents = Parents<1, 8>; @@ -90,7 +89,7 @@ impl BasicBlockBodyBuilder { } /// Finishes the builder into a [`BasicBlockBody`]. - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { verify_parents_sets(&self.strong_parents, &self.weak_parents, &self.shallow_like_parents)?; let mut body = BasicBlockBody { @@ -113,7 +112,7 @@ impl BasicBlockBodyBuilder { } /// Finishes the builder into a [`BlockBody`]. - pub fn finish_block_body(self) -> Result { + pub fn finish_block_body(self) -> Result { Ok(BlockBody::from(self.finish()?)) } } @@ -131,7 +130,7 @@ impl From for BasicBlockBodyBuilder { } #[derive(Clone, Debug, Eq, PartialEq, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = BlockError)] #[packable(unpack_visitor = ProtocolParameters)] #[packable(verify_with = verify_basic_block_body)] pub struct BasicBlockBody { @@ -193,17 +192,12 @@ impl WorkScore for BasicBlockBody { } } -fn verify_basic_block_body( - basic_block_body: &BasicBlockBody, - _: &ProtocolParameters, -) -> Result<(), Error> { - if VERIFY { - verify_parents_sets( - &basic_block_body.strong_parents, - &basic_block_body.weak_parents, - &basic_block_body.shallow_like_parents, - )?; - } +fn verify_basic_block_body(basic_block_body: &BasicBlockBody, _: &ProtocolParameters) -> Result<(), BlockError> { + verify_parents_sets( + &basic_block_body.strong_parents, + &basic_block_body.weak_parents, + &basic_block_body.shallow_like_parents, + )?; Ok(()) } @@ -216,7 +210,7 @@ pub(crate) mod dto { use super::*; use crate::types::{ - block::{payload::dto::PayloadDto, BlockId, Error}, + block::{core::BlockError, payload::dto::PayloadDto, BlockId}, TryFromDto, }; @@ -250,7 +244,7 @@ pub(crate) mod dto { } impl TryFromDto for BasicBlockBody { - type Error = Error; + type Error = BlockError; fn try_from_dto_with_params_inner( dto: BasicBlockBodyDto, diff --git a/sdk/src/types/block/core/block.rs b/sdk/src/types/block/core/block.rs index a2798a8f59..91ba728698 100644 --- a/sdk/src/types/block/core/block.rs +++ b/sdk/src/types/block/core/block.rs @@ -7,7 +7,7 @@ use core::mem::size_of; use crypto::hashes::{blake2b::Blake2b256, Digest}; use getset::{CopyGetters, Getters}; use packable::{ - error::{UnexpectedEOF, UnpackError}, + error::{UnexpectedEOF, UnpackError, UnpackErrorExt}, packer::{Packer, SlicePacker}, unpacker::{CounterUnpacker, SliceUnpacker, Unpacker}, Packable, PackableExt, @@ -15,12 +15,13 @@ use packable::{ use crate::types::block::{ block_id::{BlockHash, BlockId}, - core::{BasicBlockBody, ValidationBlockBody}, + core::{BasicBlockBody, BlockError, ValidationBlockBody}, output::AccountId, + payload::Payload, protocol::ProtocolParameters, signature::Signature, slot::{SlotCommitmentId, SlotIndex}, - BlockBody, Error, + BlockBody, }; /// Block without a signature. Can be finished into a [`Block`]. @@ -56,13 +57,31 @@ impl UnsignedBlock { [self.header.hash(), self.body.hash()].concat() } - pub fn finish(self, signature: impl Into) -> Result { - Ok(Block::new(self.header, self.body, signature)) + /// Finishes an [`UnsignedBlock`] into a [`Block`]. + pub fn finish_with_params<'a>( + self, + signature: impl Into, + params: impl Into>, + ) -> Result { + if let Some(params) = params.into() { + verify_block_slot(&self.header, &self.body, params)?; + } + + Ok(Block { + header: self.header, + body: self.body, + signature: signature.into(), + }) + } + + /// Finishes an [`UnsignedBlock`] into a [`Block`] without protocol validation. + pub fn finish(self, signature: impl Into) -> Result { + self.finish_with_params(signature, None) } } #[derive(Clone, Debug, Eq, PartialEq, CopyGetters, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = BlockError)] #[packable(unpack_visitor = ProtocolParameters)] #[getset(get_copy = "pub")] pub struct BlockHeader { @@ -113,12 +132,9 @@ impl BlockHeader { } } -fn verify_protocol_version( - protocol_version: &u8, - params: &ProtocolParameters, -) -> Result<(), Error> { - if VERIFY && *protocol_version != params.version() { - return Err(Error::ProtocolVersionMismatch { +fn verify_protocol_version(protocol_version: &u8, params: &ProtocolParameters) -> Result<(), BlockError> { + if *protocol_version != params.version() { + return Err(BlockError::ProtocolVersionMismatch { expected: params.version(), actual: *protocol_version, }); @@ -127,9 +143,9 @@ fn verify_protocol_version( Ok(()) } -fn verify_network_id(network_id: &u64, params: &ProtocolParameters) -> Result<(), Error> { - if VERIFY && *network_id != params.network_id() { - return Err(Error::NetworkIdMismatch { +fn verify_network_id(network_id: &u64, params: &ProtocolParameters) -> Result<(), BlockError> { + if *network_id != params.network_id() { + return Err(BlockError::NetworkIdMismatch { expected: params.network_id(), actual: *network_id, }); @@ -157,18 +173,6 @@ impl Block { /// The maximum number of bytes in a block. pub const LENGTH_MAX: usize = 32768; - /// Creates a new [`Block`]. - #[inline(always)] - pub fn new(header: BlockHeader, body: BlockBody, signature: impl Into) -> Self { - let signature = signature.into(); - - Self { - header, - body, - signature, - } - } - /// Creates a new [`UnsignedBlock`]. #[inline(always)] pub fn build(header: BlockHeader, body: BlockBody) -> UnsignedBlock { @@ -230,11 +234,11 @@ impl Block { visitor: &::UnpackVisitor, ) -> Result::UnpackError, UnexpectedEOF>> { let mut unpacker = CounterUnpacker::new(SliceUnpacker::new(bytes.as_ref())); - let block = Self::unpack::<_, true>(&mut unpacker, visitor)?; + let block = Self::unpack_verified(&mut unpacker, visitor)?; // When parsing the block is complete, there should not be any trailing bytes left that were not parsed. - if u8::unpack::<_, true>(&mut unpacker, &()).is_ok() { - return Err(UnpackError::Packable(Error::RemainingBytesAfterBlock)); + if u8::unpack_inner(&mut unpacker, Some(visitor)).is_ok() { + return Err(UnpackError::Packable(BlockError::RemainingBytesAfterBlock)); } Ok(block) @@ -264,7 +268,7 @@ impl Block { } impl Packable for Block { - type UnpackError = Error; + type UnpackError = BlockError; type UnpackVisitor = ProtocolParameters; fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -275,15 +279,15 @@ impl Packable for Block { Ok(()) } - fn unpack( + fn unpack( unpacker: &mut U, - protocol_params: &Self::UnpackVisitor, + protocol_params: Option<&Self::UnpackVisitor>, ) -> Result> { let start_opt = unpacker.read_bytes(); - let header = BlockHeader::unpack::<_, VERIFY>(unpacker, protocol_params)?; - let body = BlockBody::unpack::<_, VERIFY>(unpacker, protocol_params)?; - let signature = Signature::unpack::<_, VERIFY>(unpacker, &())?; + let header = BlockHeader::unpack(unpacker, protocol_params)?; + let body = BlockBody::unpack(unpacker, protocol_params)?; + let signature = Signature::unpack_inner(unpacker, protocol_params).coerce()?; let block = Self { header, @@ -291,7 +295,9 @@ impl Packable for Block { signature, }; - if VERIFY { + if let Some(protocol_params) = protocol_params { + verify_block_slot(&block.header, &block.body, protocol_params).map_err(UnpackError::Packable)?; + let block_len = if let (Some(start), Some(end)) = (start_opt, unpacker.read_bytes()) { end - start } else { @@ -299,7 +305,7 @@ impl Packable for Block { }; if block_len > Self::LENGTH_MAX { - return Err(UnpackError::Packable(Error::InvalidBlockLength(block_len))); + return Err(UnpackError::Packable(BlockError::Length(block_len))); } } @@ -307,6 +313,35 @@ impl Packable for Block { } } +fn verify_block_slot(header: &BlockHeader, body: &BlockBody, params: &ProtocolParameters) -> Result<(), BlockError> { + if let BlockBody::Basic(basic) = body { + if let Some(Payload::SignedTransaction(signed_transaction)) = basic.payload() { + let transaction = signed_transaction.transaction(); + let block_slot = params.slot_index(header.issuing_time / 1_000_000_000); + + if block_slot < transaction.creation_slot() { + return Err(BlockError::BlockSlotBeforeTransactionCreationSlot); + } + + if let Some(commitment) = signed_transaction.transaction().context_inputs().commitment() { + let commitment_slot = commitment.slot_index(); + + if !(block_slot - params.max_committable_age()..=block_slot - params.min_committable_age()) + .contains(&commitment_slot) + { + return Err(BlockError::TransactionCommitmentSlotNotInBlockSlotInterval); + } + + if commitment_slot > header.slot_commitment_id.slot_index() { + return Err(BlockError::TransactionCommitmentSlotAfterBlockCommitmentSlot); + } + } + } + } + + Ok(()) +} + #[cfg(feature = "serde")] pub(crate) mod dto { use serde::{Deserialize, Serialize}; @@ -347,7 +382,7 @@ pub(crate) mod dto { } impl TryFromDto for Block { - type Error = Error; + type Error = BlockError; fn try_from_dto_with_params_inner( dto: BlockDto, @@ -355,25 +390,25 @@ pub(crate) mod dto { ) -> Result { if let Some(protocol_params) = params { if dto.inner.header.protocol_version != protocol_params.version() { - return Err(Error::ProtocolVersionMismatch { + return Err(BlockError::ProtocolVersionMismatch { expected: protocol_params.version(), actual: dto.inner.header.protocol_version, }); } if dto.inner.header.network_id != protocol_params.network_id() { - return Err(Error::NetworkIdMismatch { + return Err(BlockError::NetworkIdMismatch { expected: protocol_params.network_id(), actual: dto.inner.header.network_id, }); } } - Ok(Self::new( + UnsignedBlock::new( BlockHeader::try_from_dto_with_params_inner(dto.inner.header, params)?, BlockBody::try_from_dto_with_params_inner(dto.inner.body, params)?, - dto.signature, - )) + ) + .finish_with_params(dto.signature, params) } } @@ -404,7 +439,7 @@ pub(crate) mod dto { } impl TryFromDto for BlockHeader { - type Error = Error; + type Error = BlockError; fn try_from_dto_with_params_inner( dto: BlockHeaderDto, @@ -412,14 +447,14 @@ pub(crate) mod dto { ) -> Result { if let Some(protocol_params) = params { if dto.protocol_version != protocol_params.version() { - return Err(Error::ProtocolVersionMismatch { + return Err(BlockError::ProtocolVersionMismatch { expected: protocol_params.version(), actual: dto.protocol_version, }); } if dto.network_id != protocol_params.network_id() { - return Err(Error::NetworkIdMismatch { + return Err(BlockError::NetworkIdMismatch { expected: protocol_params.network_id(), actual: dto.network_id, }); @@ -454,7 +489,7 @@ pub(crate) mod dto { } impl TryFromDto for UnsignedBlock { - type Error = Error; + type Error = BlockError; fn try_from_dto_with_params_inner( dto: UnsignedBlockDto, diff --git a/sdk/src/types/block/core/error.rs b/sdk/src/types/block/core/error.rs new file mode 100644 index 0000000000..16a47bbdb8 --- /dev/null +++ b/sdk/src/types/block/core/error.rs @@ -0,0 +1,76 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use crate::types::block::{ + context_input::ContextInputError, + input::InputError, + mana::ManaError, + output::{ + feature::FeatureError, unlock_condition::UnlockConditionError, NativeTokenError, OutputError, TokenSchemeError, + }, + payload::PayloadError, + protocol::ProtocolParametersError, + signature::SignatureError, + unlock::UnlockError, + IdentifierError, +}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum BlockError { + #[display(fmt = "invalid block body kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid block length {_0}")] + Length(usize), + #[display(fmt = "remaining bytes after block")] + RemainingBytesAfterBlock, + #[display(fmt = "invalid parent count")] + InvalidParentCount, + #[display(fmt = "weak parents are not disjoint to strong or shallow like parents")] + NonDisjointParents, + #[display(fmt = "parents are not unique and/or sorted")] + ParentsNotUniqueSorted, + #[display(fmt = "network ID mismatch: expected {expected} but got {actual}")] + NetworkIdMismatch { expected: u64, actual: u64 }, + #[display(fmt = "protocol version mismatch: expected {expected} but got {actual}")] + ProtocolVersionMismatch { expected: u8, actual: u8 }, + #[display(fmt = "unsupported address kind: {_0}")] + UnsupportedAddressKind(u8), + #[display(fmt = "the block slot is before its contained transaction creation slot")] + BlockSlotBeforeTransactionCreationSlot, + #[display(fmt = "the transaction commitment slot is not in the allowed block slot interval")] + TransactionCommitmentSlotNotInBlockSlotInterval, + #[display(fmt = "the transaction commitment slot is after the block commitment slot")] + TransactionCommitmentSlotAfterBlockCommitmentSlot, + #[from] + Payload(PayloadError), + #[from] + Signature(SignatureError), + #[from] + Identifier(IdentifierError), + #[from] + ProtocolParameters(ProtocolParametersError), +} + +#[cfg(feature = "std")] +impl std::error::Error for BlockError {} + +crate::impl_from_error_via!(BlockError via PayloadError: + UnlockError, + ContextInputError, + NativeTokenError, + ManaError, + UnlockConditionError, + FeatureError, + TokenSchemeError, + InputError, + OutputError +); + +impl From for BlockError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/core/mod.rs b/sdk/src/types/block/core/mod.rs index ea957c4647..f9866a52ff 100644 --- a/sdk/src/types/block/core/mod.rs +++ b/sdk/src/types/block/core/mod.rs @@ -3,6 +3,7 @@ pub mod basic; mod block; +mod error; mod parent; pub mod validation; @@ -15,19 +16,19 @@ use packable::{Packable, PackableExt}; pub use self::{ basic::{BasicBlockBody, BasicBlockBodyBuilder}, block::{Block, BlockHeader, UnsignedBlock}, + error::BlockError, parent::Parents, validation::{ValidationBlockBody, ValidationBlockBodyBuilder}, }; use crate::types::block::{ core::basic::MaxBurnedManaAmount, protocol::{ProtocolParameters, ProtocolParametersHash}, - Error, }; #[derive(Clone, Debug, Eq, PartialEq, From, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = BlockError)] #[packable(unpack_visitor = ProtocolParameters)] -#[packable(tag_type = u8, with_error = Error::InvalidBlockBodyKind)] +#[packable(tag_type = u8, with_error = BlockError::Kind)] pub enum BlockBody { #[packable(tag = BasicBlockBody::KIND)] Basic(Box), @@ -48,25 +49,25 @@ impl From for BlockBody { } impl TryFrom for BasicBlockBodyBuilder { - type Error = Error; + type Error = BlockError; fn try_from(value: BlockBody) -> Result { if let BlockBody::Basic(block) = value { Ok((*block).into()) } else { - Err(Error::InvalidBlockBodyKind(value.kind())) + Err(BlockError::Kind(value.kind())) } } } impl TryFrom for ValidationBlockBodyBuilder { - type Error = Error; + type Error = BlockError; fn try_from(value: BlockBody) -> Result { if let BlockBody::Validation(block) = value { Ok((*block).into()) } else { - Err(Error::InvalidBlockBodyKind(value.kind())) + Err(BlockError::Kind(value.kind())) } } } @@ -203,7 +204,7 @@ pub(crate) mod dto { } impl TryFromDto for BlockBody { - type Error = Error; + type Error = BlockError; fn try_from_dto_with_params_inner( dto: BlockBodyDto, diff --git a/sdk/src/types/block/core/parent.rs b/sdk/src/types/block/core/parent.rs index d7166b7f77..ecf94fc382 100644 --- a/sdk/src/types/block/core/parent.rs +++ b/sdk/src/types/block/core/parent.rs @@ -10,7 +10,7 @@ use derive_more::Deref; use iterator_sorted::is_unique_sorted; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; -use crate::types::block::{BlockId, Error}; +use crate::types::block::{core::BlockError, BlockId}; /// A [`Block`](crate::types::block::Block)'s [`Parents`] are the [`BlockId`]s of the blocks it directly approves. /// @@ -21,7 +21,7 @@ use crate::types::block::{BlockId, Error}; #[derive(Clone, Debug, Eq, PartialEq, Deref, Packable)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[deref(forward)] -#[packable(unpack_error = Error, with = |_| Error::InvalidParentCount)] +#[packable(unpack_error = BlockError, with = |_| BlockError::InvalidParentCount)] pub struct Parents( #[packable(verify_with = verify_parents)] BoxedSlicePrefix>, ); @@ -31,7 +31,7 @@ impl Parents { pub const COUNT_RANGE: RangeInclusive = MIN..=MAX; /// Creates new [`Parents`] from a vec. - pub fn from_vec(mut inner: Vec) -> Result { + pub fn from_vec(mut inner: Vec) -> Result { inner.sort_unstable(); inner.dedup(); @@ -39,18 +39,18 @@ impl Parents { inner .into_boxed_slice() .try_into() - .map_err(|_| Error::InvalidParentCount)?, + .map_err(|_| BlockError::InvalidParentCount)?, )) } /// Creates new [`Parents`] from an ordered set. - pub fn from_set(inner: BTreeSet) -> Result { + pub fn from_set(inner: BTreeSet) -> Result { Ok(Self( inner .into_iter() .collect::>() .try_into() - .map_err(|_| Error::InvalidParentCount)?, + .map_err(|_| BlockError::InvalidParentCount)?, )) } @@ -75,9 +75,9 @@ impl Parents { } } -fn verify_parents(parents: &[BlockId]) -> Result<(), Error> { - if VERIFY && !is_unique_sorted(parents.iter().map(AsRef::as_ref)) { - Err(Error::ParentsNotUniqueSorted) +fn verify_parents(parents: &[BlockId]) -> Result<(), BlockError> { + if !is_unique_sorted(parents.iter().map(AsRef::as_ref)) { + Err(BlockError::ParentsNotUniqueSorted) } else { Ok(()) } @@ -93,13 +93,13 @@ pub(crate) fn verify_parents_sets( strong_parents: &[BlockId], weak_parents: &[BlockId], shallow_like_parents: &[BlockId], -) -> Result<(), Error> { +) -> Result<(), BlockError> { let strong_parents: BTreeSet<_> = strong_parents.iter().copied().collect(); let weak_parents: BTreeSet<_> = weak_parents.iter().copied().collect(); let shallow_like_parents: BTreeSet<_> = shallow_like_parents.iter().copied().collect(); if !weak_parents.is_disjoint(&strong_parents) || !weak_parents.is_disjoint(&shallow_like_parents) { - return Err(Error::NonDisjointParents); + return Err(BlockError::NonDisjointParents); } Ok(()) diff --git a/sdk/src/types/block/core/validation.rs b/sdk/src/types/block/core/validation.rs index 66f27ae2b2..277ed508e2 100644 --- a/sdk/src/types/block/core/validation.rs +++ b/sdk/src/types/block/core/validation.rs @@ -4,9 +4,8 @@ use packable::Packable; use crate::types::block::{ - core::{parent::verify_parents_sets, BlockBody, Parents}, - protocol::{ProtocolParameters, ProtocolParametersHash}, - Error, + core::{parent::verify_parents_sets, BlockBody, BlockError, Parents}, + protocol::{ProtocolParameters, ProtocolParametersError, ProtocolParametersHash}, }; pub type StrongParents = Parents<1, 50>; @@ -75,7 +74,7 @@ impl ValidationBlockBodyBuilder { } /// Finishes the builder into a [`ValidationBlockBody`]. - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { verify_parents_sets(&self.strong_parents, &self.weak_parents, &self.shallow_like_parents)?; Ok(ValidationBlockBody { @@ -88,7 +87,7 @@ impl ValidationBlockBodyBuilder { } /// Finishes the builder into a [`BlockBody`]. - pub fn finish_block_body(self) -> Result { + pub fn finish_block_body(self) -> Result { Ok(BlockBody::from(self.finish()?)) } } @@ -109,7 +108,7 @@ impl From for ValidationBlockBodyBuilder { /// by the Congestion Control of the IOTA 2.0 protocol and can be issued without burning Mana within the constraints of /// the allowed validator throughput. It is allowed to reference more parent blocks than a normal Basic Block Body. #[derive(Clone, Debug, Eq, PartialEq, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = BlockError)] #[packable(unpack_visitor = ProtocolParameters)] #[packable(verify_with = verify_validation_block_body)] pub struct ValidationBlockBody { @@ -160,35 +159,31 @@ impl ValidationBlockBody { } } -fn verify_protocol_parameters_hash( +fn verify_protocol_parameters_hash( hash: &ProtocolParametersHash, params: &ProtocolParameters, -) -> Result<(), Error> { - if VERIFY { - let params_hash = params.hash(); - - if hash != ¶ms_hash { - return Err(Error::InvalidProtocolParametersHash { - expected: params_hash, - actual: *hash, - }); - } +) -> Result<(), ProtocolParametersError> { + let params_hash = params.hash(); + + if hash != ¶ms_hash { + return Err(ProtocolParametersError::Hash { + expected: params_hash, + actual: *hash, + }); } Ok(()) } -fn verify_validation_block_body( +fn verify_validation_block_body( validation_block_body: &ValidationBlockBody, _: &ProtocolParameters, -) -> Result<(), Error> { - if VERIFY { - verify_parents_sets( - &validation_block_body.strong_parents, - &validation_block_body.weak_parents, - &validation_block_body.shallow_like_parents, - )?; - } +) -> Result<(), BlockError> { + verify_parents_sets( + &validation_block_body.strong_parents, + &validation_block_body.weak_parents, + &validation_block_body.shallow_like_parents, + )?; Ok(()) } @@ -200,10 +195,7 @@ pub(crate) mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::types::{ - block::{BlockId, Error}, - TryFromDto, - }; + use crate::types::{block::BlockId, TryFromDto}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -233,14 +225,14 @@ pub(crate) mod dto { } impl TryFromDto for ValidationBlockBody { - type Error = Error; + type Error = BlockError; fn try_from_dto_with_params_inner( dto: ValidationBlockBodyDto, params: Option<&ProtocolParameters>, ) -> Result { - if let Some(protocol_params) = params { - verify_protocol_parameters_hash::(&dto.protocol_parameters_hash, protocol_params)?; + if let Some(params) = params { + verify_protocol_parameters_hash(&dto.protocol_parameters_hash, params)?; } ValidationBlockBodyBuilder::new( diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index 56ee47c8da..a81c4d68fa 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -1,472 +1,18 @@ -// Copyright 2020-2021 IOTA Stiftung +// Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{ - string::{FromUtf8Error, String}, - vec::Vec, -}; -use core::{convert::Infallible, fmt}; +use core::convert::Infallible; -use bech32::primitives::hrp::Error as Bech32HrpError; -use crypto::Error as CryptoError; -use prefix_hex::Error as HexError; -use primitive_types::U256; +use derive_more::{Display, From}; -use super::slot::EpochIndex; -use crate::types::block::{ - address::{AddressCapabilityFlag, WeightedAddressCount}, - context_input::{ContextInputCount, RewardContextInputIndex}, - input::UtxoInput, - mana::ManaAllotmentCount, - output::{ - feature::{BlockIssuerKeyCount, FeatureCount}, - unlock_condition::UnlockConditionCount, - AccountId, AnchorId, ChainId, MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength, - NativeTokenCount, NftId, OutputIndex, TagFeatureLength, - }, - payload::{ - tagged_data::{TagLength, TaggedDataLength}, - InputCount, OutputCount, - }, - protocol::ProtocolParametersHash, - unlock::{UnlockCount, UnlockIndex, UnlocksCount}, -}; - -/// Error occurring when creating/parsing/validating blocks. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Display, From)] #[allow(missing_docs)] -#[non_exhaustive] -pub enum Error { - ManaAllotmentsNotUniqueSorted, - ConsumedAmountOverflow, - ConsumedManaOverflow, - ConsumedNativeTokensAmountOverflow, - ContextInputsNotUniqueSorted, - CreatedAmountOverflow, - CreatedManaOverflow, - CreatedNativeTokensAmountOverflow, - Crypto(CryptoError), - DuplicateSignatureUnlock(u16), - DuplicateUtxo(UtxoInput), - ExpirationUnlockConditionZero, - FeaturesNotUniqueSorted, - InputUnlockCountMismatch { - input_count: usize, - unlock_count: usize, - }, - InvalidAddress, - InvalidAddressKind(u8), - InvalidAccountIndex(>::Error), - InvalidAnchorIndex(>::Error), - InvalidBlockBodyKind(u8), - NonGraphicAsciiMetadataKey(Vec), - InvalidRewardInputIndex(>::Error), - InvalidStorageDepositAmount(u64), - /// Invalid transaction failure reason byte. - InvalidTransactionFailureReason(u8), - // The above is used by `Packable` to denote out-of-range values. The following denotes the actual amount. - InsufficientStorageDepositAmount { - amount: u64, - required: u64, - }, - StorageDepositReturnExceedsOutputAmount { - deposit: u64, - amount: u64, - }, - InsufficientStorageDepositReturnAmount { - deposit: u64, - required: u64, - }, - InvalidAddressWeight(u8), - InvalidMultiAddressThreshold(u16), - InvalidMultiAddressCumulativeWeight { - cumulative_weight: u16, - threshold: u16, - }, - InvalidWeightedAddressCount(>::Error), - InvalidMultiUnlockCount(>::Error), - MultiUnlockRecursion, - WeightedAddressesNotUniqueSorted, - InvalidContextInputKind(u8), - InvalidContextInputCount(>::Error), - InvalidFeatureCount(>::Error), - InvalidFeatureKind(u8), - InvalidFoundryOutputSupply { - minted: U256, - melted: U256, - max: U256, - }, - Hex(HexError), - InvalidInputKind(u8), - InvalidInputCount(>::Error), - InvalidInputOutputIndex(>::Error), - InvalidBech32Hrp(Bech32HrpError), - InvalidCapabilitiesCount(>::Error), - InvalidCapabilityByte { - index: usize, - byte: u8, - }, - InvalidBlockLength(usize), - InvalidManaValue(u64), - InvalidMetadataFeature(String), - InvalidMetadataFeatureEntryCount(>::Error), - InvalidMetadataFeatureKeyLength(>::Error), - InvalidMetadataFeatureValueLength(>::Error), - InvalidNativeTokenCount(>::Error), - InvalidNetworkName(FromUtf8Error), - InvalidManaDecayFactors, - InvalidNftIndex(>::Error), - InvalidOutputAmount(u64), - InvalidOutputCount(>::Error), - InvalidOutputKind(u8), - InvalidManaAllotmentCount(>::Error), - // TODO this would now need to be generic, not sure if possible. - // https://github.com/iotaledger/iota-sdk/issues/647 - // InvalidParentCount(>::Error), - InvalidParentCount, - InvalidPayloadKind(u8), - InvalidPayloadLength { - expected: usize, - actual: usize, - }, - InvalidProtocolParametersHash { - expected: ProtocolParametersHash, - actual: ProtocolParametersHash, - }, - InvalidBlockIssuerKeyCount(>::Error), - InvalidReferenceIndex(>::Error), - InvalidSignature, - InvalidSignatureKind(u8), - InvalidBlockIssuerKeyKind(u8), - InvalidStartEpoch(EpochIndex), - InvalidStringPrefix(>::Error), - InvalidTaggedDataLength(>::Error), - InvalidTagFeatureLength(>::Error), - InvalidTagLength(>::Error), - InvalidTokenSchemeKind(u8), - InvalidTransactionAmountSum(u128), - InvalidManaAllotmentSum { - max: u64, - sum: u128, - }, - InvalidUnlockCount(>::Error), - InvalidUnlockKind(u8), - InvalidUnlockReference(u16), - InvalidUnlockAccount(u16), - InvalidUnlockNft(u16), - InvalidUnlockAnchor(u16), - InvalidUnlockConditionCount(>::Error), - InvalidUnlockConditionKind(u8), - InvalidFoundryZeroSerialNumber, - InvalidStakedAmount, - MissingAddressUnlockCondition, - MissingGovernorUnlockCondition, - MissingStateControllerUnlockCondition, - MissingSlotIndex, - NativeTokensNotUniqueSorted, - NativeTokensNullAmount, - NativeTokensOverflow, - NetworkIdMismatch { - expected: u64, - actual: u64, - }, - NonDisjointParents, - NonZeroStateIndexOrFoundryCounter, - ParentsNotUniqueSorted, - ProtocolVersionMismatch { - expected: u8, - actual: u8, - }, - BlockIssuerKeysNotUniqueSorted, - RemainingBytesAfterBlock, - SelfControlledAnchorOutput(AnchorId), - SelfDepositAccount(AccountId), - SelfDepositNft(NftId), - SignaturePublicKeyMismatch { - expected: String, - actual: String, - }, - StorageDepositReturnOverflow, - TimelockUnlockConditionZero, - UnallowedFeature { - index: usize, - kind: u8, - }, - UnallowedUnlockCondition { - index: usize, - kind: u8, - }, - UnlockConditionsNotUniqueSorted, - UnsupportedOutputKind(u8), - // TODO use Address::kind_str when available in 2.0 ? - UnsupportedAddressKind(u8), - DuplicateOutputChain(ChainId), - InvalidField(&'static str), - NullDelegationValidatorId, - InvalidEpochDiff { - created: EpochIndex, - target: EpochIndex, - }, - TrailingCapabilityBytes, - RestrictedAddressCapability(AddressCapabilityFlag), -} +pub struct IdentifierError(pub prefix_hex::Error); #[cfg(feature = "std")] -impl std::error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::ManaAllotmentsNotUniqueSorted => write!(f, "mana allotments are not unique and/or sorted"), - Self::ConsumedAmountOverflow => write!(f, "consumed amount overflow"), - Self::ConsumedManaOverflow => write!(f, "consumed mana overflow"), - Self::ConsumedNativeTokensAmountOverflow => write!(f, "consumed native tokens amount overflow"), - Self::ContextInputsNotUniqueSorted => write!(f, "context inputs are not unique and/or sorted"), - Self::CreatedAmountOverflow => write!(f, "created amount overflow"), - Self::CreatedManaOverflow => write!(f, "created mana overflow"), - Self::CreatedNativeTokensAmountOverflow => write!(f, "created native tokens amount overflow"), - Self::Crypto(e) => write!(f, "cryptographic error: {e}"), - Self::DuplicateSignatureUnlock(index) => { - write!(f, "duplicate signature unlock at index: {index}") - } - Self::DuplicateUtxo(utxo) => write!(f, "duplicate UTXO {utxo:?} in inputs"), - Self::ExpirationUnlockConditionZero => { - write!( - f, - "expiration unlock condition with milestone index and timestamp set to 0", - ) - } - Self::FeaturesNotUniqueSorted => write!(f, "features are not unique and/or sorted"), - Self::InputUnlockCountMismatch { - input_count, - unlock_count, - } => { - write!( - f, - "input count and unlock count mismatch: {input_count} != {unlock_count}", - ) - } - Self::InvalidAddress => write!(f, "invalid address provided"), - Self::InvalidAddressKind(k) => write!(f, "invalid address kind: {k}"), - Self::InvalidAccountIndex(index) => write!(f, "invalid account index: {index}"), - Self::InvalidAnchorIndex(index) => write!(f, "invalid anchor index: {index}"), - Self::InvalidBech32Hrp(e) => write!(f, "invalid bech32 hrp: {e}"), - Self::InvalidCapabilitiesCount(e) => write!(f, "invalid capabilities count: {e}"), - Self::InvalidCapabilityByte { index, byte } => { - write!(f, "invalid capability byte at index {index}: {byte:x}") - } - Self::InvalidBlockBodyKind(k) => write!(f, "invalid block body kind: {k}"), - Self::NonGraphicAsciiMetadataKey(b) => write!(f, "non graphic ASCII key: {b:?}"), - Self::InvalidRewardInputIndex(idx) => write!(f, "invalid reward input index: {idx}"), - Self::InvalidStorageDepositAmount(amount) => { - write!(f, "invalid storage deposit amount: {amount}") - } - Self::InvalidTransactionFailureReason(reason_byte) => { - write!(f, "invalid transaction failure reason byte: {reason_byte}") - } - Self::InsufficientStorageDepositAmount { amount, required } => { - write!( - f, - "insufficient output amount for storage deposit: {amount} (should be at least {required})" - ) - } - Self::InsufficientStorageDepositReturnAmount { deposit, required } => { - write!( - f, - "the return deposit ({deposit}) must be greater than the minimum output amount ({required})" - ) - } - Self::StorageDepositReturnExceedsOutputAmount { deposit, amount } => write!( - f, - "storage deposit return of {deposit} exceeds the original output amount of {amount}" - ), - Self::InvalidContextInputCount(count) => write!(f, "invalid context input count: {count}"), - Self::InvalidAddressWeight(w) => write!(f, "invalid address weight: {w}"), - Self::InvalidMultiAddressThreshold(t) => write!(f, "invalid multi address threshold: {t}"), - Self::InvalidMultiAddressCumulativeWeight { - cumulative_weight, - threshold, - } => { - write!( - f, - "invalid multi address cumulative weight {cumulative_weight} < threshold {threshold}" - ) - } - Self::InvalidWeightedAddressCount(count) => write!(f, "invalid weighted address count: {count}"), - Self::InvalidMultiUnlockCount(count) => write!(f, "invalid multi unlock count: {count}"), - Self::MultiUnlockRecursion => write!(f, "multi unlock recursion"), - Self::WeightedAddressesNotUniqueSorted => { - write!(f, "weighted addresses are not unique and/or sorted") - } - Self::InvalidContextInputKind(k) => write!(f, "invalid context input kind: {k}"), - Self::InvalidFeatureCount(count) => write!(f, "invalid feature count: {count}"), - Self::InvalidFeatureKind(k) => write!(f, "invalid feature kind: {k}"), - Self::InvalidFoundryOutputSupply { minted, melted, max } => write!( - f, - "invalid foundry output supply: minted {minted}, melted {melted} max {max}", - ), - Self::Hex(error) => write!(f, "hex error: {error}"), - Self::InvalidInputKind(k) => write!(f, "invalid input kind: {k}"), - Self::InvalidInputCount(count) => write!(f, "invalid input count: {count}"), - Self::InvalidInputOutputIndex(index) => write!(f, "invalid input or output index: {index}"), - Self::InvalidBlockLength(length) => write!(f, "invalid block length {length}"), - Self::InvalidManaValue(mana) => write!(f, "invalid mana value: {mana}"), - Self::InvalidMetadataFeature(e) => { - write!(f, "invalid metadata feature: {e}") - } - Self::InvalidMetadataFeatureEntryCount(count) => { - write!(f, "invalid metadata feature entry count: {count}") - } - Self::InvalidMetadataFeatureKeyLength(length) => { - write!(f, "invalid metadata feature key length: {length}") - } - Self::InvalidMetadataFeatureValueLength(length) => { - write!(f, "invalid metadata feature value length: {length}") - } - Self::InvalidNativeTokenCount(count) => write!(f, "invalid native token count: {count}"), - Self::InvalidNetworkName(err) => write!(f, "invalid network name: {err}"), - Self::InvalidManaDecayFactors => write!(f, "invalid mana decay factors"), - Self::InvalidNftIndex(index) => write!(f, "invalid nft index: {index}"), - Self::InvalidOutputAmount(amount) => write!(f, "invalid output amount: {amount}"), - Self::InvalidOutputCount(count) => write!(f, "invalid output count: {count}"), - Self::InvalidOutputKind(k) => write!(f, "invalid output kind: {k}"), - Self::InvalidManaAllotmentCount(count) => write!(f, "invalid mana allotment count: {count}"), - Self::InvalidParentCount => { - write!(f, "invalid parents count") - } - Self::InvalidPayloadKind(k) => write!(f, "invalid payload kind: {k}"), - Self::InvalidPayloadLength { expected, actual } => { - write!(f, "invalid payload length: expected {expected} but got {actual}") - } - Self::InvalidProtocolParametersHash { expected, actual } => { - write!( - f, - "invalid protocol parameters hash: expected {expected} but got {actual}" - ) - } - Self::InvalidBlockIssuerKeyCount(count) => write!(f, "invalid block issuer key count: {count}"), - Self::InvalidReferenceIndex(index) => write!(f, "invalid reference index: {index}"), - Self::InvalidSignature => write!(f, "invalid signature provided"), - Self::InvalidSignatureKind(k) => write!(f, "invalid signature kind: {k}"), - Self::InvalidBlockIssuerKeyKind(k) => write!(f, "invalid block issuer key kind: {k}"), - Self::InvalidStartEpoch(index) => write!(f, "invalid start epoch: {index}"), - Self::InvalidStringPrefix(p) => write!(f, "invalid string prefix: {p}"), - Self::InvalidTaggedDataLength(length) => { - write!(f, "invalid tagged data length {length}") - } - Self::InvalidTagFeatureLength(length) => { - write!(f, "invalid tag feature length {length}") - } - Self::InvalidTagLength(length) => { - write!(f, "invalid tag length {length}") - } - Self::InvalidTokenSchemeKind(k) => write!(f, "invalid token scheme kind {k}"), - Self::InvalidTransactionAmountSum(value) => write!(f, "invalid transaction amount sum: {value}"), - Self::InvalidManaAllotmentSum { max, sum } => { - write!(f, "invalid mana allotment sum: {sum} greater than max of {max}") - } - Self::InvalidUnlockCount(count) => write!(f, "invalid unlock count: {count}"), - Self::InvalidUnlockKind(k) => write!(f, "invalid unlock kind: {k}"), - Self::InvalidUnlockReference(index) => { - write!(f, "invalid unlock reference: {index}") - } - Self::InvalidUnlockAccount(index) => { - write!(f, "invalid unlock account: {index}") - } - Self::InvalidUnlockNft(index) => { - write!(f, "invalid unlock nft: {index}") - } - Self::InvalidUnlockAnchor(index) => { - write!(f, "invalid unlock anchor: {index}") - } - Self::InvalidUnlockConditionCount(count) => write!(f, "invalid unlock condition count: {count}"), - Self::InvalidUnlockConditionKind(k) => write!(f, "invalid unlock condition kind: {k}"), - Self::InvalidFoundryZeroSerialNumber => write!(f, "invalid foundry zero serial number"), - Self::InvalidStakedAmount => write!(f, "invalid staked amount"), - Self::MissingAddressUnlockCondition => write!(f, "missing address unlock condition"), - Self::MissingGovernorUnlockCondition => write!(f, "missing governor unlock condition"), - Self::MissingStateControllerUnlockCondition => write!(f, "missing state controller unlock condition"), - Self::MissingSlotIndex => write!(f, "missing slot index"), - Self::NativeTokensNotUniqueSorted => write!(f, "native tokens are not unique and/or sorted"), - Self::NativeTokensNullAmount => write!(f, "native tokens null amount"), - Self::NativeTokensOverflow => write!(f, "native tokens overflow"), - Self::NetworkIdMismatch { expected, actual } => { - write!(f, "network ID mismatch: expected {expected} but got {actual}") - } - Self::NonDisjointParents => { - write!(f, "weak parents are not disjoint to strong or shallow like parents") - } - Self::NonZeroStateIndexOrFoundryCounter => { - write!( - f, - "non zero state index or foundry counter while account ID is all zero" - ) - } - Self::ParentsNotUniqueSorted => { - write!(f, "parents are not unique and/or sorted") - } - Self::ProtocolVersionMismatch { expected, actual } => { - write!(f, "protocol version mismatch: expected {expected} but got {actual}") - } - Self::BlockIssuerKeysNotUniqueSorted => write!(f, "block issuer keys are not unique and/or sorted"), - Self::RemainingBytesAfterBlock => { - write!(f, "remaining bytes after block") - } - Self::SelfControlledAnchorOutput(anchor_id) => { - write!(f, "self controlled anchor output, anchor ID {anchor_id}") - } - Self::SelfDepositNft(nft_id) => { - write!(f, "self deposit nft output, NFT ID {nft_id}") - } - Self::SelfDepositAccount(account_id) => { - write!(f, "self deposit account output, account ID {account_id}") - } - Self::SignaturePublicKeyMismatch { expected, actual } => { - write!(f, "signature public key mismatch: expected {expected} but got {actual}",) - } - Self::StorageDepositReturnOverflow => { - write!(f, "storage deposit return overflow",) - } - Self::TimelockUnlockConditionZero => { - write!( - f, - "timelock unlock condition with milestone index and timestamp set to 0", - ) - } - Self::UnallowedFeature { index, kind } => { - write!(f, "unallowed feature at index {index} with kind {kind}") - } - Self::UnallowedUnlockCondition { index, kind } => { - write!(f, "unallowed unlock condition at index {index} with kind {kind}") - } - Self::UnlockConditionsNotUniqueSorted => write!(f, "unlock conditions are not unique and/or sorted"), - Self::UnsupportedOutputKind(k) => write!(f, "unsupported output kind: {k}"), - Self::UnsupportedAddressKind(k) => write!(f, "unsupported address kind: {k}"), - Self::DuplicateOutputChain(chain_id) => write!(f, "duplicate output chain {chain_id}"), - Self::InvalidField(field) => write!(f, "invalid field: {field}"), - Self::NullDelegationValidatorId => write!(f, "null delegation validator ID"), - Self::InvalidEpochDiff { created, target } => { - write!(f, "invalid epoch diff: created {created}, target {target}") - } - Self::TrailingCapabilityBytes => write!(f, "capability bytes have trailing zeroes"), - Self::RestrictedAddressCapability(cap) => write!(f, "restricted address capability: {cap:?}"), - } - } -} - -impl From for Error { - fn from(error: Bech32HrpError) -> Self { - Self::InvalidBech32Hrp(error) - } -} - -impl From for Error { - fn from(error: CryptoError) -> Self { - Self::Crypto(error) - } -} +impl std::error::Error for IdentifierError {} -impl From for Error { +impl From for IdentifierError { fn from(error: Infallible) -> Self { match error {} } diff --git a/sdk/src/types/block/input/error.rs b/sdk/src/types/block/input/error.rs new file mode 100644 index 0000000000..d4b6b193bd --- /dev/null +++ b/sdk/src/types/block/input/error.rs @@ -0,0 +1,26 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use crate::types::block::{payload::InputCount, IdentifierError}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum InputError { + #[display(fmt = "invalid input kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid input count: {_0}")] + Count(>::Error), + #[from] + Identifier(IdentifierError), +} + +#[cfg(feature = "std")] +impl std::error::Error for InputError {} + +impl From for InputError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/input/mod.rs b/sdk/src/types/block/input/mod.rs index 901646bd69..92b04be69d 100644 --- a/sdk/src/types/block/input/mod.rs +++ b/sdk/src/types/block/input/mod.rs @@ -1,17 +1,15 @@ // Copyright 2020-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod error; mod utxo; use core::ops::RangeInclusive; use derive_more::From; -pub use self::utxo::UtxoInput; -use crate::types::block::{ - protocol::{WorkScore, WorkScoreParameters}, - Error, -}; +pub use self::{error::InputError, utxo::UtxoInput}; +use crate::types::block::protocol::{WorkScore, WorkScoreParameters}; /// The maximum number of inputs of a transaction. pub const INPUT_COUNT_MAX: u16 = 128; @@ -24,8 +22,8 @@ pub const INPUT_INDEX_RANGE: RangeInclusive = 0..=INPUT_INDEX_MAX; // [0..1 /// A generic input supporting different input kinds. #[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd, From, packable::Packable)] -#[packable(unpack_error = Error)] -#[packable(tag_type = u8, with_error = Error::InvalidInputKind)] +#[packable(unpack_error = InputError)] +#[packable(tag_type = u8, with_error = InputError::Kind)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] pub enum Input { /// A UTXO input. diff --git a/sdk/src/types/block/input/utxo.rs b/sdk/src/types/block/input/utxo.rs index c1e20beff2..eaed376a43 100644 --- a/sdk/src/types/block/input/utxo.rs +++ b/sdk/src/types/block/input/utxo.rs @@ -6,10 +6,10 @@ use core::str::FromStr; use derive_more::From; use crate::types::block::{ + input::InputError, output::OutputId, payload::signed_transaction::TransactionId, protocol::{WorkScore, WorkScoreParameters}, - Error, }; /// Represents an input referencing an output. @@ -38,7 +38,7 @@ impl WorkScore for UtxoInput { } impl FromStr for UtxoInput { - type Err = Error; + type Err = InputError; fn from_str(s: &str) -> Result { Ok(Self(OutputId::from_str(s)?)) diff --git a/sdk/src/types/block/macro.rs b/sdk/src/types/block/macro.rs index dd2a88ff6e..b6f78bf5c7 100644 --- a/sdk/src/types/block/macro.rs +++ b/sdk/src/types/block/macro.rs @@ -52,15 +52,15 @@ macro_rules! impl_id { } impl core::str::FromStr for $hash_name { - type Err = $crate::types::block::Error; + type Err = $crate::types::block::IdentifierError; fn from_str(s: &str) -> Result { - Ok(Self::new(prefix_hex::decode(s).map_err($crate::types::block::Error::Hex)?)) + Ok(Self::new(prefix_hex::decode(s).map_err($crate::types::block::IdentifierError)?)) } } impl TryFrom<&alloc::string::String> for $hash_name { - type Error = $crate::types::block::Error; + type Error = $crate::types::block::IdentifierError; fn try_from(s: &alloc::string::String) -> Result { core::str::FromStr::from_str(s.as_str()) @@ -68,7 +68,7 @@ macro_rules! impl_id { } impl TryFrom<&str> for $hash_name { - type Error = $crate::types::block::Error; + type Error = $crate::types::block::IdentifierError; fn try_from(s: &str) -> Result { core::str::FromStr::from_str(s) @@ -76,14 +76,14 @@ macro_rules! impl_id { } impl $crate::utils::ConvertTo<$hash_name> for &alloc::string::String { - fn convert(self) -> Result<$hash_name, $crate::types::block::Error> { - self.try_into() + fn convert(self) -> Result<$hash_name, $crate::utils::ConversionError> { + self.try_into().map_err($crate::utils::ConversionError::new) } } impl $crate::utils::ConvertTo<$hash_name> for &str { - fn convert(self) -> Result<$hash_name, $crate::types::block::Error> { - self.try_into() + fn convert(self) -> Result<$hash_name, $crate::utils::ConversionError> { + self.try_into().map_err($crate::utils::ConversionError::new) } } @@ -131,7 +131,6 @@ macro_rules! impl_id { $(#[$id_meta])* #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, packable::Packable)] - #[packable(unpack_error = $crate::types::block::Error)] #[repr(C)] $id_vis struct $id_name { pub(crate) hash: $hash_name, @@ -174,10 +173,10 @@ macro_rules! impl_id { } impl core::str::FromStr for $id_name { - type Err = $crate::types::block::Error; + type Err = $crate::types::block::IdentifierError; fn from_str(s: &str) -> Result { - Ok(Self::new(prefix_hex::decode(s).map_err($crate::types::block::Error::Hex)?)) + Ok(Self::new(prefix_hex::decode(s).map_err($crate::types::block::IdentifierError)?)) } } @@ -197,7 +196,7 @@ macro_rules! impl_id { } impl TryFrom<&alloc::string::String> for $id_name { - type Error = $crate::types::block::Error; + type Error = $crate::types::block::IdentifierError; fn try_from(s: &alloc::string::String) -> Result { core::str::FromStr::from_str(s.as_str()) @@ -205,7 +204,7 @@ macro_rules! impl_id { } impl TryFrom<&str> for $id_name { - type Error = $crate::types::block::Error; + type Error = $crate::types::block::IdentifierError; fn try_from(s: &str) -> Result { core::str::FromStr::from_str(s) @@ -213,14 +212,14 @@ macro_rules! impl_id { } impl $crate::utils::ConvertTo<$id_name> for &alloc::string::String { - fn convert(self) -> Result<$id_name, $crate::types::block::Error> { - self.try_into() + fn convert(self) -> Result<$id_name, $crate::utils::ConversionError> { + self.try_into().map_err($crate::utils::ConversionError::new) } } impl $crate::utils::ConvertTo<$id_name> for &str { - fn convert(self) -> Result<$id_name, $crate::types::block::Error> { - self.try_into() + fn convert(self) -> Result<$id_name, $crate::utils::ConversionError> { + self.try_into().map_err($crate::utils::ConversionError::new) } } @@ -396,3 +395,16 @@ macro_rules! impl_deserialize_untagged { } } } + +#[macro_export] +macro_rules! impl_from_error_via { + ($self:ident via $via:ident: $($err:ident),+$(,)?) => { + $( + impl From<$err> for $self { + fn from(error: $err) -> Self { + Self::from($via::from(error)) + } + } + )+ + }; +} diff --git a/sdk/src/types/block/mana/allotment.rs b/sdk/src/types/block/mana/allotment.rs index d191fdbd87..af46f9ff97 100644 --- a/sdk/src/types/block/mana/allotment.rs +++ b/sdk/src/types/block/mana/allotment.rs @@ -9,15 +9,15 @@ use iterator_sorted::is_unique_sorted; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; use crate::types::block::{ + mana::ManaError, output::AccountId, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - Error, }; /// An allotment of Mana which will be added upon commitment of the slot in which the containing transaction was issued, /// in the form of Block Issuance Credits to the account. #[derive(Copy, Clone, Debug, Eq, PartialEq, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = ManaError)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -31,8 +31,8 @@ pub struct ManaAllotment { } impl ManaAllotment { - pub fn new(account_id: AccountId, mana: u64) -> Result { - verify_mana::(&mana)?; + pub fn new(account_id: AccountId, mana: u64) -> Result { + verify_mana(&mana)?; Ok(Self { account_id, mana }) } @@ -64,9 +64,9 @@ impl WorkScore for ManaAllotment { } } -fn verify_mana(mana: &u64) -> Result<(), Error> { - if VERIFY && *mana == 0 { - return Err(Error::InvalidManaValue(*mana)); +fn verify_mana(mana: &u64) -> Result<(), ManaError> { + if *mana == 0 { + return Err(ManaError::Value(*mana)); } Ok(()) @@ -78,7 +78,7 @@ pub(crate) type ManaAllotmentCount = /// A list of [`ManaAllotment`]s with unique [`AccountId`]s. #[derive(Clone, Debug, Eq, PartialEq, Deref, Packable)] #[packable(unpack_visitor = ProtocolParameters)] -#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidManaAllotmentCount(p.into())))] +#[packable(unpack_error = ManaError, with = |e| e.unwrap_item_err_or_else(|p| ManaError::AllotmentCount(p.into())))] pub struct ManaAllotments( #[packable(verify_with = verify_mana_allotments)] BoxedSlicePrefix, ); @@ -92,25 +92,25 @@ impl ManaAllotments { pub const COUNT_RANGE: RangeInclusive = Self::COUNT_MIN..=Self::COUNT_MAX; // [0..128] /// Creates a new [`ManaAllotments`] from a vec. - pub fn from_vec(allotments: Vec) -> Result { + pub fn from_vec(allotments: Vec) -> Result { verify_mana_allotments_unique_sorted(&allotments)?; Ok(Self( allotments .into_boxed_slice() .try_into() - .map_err(Error::InvalidManaAllotmentCount)?, + .map_err(ManaError::AllotmentCount)?, )) } /// Creates a new [`ManaAllotments`] from an ordered set. - pub fn from_set(allotments: BTreeSet) -> Result { + pub fn from_set(allotments: BTreeSet) -> Result { Ok(Self( allotments .into_iter() .collect::>() .try_into() - .map_err(Error::InvalidManaAllotmentCount)?, + .map_err(ManaError::AllotmentCount)?, )) } @@ -121,23 +121,18 @@ impl ManaAllotments { } } -fn verify_mana_allotments( - allotments: &[ManaAllotment], - protocol_params: &ProtocolParameters, -) -> Result<(), Error> { - if VERIFY { - verify_mana_allotments_unique_sorted(allotments)?; - verify_mana_allotments_sum(allotments, protocol_params)?; - } +fn verify_mana_allotments(allotments: &[ManaAllotment], protocol_params: &ProtocolParameters) -> Result<(), ManaError> { + verify_mana_allotments_unique_sorted(allotments)?; + verify_mana_allotments_sum(allotments, protocol_params)?; Ok(()) } fn verify_mana_allotments_unique_sorted<'a>( allotments: impl IntoIterator, -) -> Result<(), Error> { +) -> Result<(), ManaError> { if !is_unique_sorted(allotments.into_iter()) { - return Err(Error::ManaAllotmentsNotUniqueSorted); + return Err(ManaError::AllotmentsNotUniqueSorted); } Ok(()) } @@ -145,18 +140,18 @@ fn verify_mana_allotments_unique_sorted<'a>( pub(crate) fn verify_mana_allotments_sum<'a>( allotments: impl IntoIterator, protocol_params: &ProtocolParameters, -) -> Result<(), Error> { +) -> Result<(), ManaError> { let mut mana_sum: u64 = 0; let max_mana = protocol_params.mana_parameters().max_mana(); for ManaAllotment { mana, .. } in allotments { - mana_sum = mana_sum.checked_add(*mana).ok_or(Error::InvalidManaAllotmentSum { + mana_sum = mana_sum.checked_add(*mana).ok_or(ManaError::AllotmentSum { sum: mana_sum as u128 + *mana as u128, max: max_mana, })?; if mana_sum > max_mana { - return Err(Error::InvalidManaAllotmentSum { + return Err(ManaError::AllotmentSum { sum: mana_sum as u128, max: max_mana, }); @@ -167,7 +162,7 @@ pub(crate) fn verify_mana_allotments_sum<'a>( } impl TryFrom> for ManaAllotments { - type Error = Error; + type Error = ManaError; #[inline(always)] fn try_from(allotments: Vec) -> Result { @@ -176,7 +171,7 @@ impl TryFrom> for ManaAllotments { } impl TryFrom> for ManaAllotments { - type Error = Error; + type Error = ManaError; #[inline(always)] fn try_from(allotments: BTreeSet) -> Result { diff --git a/sdk/src/types/block/mana/error.rs b/sdk/src/types/block/mana/error.rs new file mode 100644 index 0000000000..b4d511d24e --- /dev/null +++ b/sdk/src/types/block/mana/error.rs @@ -0,0 +1,30 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use crate::types::block::{mana::allotment::ManaAllotmentCount, slot::EpochIndex}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display)] +#[allow(missing_docs)] +pub enum ManaError { + #[display(fmt = "invalid mana value: {_0}")] + Value(u64), + #[display(fmt = "invalid mana allotment count: {_0}")] + AllotmentCount(>::Error), + #[display(fmt = "invalid mana allotment sum: {sum} greater than max of {max}")] + AllotmentSum { max: u64, sum: u128 }, + #[display(fmt = "mana allotments are not unique and/or sorted")] + AllotmentsNotUniqueSorted, + #[display(fmt = "invalid epoch diff: created {created}, target {target}")] + EpochDiff { created: EpochIndex, target: EpochIndex }, +} + +#[cfg(feature = "std")] +impl std::error::Error for ManaError {} + +impl From for ManaError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/mana/mod.rs b/sdk/src/types/block/mana/mod.rs index a4f22ba61a..58cefb2631 100644 --- a/sdk/src/types/block/mana/mod.rs +++ b/sdk/src/types/block/mana/mod.rs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 mod allotment; +mod error; mod parameters; mod rewards; -pub(crate) use self::allotment::{verify_mana_allotments_sum, ManaAllotmentCount}; +pub(crate) use self::allotment::verify_mana_allotments_sum; pub use self::{ allotment::{ManaAllotment, ManaAllotments}, + error::ManaError, parameters::ManaParameters, rewards::RewardsParameters, }; diff --git a/sdk/src/types/block/mana/parameters.rs b/sdk/src/types/block/mana/parameters.rs index f08af0fd9c..10fcb8edb6 100644 --- a/sdk/src/types/block/mana/parameters.rs +++ b/sdk/src/types/block/mana/parameters.rs @@ -5,9 +5,9 @@ use getset::CopyGetters; use packable::{prefix::BoxedSlicePrefix, Packable}; use crate::types::block::{ - protocol::ProtocolParameters, + mana::ManaError, + protocol::{ProtocolParameters, ProtocolParametersError}, slot::{EpochIndex, SlotIndex}, - Error, }; #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] @@ -16,7 +16,7 @@ use crate::types::block::{ derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] -#[packable(unpack_error = Error)] +#[packable(unpack_error = ProtocolParametersError)] #[getset(get_copy = "pub")] pub struct ManaParameters { /// The number of bits used to represent Mana. @@ -28,7 +28,7 @@ pub struct ManaParameters { pub(crate) generation_rate_exponent: u8, /// A lookup table of epoch index diff to mana decay factor. /// The actual decay factor is given by decay_factors\[epoch_diff\] * 2^(-decay_factors_exponent). - #[packable(unpack_error_with = |_| Error::InvalidManaDecayFactors)] + #[packable(unpack_error_with = |_| ProtocolParametersError::ManaDecayFactors)] #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::boxed_slice_prefix"))] #[getset(skip)] pub(crate) decay_factors: BoxedSlicePrefix, @@ -98,22 +98,6 @@ impl ManaParameters { } } -impl Default for ManaParameters { - fn default() -> Self { - // TODO: use actual values - Self { - bits_count: 63, - generation_rate: Default::default(), - generation_rate_exponent: Default::default(), - decay_factors: Default::default(), - decay_factors_exponent: Default::default(), - decay_factor_epochs_sum: Default::default(), - decay_factor_epochs_sum_exponent: Default::default(), - annual_decay_factor_percentage: Default::default(), - } - } -} - impl ProtocolParameters { /// Applies mana decay to the given mana. pub fn mana_with_decay( @@ -121,14 +105,14 @@ impl ProtocolParameters { mana: u64, slot_index_created: impl Into, slot_index_target: impl Into, - ) -> Result { + ) -> Result { let (epoch_index_created, epoch_index_target) = ( self.epoch_index_of(slot_index_created), self.epoch_index_of(slot_index_target), ); if epoch_index_created > epoch_index_target { - return Err(Error::InvalidEpochDiff { + return Err(ManaError::EpochDiff { created: epoch_index_created, target: epoch_index_target, }); @@ -145,11 +129,11 @@ impl ProtocolParameters { reward: u64, reward_epoch: impl Into, claimed_epoch: impl Into, - ) -> Result { + ) -> Result { let (reward_epoch, claimed_epoch) = (reward_epoch.into(), claimed_epoch.into()); if reward_epoch > claimed_epoch { - return Err(Error::InvalidEpochDiff { + return Err(ManaError::EpochDiff { created: reward_epoch, target: claimed_epoch, }); @@ -165,7 +149,7 @@ impl ProtocolParameters { amount: u64, slot_index_created: impl Into, slot_index_target: impl Into, - ) -> Result { + ) -> Result { let (slot_index_created, slot_index_target) = (slot_index_created.into(), slot_index_target.into()); let (epoch_index_created, epoch_index_target) = ( self.epoch_index_of(slot_index_created), @@ -173,7 +157,7 @@ impl ProtocolParameters { ); if epoch_index_created > epoch_index_target { - return Err(Error::InvalidEpochDiff { + return Err(ManaError::EpochDiff { created: epoch_index_created, target: epoch_index_target, }); @@ -219,65 +203,23 @@ const fn fixed_point_multiply(value: u64, mult_factor: u32, shift_factor: u8) -> ((value as u128 * mult_factor as u128) >> shift_factor) as u64 } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod test { use pretty_assertions::assert_eq; use super::*; + use crate::types::block::protocol::iota_mainnet_protocol_parameters; // Tests from https://github.com/iotaledger/iota.go/blob/develop/mana_decay_provider_test.go fn params() -> &'static ProtocolParameters { - use once_cell::sync::Lazy; - static PARAMS: Lazy = Lazy::new(|| { - let mut params = ProtocolParameters { - genesis_slot: 0, - genesis_unix_timestamp: time::OffsetDateTime::now_utc().unix_timestamp() as _, - slots_per_epoch_exponent: 13, - slot_duration_in_seconds: 10, - token_supply: 1813620509061365, - mana_parameters: ManaParameters { - bits_count: 63, - generation_rate: 1, - generation_rate_exponent: 17, - decay_factors_exponent: 32, - decay_factor_epochs_sum_exponent: 21, - annual_decay_factor_percentage: 70, - ..Default::default() - }, - ..Default::default() - }; - params.mana_parameters.decay_factors = { - let epochs_in_table = (u16::MAX as usize).min(params.epochs_per_year().floor() as usize); - let decay_per_epoch = params.decay_per_epoch(); - (1..=epochs_in_table) - .map(|epoch| { - (decay_per_epoch.powi(epoch as _) - * 2f64.powi(params.mana_parameters().decay_factors_exponent() as _)) - .floor() as u32 - }) - .collect::>() - } - .try_into() - .unwrap(); - params.mana_parameters.decay_factor_epochs_sum = { - let delta = params.epochs_per_year().recip(); - let annual_decay_factor = params.mana_parameters().annual_decay_factor(); - (annual_decay_factor.powf(delta) / (1.0 - annual_decay_factor.powf(delta)) - * (2f64.powi(params.mana_parameters().decay_factor_epochs_sum_exponent() as _))) - .floor() as _ - }; - params - }); - &PARAMS + iota_mainnet_protocol_parameters() } #[test] fn mana_decay_no_factors() { - let mana_parameters = ManaParameters { - decay_factors: Box::<[_]>::default().try_into().unwrap(), - ..Default::default() - }; + let mut mana_parameters = params().mana_parameters().clone(); + mana_parameters.decay_factors = Box::<[_]>::default().try_into().unwrap(); assert_eq!(mana_parameters.decay(100, 100), 100); } @@ -286,7 +228,7 @@ mod test { stored_mana: u64, created_slot: SlotIndex, target_slot: SlotIndex, - err: Option, + err: Option, } #[test] @@ -311,7 +253,7 @@ mod test { stored_mana: 0, created_slot: params().first_slot_of(2), target_slot: params().first_slot_of(1), - err: Some(Error::InvalidEpochDiff { + err: Some(ManaError::EpochDiff { created: 2.into(), target: 1.into(), }), @@ -373,7 +315,7 @@ mod test { created_slot: SlotIndex, target_slot: SlotIndex, potential_mana: Option, - err: Option, + err: Option, } #[test] @@ -401,7 +343,7 @@ mod test { created_slot: params().first_slot_of(2), target_slot: params().first_slot_of(1), potential_mana: None, - err: Some(Error::InvalidEpochDiff { + err: Some(ManaError::EpochDiff { created: 2.into(), target: 1.into(), }), diff --git a/sdk/src/types/block/mana/rewards.rs b/sdk/src/types/block/mana/rewards.rs index 516d4aa3e0..ab4ec48247 100644 --- a/sdk/src/types/block/mana/rewards.rs +++ b/sdk/src/types/block/mana/rewards.rs @@ -4,7 +4,7 @@ use getset::CopyGetters; use packable::Packable; -use crate::types::block::{slot::EpochIndex, Error}; +use crate::types::block::mana::ManaError; #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] #[cfg_attr( @@ -12,37 +12,23 @@ use crate::types::block::{slot::EpochIndex, Error}; derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] -#[packable(unpack_error = Error)] +#[packable(unpack_error = ManaError)] #[getset(get_copy = "pub")] pub struct RewardsParameters { /// Used for shift operation during calculation of profit margin. - profit_margin_exponent: u8, + pub(crate) profit_margin_exponent: u8, /// The length of the bootstrapping phase in epochs. - bootstrapping_duration: EpochIndex, + pub(crate) bootstrapping_duration: u32, /// The ratio of the final rewards rate to the generation rate of Mana. - reward_to_generation_ratio: u8, + pub(crate) reward_to_generation_ratio: u8, /// The rate of Mana rewards at the start of the bootstrapping phase. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - initial_target_rewards_rate: u64, + pub(crate) initial_target_rewards_rate: u64, /// The rate of Mana rewards after the bootstrapping phase. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - final_target_rewards_rate: u64, + pub(crate) final_target_rewards_rate: u64, /// The exponent used for shifting operation during the pool rewards calculations. - pool_coefficient_exponent: u8, + pub(crate) pool_coefficient_exponent: u8, // The number of epochs for which rewards are retained. - retention_period: u16, -} - -impl Default for RewardsParameters { - fn default() -> Self { - Self { - profit_margin_exponent: 8, - bootstrapping_duration: EpochIndex(1079), - reward_to_generation_ratio: 2, - initial_target_rewards_rate: 616067521149261, - final_target_rewards_rate: 226702563632670, - pool_coefficient_exponent: 11, - retention_period: 384, - } - } + pub(crate) retention_period: u16, } diff --git a/sdk/src/types/block/mod.rs b/sdk/src/types/block/mod.rs index 98fbd33edf..f71c9bc2e8 100644 --- a/sdk/src/types/block/mod.rs +++ b/sdk/src/types/block/mod.rs @@ -43,8 +43,8 @@ pub mod unlock; pub use self::core::dto::{BlockBodyDto, BlockDto, UnsignedBlockDto}; pub use self::{ block_id::{BlockHash, BlockId}, - core::{Block, BlockBody, UnsignedBlock}, - error::Error, + core::{Block, BlockBody, BlockError, UnsignedBlock}, + error::IdentifierError, }; pub const PROTOCOL_VERSION: u8 = 3; diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 27bfa83597..ca0ea23646 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -3,7 +3,6 @@ use alloc::collections::BTreeSet; -use hashbrown::HashMap; use packable::{ error::{UnpackError, UnpackErrorExt}, packer::Packer, @@ -14,16 +13,16 @@ use packable::{ use crate::types::block::{ address::{AccountAddress, Address}, output::{ - feature::{verify_allowed_features, Feature, FeatureFlags, Features}, + feature::{verify_allowed_features, Feature, FeatureError, FeatureFlags, Features}, unlock_condition::{ verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, - ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, + ChainId, DecayedMana, MinimumOutputAmount, Output, OutputBuilderAmount, OutputError, OutputId, StorageScore, + StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::TransactionFailureReason, - Error, + slot::SlotIndex, }; crate::impl_id!( @@ -71,6 +70,11 @@ impl AccountOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount), account_id) } + /// Creates an [`AccountOutputBuilder`] with a provided amount, unless it is below the minimum. + pub fn new_with_amount_or_minimum(amount: u64, account_id: AccountId, params: StorageScoreParameters) -> Self { + Self::new(OutputBuilderAmount::AmountOrMinimum(amount, params), account_id) + } + /// Creates an [`AccountOutputBuilder`] with provided storage score parameters. /// The amount will be set to the minimum required amount of the resulting output. pub fn new_with_minimum_amount(params: StorageScoreParameters, account_id: AccountId) -> Self { @@ -96,6 +100,13 @@ impl AccountOutputBuilder { self } + /// Sets the amount to the provided value, unless it is below the minimum. + #[inline(always)] + pub fn with_amount_or_minimum(mut self, amount: u64, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::AmountOrMinimum(amount, params); + self + } + /// Sets the amount to the minimum required amount. #[inline(always)] pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { @@ -209,7 +220,7 @@ impl AccountOutputBuilder { } /// - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { let foundry_counter = self.foundry_counter.unwrap_or(0); verify_index_counter(&self.account_id, foundry_counter)?; @@ -244,16 +255,17 @@ impl AccountOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, + OutputBuilderAmount::AmountOrMinimum(amount, params) => output.minimum_amount(params).max(amount), OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; - verify_staked_amount(output.amount, &output.features)?; + verify_staking(output.amount, &output.features)?; Ok(output) } /// Finishes the [`AccountOutputBuilder`] into an [`Output`]. - pub fn finish_output(self) -> Result { + pub fn finish_output(self) -> Result { Ok(Output::Account(self.finish()?)) } } @@ -395,59 +407,38 @@ impl AccountOutput { self.features().staking().is_some() && next_state.map_or(true, |o| o.features().staking().is_none()) } - // Transition, just without full SemanticValidationContext - pub(crate) fn transition_inner( - current_state: &Self, - next_state: &Self, - input_chains: &HashMap, - outputs: &[Output], - ) -> Result<(), TransactionFailureReason> { - if current_state.immutable_features != next_state.immutable_features { - return Err(TransactionFailureReason::ChainOutputImmutableFeaturesChanged); - } - - // TODO update when TIP is updated - // // Governance transition. - // if current_state.amount != next_state.amount - // || current_state.foundry_counter != next_state.foundry_counter - // { - // return Err(StateTransitionError::MutatedFieldWithoutRights); - // } - - // // State transition. - // if current_state.features.metadata() != next_state.features.metadata() { - // return Err(StateTransitionError::MutatedFieldWithoutRights); - // } - - let created_foundries = outputs.iter().filter_map(|output| { - if let Output::Foundry(foundry) = output { - if foundry.account_address().account_id() == &next_state.account_id - && !input_chains.contains_key(&foundry.chain_id()) - { - Some(foundry) - } else { - None - } - } else { - None - } - }); - - let mut created_foundries_count = 0; - - for foundry in created_foundries { - created_foundries_count += 1; - - if foundry.serial_number() != current_state.foundry_counter + created_foundries_count { - return Err(TransactionFailureReason::FoundrySerialInvalid); - } - } - - if current_state.foundry_counter + created_foundries_count != next_state.foundry_counter { - return Err(TransactionFailureReason::AccountInvalidFoundryCounter); - } - - Ok(()) + /// Returns all the mana held by the output, which is potential + stored, all decayed. + pub fn available_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let decayed_mana = self.decayed_mana(protocol_parameters, creation_index, target_index)?; + + decayed_mana + .stored + .checked_add(decayed_mana.potential) + .ok_or(OutputError::ConsumedManaOverflow) + } + + /// Returns the decayed stored and potential mana of the output. + pub fn decayed_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + let generation_amount = self.amount().saturating_sub(min_deposit); + let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?; + let potential_mana = + protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; + + Ok(DecayedMana { + stored: stored_mana, + potential: potential_mana, + }) } } @@ -474,7 +465,7 @@ impl WorkScore for AccountOutput { impl MinimumOutputAmount for AccountOutput {} impl Packable for AccountOutput { - type UnpackError = Error; + type UnpackError = OutputError; type UnpackVisitor = ProtocolParameters; fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -489,42 +480,46 @@ impl Packable for AccountOutput { Ok(()) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { - let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let amount = u64::unpack_inner(unpacker, visitor).coerce()?; - let mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let mana = u64::unpack_inner(unpacker, visitor).coerce()?; - let account_id = AccountId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let account_id = AccountId::unpack_inner(unpacker, visitor).coerce()?; - let foundry_counter = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let foundry_counter = u32::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_index_counter(&account_id, foundry_counter).map_err(UnpackError::Packable)?; } - let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?; + let unlock_conditions = UnlockConditions::unpack(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_unlock_conditions(&unlock_conditions, &account_id).map_err(UnpackError::Packable)?; } - let features = Features::unpack::<_, VERIFY>(unpacker, &())?; + let features = Features::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) - .map_err(UnpackError::Packable)?; - verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; - verify_staked_amount(amount, &features).map_err(UnpackError::Packable)?; + .map_err(UnpackError::Packable) + .coerce()?; + verify_allowed_features(&features, Self::ALLOWED_FEATURES) + .map_err(UnpackError::Packable) + .coerce()?; + verify_staking(amount, &features).map_err(UnpackError::Packable)?; } - let immutable_features = Features::unpack::<_, VERIFY>(unpacker, &())?; + let immutable_features = Features::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_allowed_features(&immutable_features, Self::ALLOWED_IMMUTABLE_FEATURES) - .map_err(UnpackError::Packable)?; + .map_err(UnpackError::Packable) + .coerce()?; } Ok(Self { @@ -540,32 +535,38 @@ impl Packable for AccountOutput { } #[inline] -fn verify_index_counter(account_id: &AccountId, foundry_counter: u32) -> Result<(), Error> { +fn verify_index_counter(account_id: &AccountId, foundry_counter: u32) -> Result<(), OutputError> { if account_id.is_null() && foundry_counter != 0 { - Err(Error::NonZeroStateIndexOrFoundryCounter) + Err(OutputError::NonZeroStateIndexOrFoundryCounter) } else { Ok(()) } } -fn verify_unlock_conditions(unlock_conditions: &UnlockConditions, account_id: &AccountId) -> Result<(), Error> { +fn verify_unlock_conditions(unlock_conditions: &UnlockConditions, account_id: &AccountId) -> Result<(), OutputError> { if let Some(unlock_condition) = unlock_conditions.address() { if let Address::Account(account_address) = unlock_condition.address() { if !account_id.is_null() && account_address.account_id() == account_id { - return Err(Error::SelfDepositAccount(*account_id)); + return Err(OutputError::SelfDepositAccount(*account_id)); } } } else { - return Err(Error::MissingAddressUnlockCondition); + return Err(OutputError::MissingAddressUnlockCondition); } - verify_allowed_unlock_conditions(unlock_conditions, AccountOutput::ALLOWED_UNLOCK_CONDITIONS) + Ok(verify_allowed_unlock_conditions( + unlock_conditions, + AccountOutput::ALLOWED_UNLOCK_CONDITIONS, + )?) } -fn verify_staked_amount(amount: u64, features: &Features) -> Result<(), Error> { +fn verify_staking(amount: u64, features: &Features) -> Result<(), OutputError> { if let Some(staking) = features.staking() { + if features.block_issuer().is_none() { + return Err(FeatureError::StakingBlockIssuerMissing.into()); + } if amount < staking.staked_amount() { - return Err(Error::InvalidStakedAmount); + return Err(OutputError::InvalidStakedAmount); } } @@ -579,10 +580,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::block::{output::unlock_condition::UnlockCondition, Error}, - utils::serde::string, - }; + use crate::{types::block::output::unlock_condition::UnlockCondition, utils::serde::string}; /// Describes an account in the ledger that can be controlled by the state and governance controllers. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -619,7 +617,7 @@ mod dto { } impl TryFrom for AccountOutput { - type Error = Error; + type Error = OutputError; fn try_from(dto: AccountOutputDto) -> Result { let mut builder = AccountOutputBuilder::new_with_amount(dto.amount, dto.account_id) @@ -639,18 +637,19 @@ mod dto { crate::impl_serde_typed_dto!(AccountOutput, AccountOutputDto, "account output"); } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod tests { use pretty_assertions::assert_eq; use super::*; use crate::types::block::{ - output::account::dto::AccountOutputDto, protocol::protocol_parameters, rand::output::rand_account_output, + output::account::dto::AccountOutputDto, protocol::iota_mainnet_protocol_parameters, + rand::output::rand_account_output, }; #[test] fn to_from_dto() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let account_output = rand_account_output(protocol_parameters.token_supply()); let dto = AccountOutputDto::from(&account_output); let output = Output::Account(AccountOutput::try_from(dto).unwrap()); diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index 3df3d1711c..9c59ef5b45 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -3,7 +3,6 @@ use alloc::collections::BTreeSet; -use hashbrown::HashMap; use packable::{ error::{UnpackError, UnpackErrorExt}, packer::Packer, @@ -19,12 +18,13 @@ use crate::types::block::{ verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, - ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, + ChainId, DecayedMana, MinimumOutputAmount, Output, OutputBuilderAmount, OutputError, OutputId, StorageScore, + StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, semantic::{SemanticValidationContext, TransactionFailureReason}, + slot::SlotIndex, unlock::Unlock, - Error, }; crate::impl_id!( @@ -103,6 +103,11 @@ impl AnchorOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount), anchor_id) } + /// Creates an [`AnchorOutputBuilder`] with a provided amount, unless it is below the minimum. + pub fn new_with_amount_or_minimum(amount: u64, anchor_id: AnchorId, params: StorageScoreParameters) -> Self { + Self::new(OutputBuilderAmount::AmountOrMinimum(amount, params), anchor_id) + } + /// Creates an [`AnchorOutputBuilder`] with provided storage score parameters. /// The amount will be set to the minimum required amount of the resulting output. #[inline(always)] @@ -129,6 +134,13 @@ impl AnchorOutputBuilder { self } + /// Sets the amount to the provided value, unless it is below the minimum. + #[inline(always)] + pub fn with_amount_or_minimum(mut self, amount: u64, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::AmountOrMinimum(amount, params); + self + } + /// Sets the amount to the minimum required amount. #[inline(always)] pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { @@ -242,7 +254,7 @@ impl AnchorOutputBuilder { } /// - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { verify_index_counter(&self.anchor_id, self.state_index)?; let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; @@ -275,6 +287,7 @@ impl AnchorOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, + OutputBuilderAmount::AmountOrMinimum(amount, params) => output.minimum_amount(params).max(amount), OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; @@ -282,7 +295,7 @@ impl AnchorOutputBuilder { } /// Finishes the [`AnchorOutputBuilder`] into an [`Output`]. - pub fn finish_output(self) -> Result { + pub fn finish_output(self) -> Result { Ok(Output::Anchor(self.finish()?)) } } @@ -451,38 +464,38 @@ impl AnchorOutput { Ok(()) } - // Transition, just without full ValidationContext - pub(crate) fn transition_inner( - current_state: &Self, - next_state: &Self, - _input_chains: &HashMap, - _outputs: &[Output], - ) -> Result<(), TransactionFailureReason> { - if current_state.immutable_features != next_state.immutable_features { - return Err(TransactionFailureReason::ChainOutputImmutableFeaturesChanged); - } + /// Returns all the mana held by the output, which is potential + stored, all decayed. + pub fn available_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let decayed_mana = self.decayed_mana(protocol_parameters, creation_index, target_index)?; - if next_state.state_index == current_state.state_index + 1 { - // State transition. - if current_state.state_controller_address() != next_state.state_controller_address() - || current_state.governor_address() != next_state.governor_address() - || current_state.features.metadata() != next_state.features.metadata() - { - return Err(TransactionFailureReason::AnchorInvalidStateTransition); - } - } else if next_state.state_index == current_state.state_index { - // Governance transition. - if current_state.amount != next_state.amount - // TODO https://github.com/iotaledger/iota-sdk/issues/1650 - // || current_state.state_metadata != next_state.state_metadata - { - return Err(TransactionFailureReason::AnchorInvalidGovernanceTransition); - } - } else { - return Err(TransactionFailureReason::AnchorInvalidStateTransition); - } + decayed_mana + .stored + .checked_add(decayed_mana.potential) + .ok_or(OutputError::ConsumedManaOverflow) + } - Ok(()) + /// Returns the decayed stored and potential mana of the output. + pub fn decayed_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + let generation_amount = self.amount().saturating_sub(min_deposit); + let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?; + let potential_mana = + protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; + + Ok(DecayedMana { + stored: stored_mana, + potential: potential_mana, + }) } } @@ -509,7 +522,7 @@ impl WorkScore for AnchorOutput { impl MinimumOutputAmount for AnchorOutput {} impl Packable for AnchorOutput { - type UnpackError = Error; + type UnpackError = OutputError; type UnpackVisitor = ProtocolParameters; fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -524,40 +537,44 @@ impl Packable for AnchorOutput { Ok(()) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { - let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let amount = u64::unpack_inner(unpacker, visitor).coerce()?; - let mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let mana = u64::unpack_inner(unpacker, visitor).coerce()?; - let anchor_id = AnchorId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let state_index = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let anchor_id = AnchorId::unpack_inner(unpacker, visitor).coerce()?; + let state_index = u32::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_index_counter(&anchor_id, state_index).map_err(UnpackError::Packable)?; } - let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?; + let unlock_conditions = UnlockConditions::unpack(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_unlock_conditions(&unlock_conditions, &anchor_id).map_err(UnpackError::Packable)?; } - let features = Features::unpack::<_, VERIFY>(unpacker, &())?; + let features = Features::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) - .map_err(UnpackError::Packable)?; - verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; + .map_err(UnpackError::Packable) + .coerce()?; + verify_allowed_features(&features, Self::ALLOWED_FEATURES) + .map_err(UnpackError::Packable) + .coerce()?; } - let immutable_features = Features::unpack::<_, VERIFY>(unpacker, &())?; + let immutable_features = Features::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_allowed_features(&immutable_features, Self::ALLOWED_IMMUTABLE_FEATURES) - .map_err(UnpackError::Packable)?; + .map_err(UnpackError::Packable) + .coerce()?; } Ok(Self { @@ -573,36 +590,39 @@ impl Packable for AnchorOutput { } #[inline] -fn verify_index_counter(anchor_id: &AnchorId, state_index: u32) -> Result<(), Error> { +fn verify_index_counter(anchor_id: &AnchorId, state_index: u32) -> Result<(), OutputError> { if anchor_id.is_null() && state_index != 0 { - Err(Error::NonZeroStateIndexOrFoundryCounter) + Err(OutputError::NonZeroStateIndexOrFoundryCounter) } else { Ok(()) } } -fn verify_unlock_conditions(unlock_conditions: &UnlockConditions, anchor_id: &AnchorId) -> Result<(), Error> { +fn verify_unlock_conditions(unlock_conditions: &UnlockConditions, anchor_id: &AnchorId) -> Result<(), OutputError> { if let Some(unlock_condition) = unlock_conditions.state_controller_address() { if let Address::Anchor(anchor_address) = unlock_condition.address() { if anchor_address.anchor_id() == anchor_id { - return Err(Error::SelfControlledAnchorOutput(*anchor_id)); + return Err(OutputError::SelfControlledAnchorOutput(*anchor_id)); } } } else { - return Err(Error::MissingStateControllerUnlockCondition); + return Err(OutputError::MissingStateControllerUnlockCondition); } if let Some(unlock_condition) = unlock_conditions.governor_address() { if let Address::Anchor(anchor_address) = unlock_condition.address() { if anchor_address.anchor_id() == anchor_id { - return Err(Error::SelfControlledAnchorOutput(*anchor_id)); + return Err(OutputError::SelfControlledAnchorOutput(*anchor_id)); } } } else { - return Err(Error::MissingGovernorUnlockCondition); + return Err(OutputError::MissingGovernorUnlockCondition); } - verify_allowed_unlock_conditions(unlock_conditions, AnchorOutput::ALLOWED_UNLOCK_CONDITIONS) + Ok(verify_allowed_unlock_conditions( + unlock_conditions, + AnchorOutput::ALLOWED_UNLOCK_CONDITIONS, + )?) } #[cfg(feature = "serde")] @@ -612,10 +632,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::block::{output::unlock_condition::UnlockCondition, Error}, - utils::serde::string, - }; + use crate::{types::block::output::unlock_condition::UnlockCondition, utils::serde::string}; /// Describes an anchor in the ledger that can be controlled by the state and governance controllers. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -652,7 +669,7 @@ mod dto { } impl TryFrom for AnchorOutput { - type Error = Error; + type Error = OutputError; fn try_from(dto: AnchorOutputDto) -> Result { let mut builder = AnchorOutputBuilder::new_with_amount(dto.amount, dto.anchor_id) @@ -672,16 +689,17 @@ mod dto { crate::impl_serde_typed_dto!(AnchorOutput, AnchorOutputDto, "anchor output"); } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod tests { use super::*; use crate::types::block::{ - output::anchor::dto::AnchorOutputDto, protocol::protocol_parameters, rand::output::rand_anchor_output, + output::anchor::dto::AnchorOutputDto, protocol::iota_mainnet_protocol_parameters, + rand::output::rand_anchor_output, }; #[test] fn to_from_dto() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let anchor_output = rand_anchor_output(protocol_parameters.token_supply()); let dto = AnchorOutputDto::from(&anchor_output); let output = Output::Anchor(AnchorOutput::try_from(dto).unwrap()); diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 41f81ac911..ab2f6b94e4 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -13,10 +13,11 @@ use crate::types::block::{ verify_allowed_unlock_conditions, verify_restricted_addresses, AddressUnlockCondition, StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, - MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, StorageScore, StorageScoreParameters, + DecayedMana, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, OutputError, StorageScore, + StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - Error, + slot::SlotIndex, }; /// Builder for a [`BasicOutput`]. @@ -36,6 +37,11 @@ impl BasicOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount)) } + /// Creates a [`BasicOutputBuilder`] with a provided amount, unless it is below the minimum. + pub fn new_with_amount_or_minimum(amount: u64, params: StorageScoreParameters) -> Self { + Self::new(OutputBuilderAmount::AmountOrMinimum(amount, params)) + } + /// Creates an [`BasicOutputBuilder`] with provided storage score parameters. /// The amount will be set to the minimum required amount of the resulting output. #[inline(always)] @@ -59,6 +65,13 @@ impl BasicOutputBuilder { self } + /// Sets the amount to the provided value, unless it is below the minimum. + #[inline(always)] + pub fn with_amount_or_minimum(mut self, amount: u64, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::AmountOrMinimum(amount, params); + self + } + /// Sets the amount to the minimum required amount. #[inline(always)] pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { @@ -143,7 +156,7 @@ impl BasicOutputBuilder { mut self, return_address: impl Into
, params: StorageScoreParameters, - ) -> Result { + ) -> Result { Ok(match self.amount { OutputBuilderAmount::Amount(amount) => { let return_address = return_address.into(); @@ -180,15 +193,16 @@ impl BasicOutputBuilder { self } } + OutputBuilderAmount::AmountOrMinimum(_, _) => self, OutputBuilderAmount::MinimumAmount(_) => self, }) } /// - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; - verify_unlock_conditions::(&unlock_conditions)?; + verify_unlock_conditions(&unlock_conditions)?; let features = Features::from_set(self.features)?; @@ -198,7 +212,7 @@ impl BasicOutputBuilder { features.native_token(), self.mana, )?; - verify_features::(&features)?; + verify_features(&features)?; let mut output = BasicOutput { amount: 0, @@ -209,6 +223,7 @@ impl BasicOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, + OutputBuilderAmount::AmountOrMinimum(amount, params) => output.minimum_amount(params).max(amount), OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; @@ -216,7 +231,7 @@ impl BasicOutputBuilder { } /// Finishes the [`BasicOutputBuilder`] into an [`Output`]. - pub fn finish_output(self) -> Result { + pub fn finish_output(self) -> Result { Ok(Output::Basic(self.finish()?)) } } @@ -234,7 +249,7 @@ impl From<&BasicOutput> for BasicOutputBuilder { /// Describes a basic output with optional features. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = OutputError)] #[packable(unpack_visitor = ProtocolParameters)] #[packable(verify_with = verify_basic_output)] pub struct BasicOutput { @@ -348,6 +363,40 @@ impl BasicOutput { .unwrap() .amount() } + + /// Returns all the mana held by the output, which is potential + stored, all decayed. + pub fn available_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let decayed_mana = self.decayed_mana(protocol_parameters, creation_index, target_index)?; + + decayed_mana + .stored + .checked_add(decayed_mana.potential) + .ok_or(OutputError::ConsumedManaOverflow) + } + + /// Returns the decayed stored and potential mana of the output. + pub fn decayed_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + let generation_amount = self.amount().saturating_sub(min_deposit); + let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?; + let potential_mana = + protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; + + Ok(DecayedMana { + stored: stored_mana, + potential: potential_mana, + }) + } } impl StorageScore for BasicOutput { @@ -368,44 +417,39 @@ impl WorkScore for BasicOutput { impl MinimumOutputAmount for BasicOutput {} -fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> { - if VERIFY { - if unlock_conditions.address().is_none() { - Err(Error::MissingAddressUnlockCondition) - } else { - verify_allowed_unlock_conditions(unlock_conditions, BasicOutput::ALLOWED_UNLOCK_CONDITIONS) - } +fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), OutputError> { + if unlock_conditions.address().is_none() { + Err(OutputError::MissingAddressUnlockCondition) } else { - Ok(()) + Ok(verify_allowed_unlock_conditions( + unlock_conditions, + BasicOutput::ALLOWED_UNLOCK_CONDITIONS, + )?) } } -fn verify_unlock_conditions_packable( +fn verify_unlock_conditions_packable( unlock_conditions: &UnlockConditions, _: &ProtocolParameters, -) -> Result<(), Error> { - verify_unlock_conditions::(unlock_conditions) +) -> Result<(), OutputError> { + verify_unlock_conditions(unlock_conditions) } -fn verify_features(features: &Features) -> Result<(), Error> { - if VERIFY { - verify_allowed_features(features, BasicOutput::ALLOWED_FEATURES) - } else { - Ok(()) - } +fn verify_features(features: &Features) -> Result<(), OutputError> { + Ok(verify_allowed_features(features, BasicOutput::ALLOWED_FEATURES)?) } -fn verify_features_packable(features: &Features, _: &ProtocolParameters) -> Result<(), Error> { - verify_features::(features) +fn verify_features_packable(features: &Features, _: &ProtocolParameters) -> Result<(), OutputError> { + verify_features(features) } -fn verify_basic_output(output: &BasicOutput, _: &ProtocolParameters) -> Result<(), Error> { - verify_restricted_addresses( +fn verify_basic_output(output: &BasicOutput, _: &ProtocolParameters) -> Result<(), OutputError> { + Ok(verify_restricted_addresses( output.unlock_conditions(), BasicOutput::KIND, output.features.native_token(), output.mana, - ) + )?) } #[cfg(feature = "serde")] @@ -415,10 +459,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::block::{output::unlock_condition::UnlockCondition, Error}, - utils::serde::string, - }; + use crate::{types::block::output::unlock_condition::UnlockCondition, utils::serde::string}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -447,7 +488,7 @@ mod dto { } impl TryFrom for BasicOutput { - type Error = Error; + type Error = OutputError; fn try_from(dto: BasicOutputDto) -> Result { let mut builder = BasicOutputBuilder::new_with_amount(dto.amount) @@ -465,14 +506,14 @@ mod dto { crate::impl_serde_typed_dto!(BasicOutput, BasicOutputDto, "basic output"); } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod tests { use pretty_assertions::assert_eq; use super::*; use crate::types::block::{ output::{basic::dto::BasicOutputDto, FoundryId, SimpleTokenScheme, TokenId}, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, rand::{ address::rand_account_address, output::{ @@ -483,7 +524,7 @@ mod tests { #[test] fn to_from_dto() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let basic_output = rand_basic_output(protocol_parameters.token_supply()); let dto = BasicOutputDto::from(&basic_output); let output = Output::Basic(BasicOutput::try_from(dto).unwrap()); diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 27a3e8941e..eadc8ccd39 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -6,19 +6,18 @@ use alloc::collections::BTreeSet; use packable::{Packable, PackableExt}; use crate::types::block::{ - address::{AccountAddress, Address}, + address::{AccountAddress, Address, AddressError}, output::{ chain_id::ChainId, unlock_condition::{ verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, - MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, + DecayedMana, MinimumOutputAmount, Output, OutputBuilderAmount, OutputError, OutputId, StorageScore, + StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::TransactionFailureReason, - slot::EpochIndex, - Error, + slot::{EpochIndex, SlotIndex}, }; crate::impl_id!( @@ -41,13 +40,20 @@ impl DelegationId { } } +// TODO maybe can be removed as part of https://github.com/iotaledger/iota-sdk/issues/1938 +#[derive(Copy, Clone)] +pub enum DelegatedAmount { + Amount(u64), + MinimumAmount(StorageScoreParameters), +} + /// Builder for a [`DelegationOutput`]. #[derive(Clone)] #[must_use] pub struct DelegationOutputBuilder { // TODO https://github.com/iotaledger/iota-sdk/issues/1938 amount: Option, - delegated_amount: OutputBuilderAmount, + delegated_amount: DelegatedAmount, delegation_id: DelegationId, validator_address: AccountAddress, start_epoch: EpochIndex, @@ -59,7 +65,19 @@ impl DelegationOutputBuilder { /// Creates a [`DelegationOutputBuilder`] with a provided amount. /// Will set the delegated amount field to match. pub fn new_with_amount(amount: u64, delegation_id: DelegationId, validator_address: AccountAddress) -> Self { - Self::new(OutputBuilderAmount::Amount(amount), delegation_id, validator_address) + Self::new(DelegatedAmount::Amount(amount), delegation_id, validator_address) + } + + /// Creates a [`DelegationOutputBuilder`] with a provided amount, unless it is below the minimum. + /// Will set the delegated amount field to match. + pub fn new_with_amount_or_minimum( + amount: u64, + delegation_id: DelegationId, + validator_address: AccountAddress, + params: StorageScoreParameters, + ) -> Self { + Self::new(DelegatedAmount::Amount(amount), delegation_id, validator_address) + .with_amount_or_minimum(amount, params) } /// Creates a [`DelegationOutputBuilder`] with provided storage score parameters. @@ -69,18 +87,10 @@ impl DelegationOutputBuilder { delegation_id: DelegationId, validator_address: AccountAddress, ) -> Self { - Self::new( - OutputBuilderAmount::MinimumAmount(params), - delegation_id, - validator_address, - ) + Self::new(DelegatedAmount::MinimumAmount(params), delegation_id, validator_address) } - fn new( - delegated_amount: OutputBuilderAmount, - delegation_id: DelegationId, - validator_address: AccountAddress, - ) -> Self { + fn new(delegated_amount: DelegatedAmount, delegation_id: DelegationId, validator_address: AccountAddress) -> Self { Self { amount: None, delegated_amount, @@ -98,9 +108,16 @@ impl DelegationOutputBuilder { self } + /// Sets the amount to the provided value, unless it is below the minimum. + #[inline(always)] + pub fn with_amount_or_minimum(mut self, amount: u64, params: StorageScoreParameters) -> Self { + self.amount = Some(OutputBuilderAmount::AmountOrMinimum(amount, params)); + self + } + /// Sets the amount to the minimum required amount. pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { - if matches!(self.delegated_amount, OutputBuilderAmount::MinimumAmount(_)) { + if matches!(self.delegated_amount, DelegatedAmount::MinimumAmount(_)) { self.amount = None; } else { self.amount = Some(OutputBuilderAmount::MinimumAmount(params)); @@ -160,14 +177,14 @@ impl DelegationOutputBuilder { } /// Finishes the builder into a [`DelegationOutput`] without parameters verification. - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { let validator_address = Address::from(self.validator_address); - verify_validator_address::(&validator_address)?; + verify_validator_address(&validator_address)?; let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; - verify_unlock_conditions::(&unlock_conditions)?; + verify_unlock_conditions(&unlock_conditions)?; verify_restricted_addresses(&unlock_conditions, DelegationOutput::KIND, None, 0)?; let mut output = DelegationOutput { @@ -181,21 +198,22 @@ impl DelegationOutputBuilder { }; match self.delegated_amount { - OutputBuilderAmount::Amount(amount) => { + DelegatedAmount::Amount(amount) => { output.delegated_amount = amount; output.amount = self.amount.map_or(amount, |builder_amount| match builder_amount { OutputBuilderAmount::Amount(amount) => amount, + OutputBuilderAmount::AmountOrMinimum(amount, params) => output.minimum_amount(params).max(amount), OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }); } - OutputBuilderAmount::MinimumAmount(params) => { + DelegatedAmount::MinimumAmount(params) => { let min = output.minimum_amount(params); output.delegated_amount = min; - output.amount = if let Some(OutputBuilderAmount::Amount(amount)) = self.amount { - amount - } else { - min - }; + output.amount = self.amount.map_or(min, |builder_amount| match builder_amount { + OutputBuilderAmount::Amount(amount) => amount, + OutputBuilderAmount::AmountOrMinimum(amount, params) => output.minimum_amount(params).max(amount), + OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), + }); } } @@ -203,7 +221,7 @@ impl DelegationOutputBuilder { } /// Finishes the [`DelegationOutputBuilder`] into an [`Output`]. - pub fn finish_output(self) -> Result { + pub fn finish_output(self) -> Result { Ok(Output::Delegation(self.finish()?)) } } @@ -212,7 +230,7 @@ impl From<&DelegationOutput> for DelegationOutputBuilder { fn from(output: &DelegationOutput) -> Self { Self { amount: Some(OutputBuilderAmount::Amount(output.amount)), - delegated_amount: OutputBuilderAmount::Amount(output.delegated_amount), + delegated_amount: DelegatedAmount::Amount(output.delegated_amount), delegation_id: output.delegation_id, validator_address: *output.validator_address.as_account(), start_epoch: output.start_epoch, @@ -224,7 +242,7 @@ impl From<&DelegationOutput> for DelegationOutputBuilder { /// An output which delegates its contained IOTA coins as voting power to a validator. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = OutputError)] #[packable(unpack_visitor = ProtocolParameters)] #[packable(verify_with = verify_delegation_output)] pub struct DelegationOutput { @@ -236,6 +254,7 @@ pub struct DelegationOutput { delegation_id: DelegationId, /// Account address of the validator to which this output is delegating. #[packable(verify_with = verify_validator_address_packable)] + #[packable(unpack_error_with = OutputError::ValidatorAddress)] validator_address: Address, /// Index of the first epoch for which this output delegates. start_epoch: EpochIndex, @@ -331,20 +350,37 @@ impl DelegationOutput { ChainId::Delegation(self.delegation_id) } - // Transition, just without full SemanticValidationContext. - pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), TransactionFailureReason> { - if !current_state.delegation_id.is_null() || next_state.delegation_id.is_null() { - return Err(TransactionFailureReason::DelegationOutputTransitionedTwice); - } - - if current_state.delegated_amount != next_state.delegated_amount - || current_state.start_epoch != next_state.start_epoch - || current_state.validator_address != next_state.validator_address - { - return Err(TransactionFailureReason::DelegationModified); - } - - Ok(()) + /// Returns all the mana held by the output, which is potential + stored, all decayed. + pub fn available_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let decayed_mana = self.decayed_mana(protocol_parameters, creation_index, target_index)?; + + decayed_mana + .stored + .checked_add(decayed_mana.potential) + .ok_or(OutputError::ConsumedManaOverflow) + } + + /// Returns the decayed stored and potential mana of the output. + pub fn decayed_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + let generation_amount = self.amount().saturating_sub(min_deposit); + let potential_mana = + protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; + + Ok(DecayedMana { + stored: 0, + potential: potential_mana, + }) } } @@ -366,51 +402,49 @@ impl WorkScore for DelegationOutput { impl MinimumOutputAmount for DelegationOutput {} -fn verify_validator_address(validator_address: &Address) -> Result<(), Error> { - if VERIFY { - if let Address::Account(validator_address) = validator_address { - if validator_address.is_null() { - return Err(Error::NullDelegationValidatorId); - } - } else { - return Err(Error::InvalidAddressKind(validator_address.kind())); +fn verify_validator_address(validator_address: &Address) -> Result<(), OutputError> { + if let Address::Account(validator_address) = validator_address { + if validator_address.is_null() { + return Err(OutputError::NullDelegationValidatorId); } + } else { + return Err(OutputError::ValidatorAddress(AddressError::Kind( + validator_address.kind(), + ))); } Ok(()) } -fn verify_validator_address_packable( - validator_address: &Address, - _: &ProtocolParameters, -) -> Result<(), Error> { - verify_validator_address::(validator_address) +fn verify_validator_address_packable(validator_address: &Address, _: &ProtocolParameters) -> Result<(), OutputError> { + verify_validator_address(validator_address) } -fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> { - if VERIFY { - if unlock_conditions.address().is_none() { - Err(Error::MissingAddressUnlockCondition) - } else { - verify_allowed_unlock_conditions(unlock_conditions, DelegationOutput::ALLOWED_UNLOCK_CONDITIONS) - } +fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), OutputError> { + if unlock_conditions.address().is_none() { + Err(OutputError::MissingAddressUnlockCondition) } else { - Ok(()) + Ok(verify_allowed_unlock_conditions( + unlock_conditions, + DelegationOutput::ALLOWED_UNLOCK_CONDITIONS, + )?) } } -fn verify_unlock_conditions_packable( +fn verify_unlock_conditions_packable( unlock_conditions: &UnlockConditions, _: &ProtocolParameters, -) -> Result<(), Error> { - verify_unlock_conditions::(unlock_conditions) +) -> Result<(), OutputError> { + verify_unlock_conditions(unlock_conditions) } -fn verify_delegation_output( - output: &DelegationOutput, - _: &ProtocolParameters, -) -> Result<(), Error> { - verify_restricted_addresses(output.unlock_conditions(), DelegationOutput::KIND, None, 0) +fn verify_delegation_output(output: &DelegationOutput, _: &ProtocolParameters) -> Result<(), OutputError> { + Ok(verify_restricted_addresses( + output.unlock_conditions(), + DelegationOutput::KIND, + None, + 0, + )?) } #[cfg(feature = "serde")] @@ -420,10 +454,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::block::{output::unlock_condition::UnlockCondition, Error}, - utils::serde::string, - }; + use crate::{types::block::output::unlock_condition::UnlockCondition, utils::serde::string}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -457,7 +488,7 @@ mod dto { } impl TryFrom for DelegationOutput { - type Error = Error; + type Error = OutputError; fn try_from(dto: DelegationOutputDto) -> Result { let mut builder = DelegationOutputBuilder::new_with_amount( diff --git a/sdk/src/types/block/output/error.rs b/sdk/src/types/block/output/error.rs new file mode 100644 index 0000000000..c26eaca5ae --- /dev/null +++ b/sdk/src/types/block/output/error.rs @@ -0,0 +1,71 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use crate::types::block::{ + address::AddressError, + mana::ManaError, + output::{ + feature::FeatureError, unlock_condition::UnlockConditionError, AccountId, AnchorId, NativeTokenError, NftId, + TokenSchemeError, + }, +}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum OutputError { + #[display(fmt = "invalid output kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid output amount: {_0}")] + Amount(u64), + #[display(fmt = "consumed mana overflow")] + ConsumedManaOverflow, + #[display(fmt = "the return deposit ({deposit}) must be greater than the minimum output amount ({required})")] + InsufficientStorageDepositReturnAmount { deposit: u64, required: u64 }, + #[display(fmt = "insufficient output amount: {amount} (should be at least {required})")] + AmountLessThanMinimum { amount: u64, required: u64 }, + #[display(fmt = "storage deposit return of {deposit} exceeds the original output amount of {amount}")] + StorageDepositReturnExceedsOutputAmount { deposit: u64, amount: u64 }, + #[display(fmt = "missing address unlock condition")] + MissingAddressUnlockCondition, + #[display(fmt = "missing governor address unlock condition")] + MissingGovernorUnlockCondition, + #[display(fmt = "missing state controller address unlock condition")] + MissingStateControllerUnlockCondition, + #[display(fmt = "null delegation validator ID")] + NullDelegationValidatorId, + #[display(fmt = "invalid foundry zero serial number")] + InvalidFoundryZeroSerialNumber, + #[display(fmt = "non zero state index or foundry counter while account ID is all zero")] + NonZeroStateIndexOrFoundryCounter, + #[display(fmt = "invalid stakes amount")] + InvalidStakedAmount, + #[display(fmt = "invalid validator address: {_0}")] + ValidatorAddress(AddressError), + #[display(fmt = "self deposit nft output, NFT ID {_0}")] + SelfDepositNft(NftId), + #[display(fmt = "self controlled anchor output, anchor ID {_0}")] + SelfControlledAnchorOutput(AnchorId), + #[display(fmt = "self deposit account output, account ID {_0}")] + SelfDepositAccount(AccountId), + #[from] + UnlockCondition(UnlockConditionError), + #[from] + Feature(FeatureError), + #[from] + Mana(ManaError), + #[from] + NativeToken(NativeTokenError), + #[from] + TokenScheme(TokenSchemeError), +} + +#[cfg(feature = "std")] +impl std::error::Error for OutputError {} + +impl From for OutputError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/output/feature/block_issuer.rs b/sdk/src/types/block/output/feature/block_issuer.rs index 5334d301c4..974c9c05a9 100644 --- a/sdk/src/types/block/output/feature/block_issuer.rs +++ b/sdk/src/types/block/output/feature/block_issuer.rs @@ -21,16 +21,15 @@ use packable::{ }; use crate::types::block::{ - output::{StorageScore, StorageScoreParameters}, + output::{feature::FeatureError, StorageScore, StorageScoreParameters}, protocol::{WorkScore, WorkScoreParameters}, slot::SlotIndex, - Error, }; #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] -#[packable(unpack_error = Error)] -#[packable(tag_type = u8, with_error = Error::InvalidBlockIssuerKeyKind)] +#[packable(unpack_error = FeatureError)] +#[packable(tag_type = u8, with_error = FeatureError::InvalidBlockIssuerKeyKind)] pub enum BlockIssuerKey { /// An Ed25519 public key hash block issuer key. #[packable(tag = Ed25519PublicKeyHashBlockIssuerKey::KIND)] @@ -99,7 +98,7 @@ impl core::fmt::Debug for Ed25519PublicKeyHashBlockIssuerKey { } impl Packable for Ed25519PublicKeyHashBlockIssuerKey { - type UnpackError = Error; + type UnpackError = FeatureError; type UnpackVisitor = (); fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -107,13 +106,11 @@ impl Packable for Ed25519PublicKeyHashBlockIssuerKey { Ok(()) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { - Ok(Self( - <[u8; Self::LENGTH]>::unpack::<_, VERIFY>(unpacker, visitor).coerce()?, - )) + Ok(Self(<[u8; Self::LENGTH]>::unpack(unpacker, visitor).coerce()?)) } } @@ -122,21 +119,21 @@ pub(crate) type BlockIssuerKeyCount = /// Lexicographically ordered list of unique [`BlockIssuerKey`] #[derive(Clone, Debug, Eq, PartialEq, Deref, Packable, Hash)] -#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidBlockIssuerKeyCount(p.into())))] +#[packable(unpack_error = FeatureError, with = |e| e.unwrap_item_err_or_else(|p| FeatureError::InvalidBlockIssuerKeyCount(p.into())))] pub struct BlockIssuerKeys( #[packable(verify_with = verify_block_issuer_keys)] BoxedSlicePrefix, ); -fn verify_block_issuer_keys(block_issuer_keys: &[BlockIssuerKey]) -> Result<(), Error> { - if VERIFY && !is_unique_sorted(block_issuer_keys.iter()) { - return Err(Error::BlockIssuerKeysNotUniqueSorted); +fn verify_block_issuer_keys(block_issuer_keys: &[BlockIssuerKey]) -> Result<(), FeatureError> { + if !is_unique_sorted(block_issuer_keys.iter()) { + return Err(FeatureError::BlockIssuerKeysNotUniqueSorted); } Ok(()) } impl TryFrom> for BlockIssuerKeys { - type Error = Error; + type Error = FeatureError; #[inline(always)] fn try_from(block_issuer_keys: Vec) -> Result { @@ -145,7 +142,7 @@ impl TryFrom> for BlockIssuerKeys { } impl TryFrom> for BlockIssuerKeys { - type Error = Error; + type Error = FeatureError; #[inline(always)] fn try_from(block_issuer_keys: BTreeSet) -> Result { @@ -171,25 +168,25 @@ impl BlockIssuerKeys { pub const COUNT_RANGE: RangeInclusive = Self::COUNT_MIN..=Self::COUNT_MAX; // [1..128] /// Creates a new [`BlockIssuerKeys`] from a vec. - pub fn from_vec(block_issuer_keys: Vec) -> Result { + pub fn from_vec(block_issuer_keys: Vec) -> Result { let mut block_issuer_keys = BoxedSlicePrefix::::try_from(block_issuer_keys.into_boxed_slice()) - .map_err(Error::InvalidBlockIssuerKeyCount)?; + .map_err(FeatureError::InvalidBlockIssuerKeyCount)?; block_issuer_keys.sort(); // Still need to verify the duplicate block issuer keys. - verify_block_issuer_keys::(&block_issuer_keys)?; + verify_block_issuer_keys(&block_issuer_keys)?; Ok(Self(block_issuer_keys)) } /// Creates a new [`BlockIssuerKeys`] from an ordered set. - pub fn from_set(block_issuer_keys: BTreeSet) -> Result { + pub fn from_set(block_issuer_keys: BTreeSet) -> Result { let block_issuer_keys = BoxedSlicePrefix::::try_from( block_issuer_keys.into_iter().collect::>(), ) - .map_err(Error::InvalidBlockIssuerKeyCount)?; + .map_err(FeatureError::InvalidBlockIssuerKeyCount)?; // We don't need to verify the block issuer keys here, because they are already verified by the BTreeSet. Ok(Self(block_issuer_keys)) @@ -205,7 +202,7 @@ impl StorageScore for BlockIssuerKeys { /// This feature defines the block issuer keys with which a signature from the containing /// account's Block Issuance Credit can be verified in order to burn Mana. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = FeatureError)] pub struct BlockIssuerFeature { /// The slot index at which the feature expires and can be removed. expiry_slot: SlotIndex, @@ -222,7 +219,7 @@ impl BlockIssuerFeature { pub fn new( expiry_slot: impl Into, block_issuer_keys: impl IntoIterator, - ) -> Result { + ) -> Result { let block_issuer_keys = BlockIssuerKeys::from_vec(block_issuer_keys.into_iter().collect::>())?; @@ -269,10 +266,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::block::{slot::SlotIndex, Error}, - utils::serde::prefix_hex_bytes, - }; + use crate::{types::block::slot::SlotIndex, utils::serde::prefix_hex_bytes}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -324,7 +318,7 @@ mod dto { } impl TryFrom for BlockIssuerFeature { - type Error = Error; + type Error = FeatureError; fn try_from(value: BlockIssuerFeatureDto) -> Result { Self::new(value.expiry_slot, BlockIssuerKeys::from_vec(value.block_issuer_keys)?) diff --git a/sdk/src/types/block/output/feature/error.rs b/sdk/src/types/block/output/feature/error.rs new file mode 100644 index 0000000000..f340d55766 --- /dev/null +++ b/sdk/src/types/block/output/feature/error.rs @@ -0,0 +1,62 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::{string::String, vec::Vec}; +use core::convert::Infallible; + +use crate::types::block::{ + address::AddressError, + output::{ + feature::{ + BlockIssuerKeyCount, FeatureCount, MetadataFeatureEntryCount, MetadataFeatureKeyLength, + MetadataFeatureValueLength, TagFeatureLength, + }, + NativeTokenError, + }, +}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum FeatureError { + #[display(fmt = "invalid feature kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid feature count: {_0}")] + Count(>::Error), + #[display(fmt = "invalid tag feature length {_0}")] + TagFeatureLength(>::Error), + #[display(fmt = "invalid metadata feature: {_0}")] + MetadataFeature(String), + #[display(fmt = "invalid metadata feature entry count: {_0}")] + MetadataFeatureEntryCount(>::Error), + #[display(fmt = "invalid metadata feature key length: {_0}")] + MetadataFeatureKeyLength(>::Error), + #[display(fmt = "invalid metadata feature value length: {_0}")] + MetadataFeatureValueLength(>::Error), + #[display(fmt = "features are not unique and/or sorted")] + NotUniqueSorted, + #[display(fmt = "disallowed feature at index {index} with kind {kind}")] + Disallowed { index: usize, kind: u8 }, + #[display(fmt = "non graphic ASCII key: {_0:?}")] + NonGraphicAsciiMetadataKey(Vec), + #[display(fmt = "invalid block issuer key kind: {_0}")] + InvalidBlockIssuerKeyKind(u8), + #[display(fmt = "invalid block issuer key count: {_0}")] + InvalidBlockIssuerKeyCount(>::Error), + #[display(fmt = "block issuer keys are not unique and/or sorted")] + BlockIssuerKeysNotUniqueSorted, + #[display(fmt = "block issuer feature missing for account with staking feature")] + StakingBlockIssuerMissing, + #[from] + NativeToken(NativeTokenError), + #[from] + Address(AddressError), +} + +#[cfg(feature = "std")] +impl std::error::Error for FeatureError {} + +impl From for FeatureError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/output/feature/metadata.rs b/sdk/src/types/block/output/feature/metadata.rs index 8d21874b84..e1e53a7891 100644 --- a/sdk/src/types/block/output/feature/metadata.rs +++ b/sdk/src/types/block/output/feature/metadata.rs @@ -19,7 +19,10 @@ use packable::{ Packable, PackableExt, }; -use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; +use crate::types::block::{ + output::{feature::FeatureError, StorageScore}, + protocol::WorkScore, +}; pub(crate) type MetadataFeatureEntryCount = BoundedU8<1, { u8::MAX }>; pub(crate) type MetadataFeatureKeyLength = BoundedU8<1, { u8::MAX }>; @@ -37,25 +40,18 @@ pub(crate) type MetadataBTreeMap = #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct MetadataFeature(MetadataBTreeMapPrefix); -pub(crate) fn verify_keys(map: &MetadataBTreeMapPrefix) -> Result<(), Error> { - if VERIFY { - for key in map.keys() { - if !key.iter().all(|c| c.is_ascii_graphic()) { - return Err(Error::NonGraphicAsciiMetadataKey(key.to_vec())); - } +pub(crate) fn verify_keys(map: &MetadataBTreeMapPrefix) -> Result<(), FeatureError> { + for key in map.keys() { + if !key.iter().all(|c| c.is_ascii_graphic()) { + return Err(FeatureError::NonGraphicAsciiMetadataKey(key.to_vec())); } } Ok(()) } -pub(crate) fn verify_packed_len( - len: usize, - bytes_length_range: RangeInclusive, -) -> Result<(), Error> { - if VERIFY - && !bytes_length_range.contains(&u16::try_from(len).map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?) - { - return Err(Error::InvalidMetadataFeature(format!( +pub(crate) fn verify_packed_len(len: usize, bytes_length_range: RangeInclusive) -> Result<(), FeatureError> { + if !bytes_length_range.contains(&u16::try_from(len).map_err(|e| FeatureError::MetadataFeature(e.to_string()))?) { + return Err(FeatureError::MetadataFeature(format!( "Out of bounds byte length: {len}" ))); } @@ -70,7 +66,7 @@ impl MetadataFeature { /// Creates a new [`MetadataFeature`]. #[inline(always)] - pub fn new(data: impl IntoIterator)>) -> Result { + pub fn new(data: impl IntoIterator)>) -> Result { let mut builder = Self::build(); builder.extend(data); builder.finish() @@ -130,7 +126,7 @@ impl MetadataFeatureMap { self } - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { let res = MetadataFeature( MetadataBTreeMapPrefix::try_from( self.0 @@ -140,18 +136,18 @@ impl MetadataFeatureMap { BoxedSlicePrefix::::try_from( k.as_bytes().to_vec().into_boxed_slice(), ) - .map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + .map_err(|e| FeatureError::MetadataFeature(e.to_string()))?, BoxedSlicePrefix::::try_from(v.clone().into_boxed_slice()) - .map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + .map_err(|e| FeatureError::MetadataFeature(e.to_string()))?, )) }) - .collect::>()?, + .collect::>()?, ) - .map_err(Error::InvalidMetadataFeatureEntryCount)?, + .map_err(FeatureError::MetadataFeatureEntryCount)?, ); - verify_keys::(&res.0)?; - verify_packed_len::(res.packed_len(), MetadataFeature::BYTE_LENGTH_RANGE)?; + verify_keys(&res.0)?; + verify_packed_len(res.packed_len(), MetadataFeature::BYTE_LENGTH_RANGE)?; Ok(res) } @@ -176,42 +172,44 @@ impl StorageScore for MetadataFeature {} impl WorkScore for MetadataFeature {} impl Packable for MetadataFeature { - type UnpackError = Error; + type UnpackError = FeatureError; type UnpackVisitor = (); fn pack(&self, packer: &mut P) -> Result<(), P::Error> { self.0.pack(packer) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { let mut unpacker = CounterUnpacker::new(unpacker); let res = Self( - MetadataBTreeMapPrefix::unpack::<_, VERIFY>(&mut unpacker, visitor) - .map_packable_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + MetadataBTreeMapPrefix::unpack(&mut unpacker, visitor) + .map_packable_err(|e| FeatureError::MetadataFeature(e.to_string()))?, ); - verify_keys::(&res.0).map_err(UnpackError::Packable)?; - verify_packed_len::(unpacker.counter(), Self::BYTE_LENGTH_RANGE).map_err(UnpackError::Packable)?; + if visitor.is_some() { + verify_keys(&res.0).map_err(UnpackError::Packable)?; + verify_packed_len(unpacker.counter(), Self::BYTE_LENGTH_RANGE).map_err(UnpackError::Packable)?; + } Ok(res) } } impl TryFrom)>> for MetadataFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(data: Vec<(String, Vec)>) -> Result { + fn try_from(data: Vec<(String, Vec)>) -> Result { Self::new(data) } } impl TryFrom>> for MetadataFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(data: BTreeMap>) -> Result { + fn try_from(data: BTreeMap>) -> Result { Self::new(data) } } @@ -353,9 +351,9 @@ pub(crate) mod irc_27 { } impl TryFrom for MetadataFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(value: Irc27Metadata) -> Result { + fn try_from(value: Irc27Metadata) -> Result { // TODO: is this hardcoded key correct or should users provide it? Self::build().with_key_value("irc-27", value).finish() } @@ -529,9 +527,9 @@ pub(crate) mod irc_30 { } impl TryFrom for MetadataFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(value: Irc30Metadata) -> Result { + fn try_from(value: Irc30Metadata) -> Result { // TODO: is this hardcoded key correct or should users provide it? Self::build().with_key_value("irc-30", value).finish() } diff --git a/sdk/src/types/block/output/feature/mod.rs b/sdk/src/types/block/output/feature/mod.rs index 03381a2754..9a9f4a1d59 100644 --- a/sdk/src/types/block/output/feature/mod.rs +++ b/sdk/src/types/block/output/feature/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 mod block_issuer; +mod error; mod issuer; mod metadata; mod native_token; @@ -30,6 +31,7 @@ pub use self::{ block_issuer::{ BlockIssuerFeature, BlockIssuerKey, BlockIssuerKeySource, BlockIssuerKeys, Ed25519PublicKeyHashBlockIssuerKey, }, + error::FeatureError, issuer::IssuerFeature, metadata::{MetadataFeature, MetadataFeatureMap}, native_token::NativeTokenFeature, @@ -41,13 +43,12 @@ pub use self::{ use crate::types::block::{ output::{StorageScore, StorageScoreParameters}, protocol::{WorkScore, WorkScoreParameters}, - Error, }; /// #[derive(Clone, Eq, PartialEq, Hash, From, Packable)] -#[packable(unpack_error = Error)] -#[packable(tag_type = u8, with_error = Error::InvalidFeatureKind)] +#[packable(unpack_error = FeatureError)] +#[packable(tag_type = u8, with_error = FeatureError::Kind)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] pub enum Feature { /// A sender feature. @@ -185,11 +186,11 @@ pub(crate) type FeatureCount = BoundedU8<0, { FeatureFlags::ALL_FLAGS.len() as u /// #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, Packable)] -#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidFeatureCount(p.into())))] +#[packable(unpack_error = FeatureError, with = |e| e.unwrap_item_err_or_else(|p| FeatureError::Count(p.into())))] pub struct Features(#[packable(verify_with = verify_unique_sorted)] BoxedSlicePrefix); impl TryFrom> for Features { - type Error = Error; + type Error = FeatureError; #[inline(always)] fn try_from(features: Vec) -> Result { @@ -198,7 +199,7 @@ impl TryFrom> for Features { } impl TryFrom> for Features { - type Error = Error; + type Error = FeatureError; #[inline(always)] fn try_from(features: BTreeSet) -> Result { @@ -217,25 +218,25 @@ impl IntoIterator for Features { impl Features { /// Creates a new [`Features`] from a vec. - pub fn from_vec(features: Vec) -> Result { + pub fn from_vec(features: Vec) -> Result { let mut features = BoxedSlicePrefix::::try_from(features.into_boxed_slice()) - .map_err(Error::InvalidFeatureCount)?; + .map_err(FeatureError::Count)?; features.sort_by_key(Feature::kind); // Sort is obviously fine now but uniqueness still needs to be checked. - verify_unique_sorted::(&features)?; + verify_unique_sorted(&features)?; Ok(Self(features)) } /// Creates a new [`Features`] from an ordered set. - pub fn from_set(features: BTreeSet) -> Result { + pub fn from_set(features: BTreeSet) -> Result { Ok(Self( features .into_iter() .collect::>() .try_into() - .map_err(Error::InvalidFeatureCount)?, + .map_err(FeatureError::Count)?, )) } @@ -297,18 +298,18 @@ impl StorageScore for Features { } #[inline] -fn verify_unique_sorted(features: &[Feature]) -> Result<(), Error> { - if VERIFY && !is_unique_sorted(features.iter().map(Feature::kind)) { - Err(Error::FeaturesNotUniqueSorted) +fn verify_unique_sorted(features: &[Feature]) -> Result<(), FeatureError> { + if !is_unique_sorted(features.iter().map(Feature::kind)) { + Err(FeatureError::NotUniqueSorted) } else { Ok(()) } } -pub(crate) fn verify_allowed_features(features: &Features, allowed_features: FeatureFlags) -> Result<(), Error> { +pub(crate) fn verify_allowed_features(features: &Features, allowed_features: FeatureFlags) -> Result<(), FeatureError> { for (index, feature) in features.iter().enumerate() { if !allowed_features.contains(feature.flag()) { - return Err(Error::UnallowedFeature { + return Err(FeatureError::Disallowed { index, kind: feature.kind(), }); diff --git a/sdk/src/types/block/output/feature/native_token.rs b/sdk/src/types/block/output/feature/native_token.rs index 932f536e01..bc01906492 100644 --- a/sdk/src/types/block/output/feature/native_token.rs +++ b/sdk/src/types/block/output/feature/native_token.rs @@ -40,7 +40,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::types::block::{output::TokenId, Error}; + use crate::types::block::output::{feature::FeatureError, TokenId}; #[derive(Serialize, Deserialize)] struct NativeTokenFeatureDto { @@ -62,9 +62,9 @@ mod dto { } impl TryFrom for NativeTokenFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(value: NativeTokenFeatureDto) -> Result { + fn try_from(value: NativeTokenFeatureDto) -> Result { Ok(Self::new(NativeToken::new(value.token_id, value.amount)?)) } } diff --git a/sdk/src/types/block/output/feature/state_metadata.rs b/sdk/src/types/block/output/feature/state_metadata.rs index de5c806d05..55346367c0 100644 --- a/sdk/src/types/block/output/feature/state_metadata.rs +++ b/sdk/src/types/block/output/feature/state_metadata.rs @@ -21,9 +21,12 @@ use super::{ metadata::{verify_keys, verify_packed_len, MetadataBTreeMap, MetadataBTreeMapPrefix}, MetadataFeatureKeyLength, MetadataFeatureValueLength, }; -use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; +use crate::types::block::{ + output::{feature::FeatureError, StorageScore}, + protocol::WorkScore, +}; -/// Defines metadata, arbitrary binary data, that will be stored in the output. +/// A Metadata Feature that can only be changed by the State Controller. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct StateMetadataFeature(pub(crate) MetadataBTreeMapPrefix); @@ -35,7 +38,7 @@ impl StateMetadataFeature { /// Creates a new [`StateMetadataFeature`]. #[inline(always)] - pub fn new(data: impl IntoIterator)>) -> Result { + pub fn new(data: impl IntoIterator)>) -> Result { let mut builder = Self::build(); builder.extend(data); builder.finish() @@ -95,7 +98,7 @@ impl StateMetadataFeatureMap { self } - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { let res = StateMetadataFeature( MetadataBTreeMapPrefix::try_from( self.0 @@ -105,18 +108,18 @@ impl StateMetadataFeatureMap { BoxedSlicePrefix::::try_from( k.as_bytes().to_vec().into_boxed_slice(), ) - .map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + .map_err(|e| FeatureError::MetadataFeature(e.to_string()))?, BoxedSlicePrefix::::try_from(v.clone().into_boxed_slice()) - .map_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + .map_err(|e| FeatureError::MetadataFeature(e.to_string()))?, )) }) - .collect::>()?, + .collect::>()?, ) - .map_err(Error::InvalidMetadataFeatureEntryCount)?, + .map_err(FeatureError::MetadataFeatureEntryCount)?, ); - verify_keys::(&res.0)?; - verify_packed_len::(res.packed_len(), StateMetadataFeature::BYTE_LENGTH_RANGE)?; + verify_keys(&res.0)?; + verify_packed_len(res.packed_len(), StateMetadataFeature::BYTE_LENGTH_RANGE)?; Ok(res) } @@ -141,42 +144,44 @@ impl StorageScore for StateMetadataFeature {} impl WorkScore for StateMetadataFeature {} impl Packable for StateMetadataFeature { - type UnpackError = Error; + type UnpackError = FeatureError; type UnpackVisitor = (); fn pack(&self, packer: &mut P) -> Result<(), P::Error> { self.0.pack(packer) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { let mut unpacker = CounterUnpacker::new(unpacker); let res = Self( - MetadataBTreeMapPrefix::unpack::<_, VERIFY>(&mut unpacker, visitor) - .map_packable_err(|e| Error::InvalidMetadataFeature(e.to_string()))?, + MetadataBTreeMapPrefix::unpack(&mut unpacker, visitor) + .map_packable_err(|e| FeatureError::MetadataFeature(e.to_string()))?, ); - verify_keys::(&res.0).map_err(UnpackError::Packable)?; - verify_packed_len::(unpacker.counter(), Self::BYTE_LENGTH_RANGE).map_err(UnpackError::Packable)?; + if visitor.is_some() { + verify_keys(&res.0).map_err(UnpackError::Packable)?; + verify_packed_len(unpacker.counter(), Self::BYTE_LENGTH_RANGE).map_err(UnpackError::Packable)?; + } Ok(res) } } impl TryFrom)>> for StateMetadataFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(data: Vec<(String, Vec)>) -> Result { + fn try_from(data: Vec<(String, Vec)>) -> Result { Self::new(data) } } impl TryFrom>> for StateMetadataFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(data: BTreeMap>) -> Result { + fn try_from(data: BTreeMap>) -> Result { Self::new(data) } } diff --git a/sdk/src/types/block/output/feature/tag.rs b/sdk/src/types/block/output/feature/tag.rs index e150834d6f..57589f0318 100644 --- a/sdk/src/types/block/output/feature/tag.rs +++ b/sdk/src/types/block/output/feature/tag.rs @@ -6,14 +6,17 @@ use core::ops::RangeInclusive; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix}; -use crate::types::block::{output::StorageScore, protocol::WorkScore, Error}; +use crate::types::block::{ + output::{feature::FeatureError, StorageScore}, + protocol::WorkScore, +}; pub(crate) type TagFeatureLength = BoundedU8<{ *TagFeature::LENGTH_RANGE.start() }, { *TagFeature::LENGTH_RANGE.end() }>; /// Makes it possible to tag outputs with an index, so they can be retrieved through an indexer API. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = |e| Error::InvalidTagFeatureLength(e.into_prefix_err().into()))] +#[packable(unpack_error = FeatureError, with = |e| FeatureError::TagFeatureLength(e.into_prefix_err().into()))] pub struct TagFeature( // Binary tag. pub(crate) BoxedSlicePrefix, @@ -27,7 +30,7 @@ impl TagFeature { /// Creates a new [`TagFeature`]. #[inline(always)] - pub fn new(tag: impl Into>) -> Result { + pub fn new(tag: impl Into>) -> Result { Self::try_from(tag.into()) } @@ -43,18 +46,18 @@ impl StorageScore for TagFeature {} impl WorkScore for TagFeature {} impl TryFrom> for TagFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(tag: Vec) -> Result { + fn try_from(tag: Vec) -> Result { tag.into_boxed_slice().try_into() } } impl TryFrom> for TagFeature { - type Error = Error; + type Error = FeatureError; - fn try_from(tag: Box<[u8]>) -> Result { - tag.try_into().map(Self).map_err(Error::InvalidTagFeatureLength) + fn try_from(tag: Box<[u8]>) -> Result { + tag.try_into().map(Self).map_err(FeatureError::TagFeatureLength) } } diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index 5234028d7b..6ed1c2a764 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -1,8 +1,7 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::collections::{BTreeMap, BTreeSet}; -use core::cmp::Ordering; +use alloc::collections::BTreeSet; use packable::{ error::{UnpackError, UnpackErrorExt}, @@ -10,7 +9,6 @@ use packable::{ unpacker::Unpacker, Packable, PackableExt, }; -use primitive_types::U256; use crate::types::block::{ address::{AccountAddress, Address}, @@ -18,13 +16,11 @@ use crate::types::block::{ account::AccountId, feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature}, unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, - ChainId, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, StorageScore, StorageScoreParameters, - TokenId, TokenScheme, + ChainId, DecayedMana, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, OutputError, StorageScore, + StorageScoreParameters, TokenId, TokenScheme, }, - payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::TransactionFailureReason, - Error, + slot::SlotIndex, }; crate::impl_id!( @@ -96,6 +92,20 @@ impl FoundryOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount), serial_number, token_scheme) } + /// Creates a [`FoundryOutputBuilder`] with a provided amount, unless it is below the minimum. + pub fn new_with_amount_or_minimum( + amount: u64, + serial_number: u32, + token_scheme: TokenScheme, + params: StorageScoreParameters, + ) -> Self { + Self::new( + OutputBuilderAmount::AmountOrMinimum(amount, params), + serial_number, + token_scheme, + ) + } + /// Creates a [`FoundryOutputBuilder`] with provided storage score parameters. /// The amount will be set to the minimum required amount of the resulting output. pub fn new_with_minimum_amount( @@ -124,6 +134,13 @@ impl FoundryOutputBuilder { self } + /// Sets the amount to the provided value, unless it is below the minimum. + #[inline(always)] + pub fn with_amount_or_minimum(mut self, amount: u64, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::AmountOrMinimum(amount, params); + self + } + /// Sets the amount to the minimum required amount. #[inline(always)] pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { @@ -236,9 +253,9 @@ impl FoundryOutputBuilder { } /// - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { if self.serial_number == 0 { - return Err(Error::InvalidFoundryZeroSerialNumber); + return Err(OutputError::InvalidFoundryZeroSerialNumber); } let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; @@ -264,6 +281,7 @@ impl FoundryOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, + OutputBuilderAmount::AmountOrMinimum(amount, params) => output.minimum_amount(params).max(amount), OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; @@ -271,7 +289,7 @@ impl FoundryOutputBuilder { } /// Finishes the [`FoundryOutputBuilder`] into an [`Output`]. - pub fn finish_output(self) -> Result { + pub fn finish_output(self) -> Result { Ok(Output::Foundry(self.finish()?)) } } @@ -401,90 +419,37 @@ impl FoundryOutput { ChainId::Foundry(self.id()) } - // Transition, just without full SemanticValidationContext - pub(crate) fn transition_inner( - current_state: &Self, - next_state: &Self, - input_native_tokens: &BTreeMap, - output_native_tokens: &BTreeMap, - capabilities: &TransactionCapabilities, - ) -> Result<(), TransactionFailureReason> { - if current_state.account_address() != next_state.account_address() - || current_state.serial_number != next_state.serial_number - || current_state.immutable_features != next_state.immutable_features - { - return Err(TransactionFailureReason::ChainOutputImmutableFeaturesChanged); - } - - let token_id = next_state.token_id(); - let input_tokens = input_native_tokens.get(&token_id).copied().unwrap_or_default(); - let output_tokens = output_native_tokens.get(&token_id).copied().unwrap_or_default(); - let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme; - let TokenScheme::Simple(ref next_token_scheme) = next_state.token_scheme; - - if current_token_scheme.maximum_supply() != next_token_scheme.maximum_supply() { - return Err(TransactionFailureReason::SimpleTokenSchemeMaximumSupplyChanged); - } - - if current_token_scheme.minted_tokens() > next_token_scheme.minted_tokens() - || current_token_scheme.melted_tokens() > next_token_scheme.melted_tokens() - { - return Err(TransactionFailureReason::SimpleTokenSchemeMintedMeltedTokenDecrease); - } - - match input_tokens.cmp(&output_tokens) { - Ordering::Less => { - // Mint - - // This can't underflow as it is known that current_minted_tokens <= next_minted_tokens. - let minted_diff = next_token_scheme.minted_tokens() - current_token_scheme.minted_tokens(); - // This can't underflow as it is known that input_tokens < output_tokens (Ordering::Less). - let token_diff = output_tokens - input_tokens; - - if minted_diff != token_diff { - return Err(TransactionFailureReason::NativeTokenSumUnbalanced); - } - - if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens() { - return Err(TransactionFailureReason::NativeTokenSumUnbalanced); - } - } - Ordering::Equal => { - // Transition - - if current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens() - || current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens() - { - return Err(TransactionFailureReason::NativeTokenSumUnbalanced); - } - } - Ordering::Greater => { - // Melt / Burn - - if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens() - && current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens() - { - return Err(TransactionFailureReason::NativeTokenSumUnbalanced); - } - - // This can't underflow as it is known that current_melted_tokens <= next_melted_tokens. - let melted_diff = next_token_scheme.melted_tokens() - current_token_scheme.melted_tokens(); - // This can't underflow as it is known that input_tokens > output_tokens (Ordering::Greater). - let token_diff = input_tokens - output_tokens; - - if melted_diff > token_diff { - return Err(TransactionFailureReason::NativeTokenSumUnbalanced); - } - - let burned_diff = token_diff - melted_diff; - - if !burned_diff.is_zero() && !capabilities.has_capability(TransactionCapabilityFlag::BurnNativeTokens) { - return Err(TransactionFailureReason::CapabilitiesNativeTokenBurningNotAllowed)?; - } - } - } - - Ok(()) + /// Returns all the mana held by the output, which is potential + stored, all decayed. + pub fn available_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let decayed_mana = self.decayed_mana(protocol_parameters, creation_index, target_index)?; + + decayed_mana + .stored + .checked_add(decayed_mana.potential) + .ok_or(OutputError::ConsumedManaOverflow) + } + + /// Returns the decayed stored and potential mana of the output. + pub fn decayed_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + let generation_amount = self.amount().saturating_sub(min_deposit); + let potential_mana = + protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; + + Ok(DecayedMana { + stored: 0, + potential: potential_mana, + }) } } @@ -512,7 +477,7 @@ impl WorkScore for FoundryOutput { impl MinimumOutputAmount for FoundryOutput {} impl Packable for FoundryOutput { - type UnpackError = Error; + type UnpackError = OutputError; type UnpackVisitor = ProtocolParameters; fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -526,32 +491,35 @@ impl Packable for FoundryOutput { Ok(()) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { - let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let amount = u64::unpack_inner(unpacker, visitor).coerce()?; - let serial_number = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let token_scheme = TokenScheme::unpack::<_, VERIFY>(unpacker, &())?; + let serial_number = u32::unpack_inner(unpacker, visitor).coerce()?; + let token_scheme = TokenScheme::unpack_inner(unpacker, visitor).coerce()?; - let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?; + let unlock_conditions = UnlockConditions::unpack(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_unlock_conditions(&unlock_conditions).map_err(UnpackError::Packable)?; } - let features = Features::unpack::<_, VERIFY>(unpacker, &())?; + let features = Features::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { - verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; + if visitor.is_some() { + verify_allowed_features(&features, Self::ALLOWED_FEATURES) + .map_err(UnpackError::Packable) + .coerce()?; } - let immutable_features = Features::unpack::<_, VERIFY>(unpacker, &())?; + let immutable_features = Features::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_allowed_features(&immutable_features, Self::ALLOWED_IMMUTABLE_FEATURES) - .map_err(UnpackError::Packable)?; + .map_err(UnpackError::Packable) + .coerce()?; } Ok(Self { @@ -565,11 +533,14 @@ impl Packable for FoundryOutput { } } -fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), Error> { +fn verify_unlock_conditions(unlock_conditions: &UnlockConditions) -> Result<(), OutputError> { if unlock_conditions.immutable_account_address().is_none() { - Err(Error::MissingAddressUnlockCondition) + Err(OutputError::MissingAddressUnlockCondition) } else { - verify_allowed_unlock_conditions(unlock_conditions, FoundryOutput::ALLOWED_UNLOCK_CONDITIONS) + Ok(verify_allowed_unlock_conditions( + unlock_conditions, + FoundryOutput::ALLOWED_UNLOCK_CONDITIONS, + )?) } } @@ -580,10 +551,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::block::{output::unlock_condition::UnlockCondition, Error}, - utils::serde::string, - }; + use crate::{types::block::output::unlock_condition::UnlockCondition, utils::serde::string}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -616,7 +584,7 @@ mod dto { } impl TryFrom for FoundryOutput { - type Error = Error; + type Error = OutputError; fn try_from(dto: FoundryOutputDto) -> Result { let mut builder: FoundryOutputBuilder = @@ -641,18 +609,19 @@ mod dto { crate::impl_serde_typed_dto!(FoundryOutput, FoundryOutputDto, "foundry output"); } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod tests { use pretty_assertions::assert_eq; use super::*; use crate::types::block::{ - output::foundry::dto::FoundryOutputDto, protocol::protocol_parameters, rand::output::rand_foundry_output, + output::foundry::dto::FoundryOutputDto, protocol::iota_mainnet_protocol_parameters, + rand::output::rand_foundry_output, }; #[test] fn to_from_dto() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let foundry_output = rand_foundry_output(protocol_parameters.token_supply()); let dto = FoundryOutputDto::from(&foundry_output); let output = Output::Foundry(FoundryOutput::try_from(dto).unwrap()); diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index 6737e268fe..09d8b86761 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -4,6 +4,7 @@ mod anchor; mod chain_id; mod delegation; +mod error; mod metadata; mod native_token; mod output_id; @@ -30,34 +31,29 @@ use derive_more::From; use getset::Getters; use packable::Packable; +pub(crate) use self::unlock_condition::AddressUnlockCondition; pub use self::{ account::{AccountId, AccountOutput, AccountOutputBuilder}, anchor::{AnchorId, AnchorOutput, AnchorOutputBuilder, AnchorTransition}, basic::{BasicOutput, BasicOutputBuilder}, chain_id::ChainId, delegation::{DelegationId, DelegationOutput, DelegationOutputBuilder}, + error::OutputError, feature::{Feature, Features}, foundry::{FoundryId, FoundryOutput, FoundryOutputBuilder}, metadata::{OutputConsumptionMetadata, OutputInclusionMetadata, OutputMetadata}, - native_token::{NativeToken, NativeTokens, NativeTokensBuilder, TokenId}, + native_token::{NativeToken, NativeTokenError, NativeTokens, NativeTokensBuilder, TokenId}, nft::{NftId, NftOutput, NftOutputBuilder}, output_id::OutputId, - output_id_proof::{HashableNode, LeafHash, OutputCommitmentProof, OutputIdProof, ValueHash}, + output_id_proof::{HashableNode, LeafHash, OutputCommitmentProof, OutputIdProof, ProofError, ValueHash}, storage_score::{StorageScore, StorageScoreParameters}, - token_scheme::{SimpleTokenScheme, TokenScheme}, + token_scheme::{SimpleTokenScheme, TokenScheme, TokenSchemeError}, unlock_condition::{UnlockCondition, UnlockConditions}, }; -pub(crate) use self::{ - feature::{MetadataFeatureEntryCount, MetadataFeatureKeyLength, MetadataFeatureValueLength, TagFeatureLength}, - native_token::NativeTokenCount, - output_id::OutputIndex, - unlock_condition::AddressUnlockCondition, -}; use crate::types::block::{ address::Address, protocol::{CommittableAgeRange, ProtocolParameters, WorkScore, WorkScoreParameters}, slot::SlotIndex, - Error, }; #[cfg(feature = "serde")] use crate::utils::serde::string; @@ -74,11 +70,12 @@ pub const OUTPUT_INDEX_RANGE: RangeInclusive = 0..=OUTPUT_INDEX_MAX; // [0. #[derive(Copy, Clone)] pub enum OutputBuilderAmount { Amount(u64), + AmountOrMinimum(u64, StorageScoreParameters), MinimumAmount(StorageScoreParameters), } -/// Contains the generic [`Output`] with associated [`OutputIdProof`] and [`OutputMetadata`]. -#[derive(Clone, Debug)] +/// Contains the generic [`Output`] and the associated [`OutputMetadata`]. +#[derive(Clone, Debug, Eq, PartialEq)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -86,57 +83,15 @@ pub enum OutputBuilderAmount { )] pub struct OutputWithMetadata { pub output: Output, - pub output_id_proof: OutputIdProof, pub metadata: OutputMetadata, } -impl OutputWithMetadata { - /// Creates a new [`OutputWithMetadata`]. - pub fn new(output: Output, output_id_proof: OutputIdProof, metadata: OutputMetadata) -> Self { - Self { - output, - output_id_proof, - metadata, - } - } - - /// Returns the [`Output`]. - pub fn output(&self) -> &Output { - &self.output - } - - /// Consumes self and returns the [`Output`]. - pub fn into_output(self) -> Output { - self.output - } - - /// Returns the [`OutputIdProof`]. - pub fn output_id_proof(&self) -> &OutputIdProof { - &self.output_id_proof - } - - /// Consumes self and returns the [`OutputIdProof`]. - pub fn into_output_id_proof(self) -> OutputIdProof { - self.output_id_proof - } - - /// Returns the [`OutputMetadata`]. - pub fn metadata(&self) -> &OutputMetadata { - &self.metadata - } - - /// Consumes self and returns the [`OutputMetadata`]. - pub fn into_metadata(self) -> OutputMetadata { - self.metadata - } -} - /// A generic output that can represent different types defining the deposit of funds. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From, Packable)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] -#[packable(unpack_error = Error)] +#[packable(unpack_error = OutputError)] #[packable(unpack_visitor = ProtocolParameters)] -#[packable(tag_type = u8, with_error = Error::InvalidOutputKind)] +#[packable(tag_type = u8, with_error = OutputError::Kind)] pub enum Output { /// A basic output. #[packable(tag = BasicOutput::KIND)] @@ -226,13 +181,13 @@ impl Output { protocol_parameters: &ProtocolParameters, creation_index: SlotIndex, target_index: SlotIndex, - ) -> Result { + ) -> Result { let decayed_mana = self.decayed_mana(protocol_parameters, creation_index, target_index)?; decayed_mana .stored .checked_add(decayed_mana.potential) - .ok_or(Error::ConsumedManaOverflow) + .ok_or(OutputError::ConsumedManaOverflow) } /// Returns the decayed stored and potential mana of the output. @@ -241,26 +196,15 @@ impl Output { protocol_parameters: &ProtocolParameters, creation_index: SlotIndex, target_index: SlotIndex, - ) -> Result { - let (amount, mana) = match self { - Self::Basic(output) => (output.amount(), output.mana()), - Self::Account(output) => (output.amount(), output.mana()), - Self::Anchor(output) => (output.amount(), output.mana()), - Self::Foundry(output) => (output.amount(), 0), - Self::Nft(output) => (output.amount(), output.mana()), - Self::Delegation(output) => (output.amount(), 0), - }; - - let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); - let generation_amount = amount.saturating_sub(min_deposit); - let stored_mana = protocol_parameters.mana_with_decay(mana, creation_index, target_index)?; - let potential_mana = - protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; - - Ok(DecayedMana { - stored: stored_mana, - potential: potential_mana, - }) + ) -> Result { + match self { + Self::Basic(output) => output.decayed_mana(protocol_parameters, creation_index, target_index), + Self::Account(output) => output.decayed_mana(protocol_parameters, creation_index, target_index), + Self::Anchor(output) => output.decayed_mana(protocol_parameters, creation_index, target_index), + Self::Foundry(output) => output.decayed_mana(protocol_parameters, creation_index, target_index), + Self::Nft(output) => output.decayed_mana(protocol_parameters, creation_index, target_index), + Self::Delegation(output) => output.decayed_mana(protocol_parameters, creation_index, target_index), + } } /// Returns the unlock conditions of an [`Output`], if any. @@ -352,14 +296,14 @@ impl Output { &self, commitment_slot_index: impl Into>, committable_age_range: CommittableAgeRange, - ) -> Result, Error> { + ) -> Result, OutputError> { Ok(match self { Self::Basic(output) => output .unlock_conditions() .locked_address(output.address(), commitment_slot_index, committable_age_range)? .cloned(), Self::Account(output) => Some(output.address().clone()), - Self::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), + Self::Anchor(_) => return Err(OutputError::Kind(AnchorOutput::KIND)), Self::Foundry(output) => Some(Address::Account(*output.account_address())), Self::Nft(output) => output .unlock_conditions() @@ -373,12 +317,12 @@ impl Output { /// Each [`Output`] has to have an amount that covers its associated byte cost, given by [`StorageScoreParameters`]. /// If there is a [`StorageDepositReturnUnlockCondition`](unlock_condition::StorageDepositReturnUnlockCondition), /// its amount is also checked. - pub fn verify_storage_deposit(&self, params: StorageScoreParameters) -> Result<(), Error> { + pub fn verify_storage_deposit(&self, params: StorageScoreParameters) -> Result<(), OutputError> { let minimum_storage_deposit = self.minimum_amount(params); // For any created `Output` in a transaction, it must hold that `Output::Amount >= Minimum Storage Deposit`. if self.amount() < minimum_storage_deposit { - return Err(Error::InsufficientStorageDepositAmount { + return Err(OutputError::AmountLessThanMinimum { amount: self.amount(), required: minimum_storage_deposit, }); @@ -391,7 +335,7 @@ impl Output { // We can't return more tokens than were originally contained in the output. // `Return Amount` ≤ `Amount`. if return_condition.amount() > self.amount() { - return Err(Error::StorageDepositReturnExceedsOutputAmount { + return Err(OutputError::StorageDepositReturnExceedsOutputAmount { deposit: return_condition.amount(), amount: self.amount(), }); @@ -401,7 +345,7 @@ impl Output { // `Minimum Storage Deposit` ≤ `Return Amount` if return_condition.amount() < minimum_storage_deposit { - return Err(Error::InsufficientStorageDepositReturnAmount { + return Err(OutputError::InsufficientStorageDepositReturnAmount { deposit: return_condition.amount(), required: minimum_storage_deposit, }); diff --git a/sdk/src/types/block/output/native_token.rs b/sdk/src/types/block/output/native_token.rs index 20084d4c46..674d085e79 100644 --- a/sdk/src/types/block/output/native_token.rs +++ b/sdk/src/types/block/output/native_token.rs @@ -6,6 +6,7 @@ use alloc::{ collections::{BTreeMap, BTreeSet}, vec::Vec, }; +use core::convert::Infallible; use derive_more::{Deref, DerefMut, From}; use iterator_sorted::is_unique_sorted; @@ -15,7 +16,6 @@ use primitive_types::U256; use crate::types::block::{ output::FoundryId, protocol::{WorkScore, WorkScoreParameters}, - Error, }; crate::impl_id!( @@ -33,10 +33,36 @@ impl From for TokenId { } } +#[derive(Debug, PartialEq, Eq, derive_more::Display)] +#[allow(missing_docs)] +pub enum NativeTokenError { + #[display(fmt = "invalid native token count: {_0}")] + Count(>::Error), + #[display(fmt = "native tokens are not unique and/or sorted")] + NotUniqueSorted, + #[display(fmt = "native tokens null amount")] + NullAmount, + #[display(fmt = "native tokens overflow")] + Overflow, + #[display(fmt = "consumed native tokens amount overflow")] + ConsumedAmountOverflow, + #[display(fmt = "created native tokens amount overflow")] + CreatedAmountOverflow, +} + +#[cfg(feature = "std")] +impl std::error::Error for NativeTokenError {} + +impl From for NativeTokenError { + fn from(error: Infallible) -> Self { + match error {} + } +} + /// #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Packable)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[packable(unpack_error = Error)] +#[packable(unpack_error = NativeTokenError)] pub struct NativeToken { // Identifier of the native token. #[cfg_attr(feature = "serde", serde(rename = "id"))] @@ -49,10 +75,10 @@ pub struct NativeToken { impl NativeToken { /// Creates a new [`NativeToken`]. #[inline(always)] - pub fn new(token_id: TokenId, amount: impl Into) -> Result { + pub fn new(token_id: TokenId, amount: impl Into) -> Result { let amount = amount.into(); - verify_amount::(&amount)?; + verify_amount(&amount)?; Ok(Self { token_id, amount }) } @@ -88,9 +114,9 @@ impl Ord for NativeToken { } #[inline] -fn verify_amount(amount: &U256) -> Result<(), Error> { - if VERIFY && amount.is_zero() { - Err(Error::NativeTokensNullAmount) +fn verify_amount(amount: &U256) -> Result<(), NativeTokenError> { + if amount.is_zero() { + Err(NativeTokenError::NullAmount) } else { Ok(()) } @@ -109,17 +135,17 @@ impl NativeTokensBuilder { } /// Adds the given [`NativeToken`]. - pub fn add_native_token(&mut self, native_token: NativeToken) -> Result<(), Error> { + pub fn add_native_token(&mut self, native_token: NativeToken) -> Result<(), NativeTokenError> { let entry = self.0.entry(*native_token.token_id()).or_default(); *entry = entry .checked_add(native_token.amount()) - .ok_or(Error::NativeTokensOverflow)?; + .ok_or(NativeTokenError::Overflow)?; Ok(()) } /// Adds the given [`NativeTokens`]. - pub fn add_native_tokens(&mut self, native_tokens: NativeTokens) -> Result<(), Error> { + pub fn add_native_tokens(&mut self, native_tokens: NativeTokens) -> Result<(), NativeTokenError> { for native_token in native_tokens { self.add_native_token(native_token)?; } @@ -128,7 +154,7 @@ impl NativeTokensBuilder { } /// Merges another [`NativeTokensBuilder`] into this one. - pub fn merge(&mut self, other: Self) -> Result<(), Error> { + pub fn merge(&mut self, other: Self) -> Result<(), NativeTokenError> { for (token_id, amount) in other.0.into_iter() { self.add_native_token(NativeToken::new(token_id, amount)?)?; } @@ -137,7 +163,7 @@ impl NativeTokensBuilder { } /// Finishes the [`NativeTokensBuilder`] into [`NativeTokens`]. - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { NativeTokens::try_from( self.0 .into_iter() @@ -147,7 +173,7 @@ impl NativeTokensBuilder { } /// Finishes the [`NativeTokensBuilder`] into a [`Vec`]. - pub fn finish_vec(self) -> Result, Error> { + pub fn finish_vec(self) -> Result, NativeTokenError> { self.0 .into_iter() .map(|(token_id, amount)| NativeToken::new(token_id, amount)) @@ -155,7 +181,7 @@ impl NativeTokensBuilder { } /// Finishes the [`NativeTokensBuilder`] into a [`BTreeSet`]. - pub fn finish_set(self) -> Result, Error> { + pub fn finish_set(self) -> Result, NativeTokenError> { self.0 .into_iter() .map(|(token_id, amount)| NativeToken::new(token_id, amount)) @@ -178,13 +204,13 @@ pub(crate) type NativeTokenCount = BoundedU8<0, 255>; /// #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, Packable)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidNativeTokenCount(p.into())))] +#[packable(unpack_error = NativeTokenError, with = |e| e.unwrap_item_err_or_else(|p| NativeTokenError::Count(p.into())))] pub struct NativeTokens( #[packable(verify_with = verify_unique_sorted)] BoxedSlicePrefix, ); impl TryFrom> for NativeTokens { - type Error = Error; + type Error = NativeTokenError; #[inline(always)] fn try_from(native_tokens: Vec) -> Result { @@ -193,7 +219,7 @@ impl TryFrom> for NativeTokens { } impl TryFrom> for NativeTokens { - type Error = Error; + type Error = NativeTokenError; #[inline(always)] fn try_from(native_tokens: BTreeSet) -> Result { @@ -212,26 +238,26 @@ impl IntoIterator for NativeTokens { impl NativeTokens { /// Creates a new [`NativeTokens`] from a vec. - pub fn from_vec(native_tokens: Vec) -> Result { + pub fn from_vec(native_tokens: Vec) -> Result { let mut native_tokens = BoxedSlicePrefix::::try_from(native_tokens.into_boxed_slice()) - .map_err(Error::InvalidNativeTokenCount)?; + .map_err(NativeTokenError::Count)?; native_tokens.sort_by(|a, b| a.token_id().cmp(b.token_id())); // Sort is obviously fine now but uniqueness still needs to be checked. - verify_unique_sorted::(&native_tokens)?; + verify_unique_sorted(&native_tokens)?; Ok(Self(native_tokens)) } /// Creates a new [`NativeTokens`] from an ordered set. - pub fn from_set(native_tokens: BTreeSet) -> Result { + pub fn from_set(native_tokens: BTreeSet) -> Result { Ok(Self( native_tokens .into_iter() .collect::>() .try_into() - .map_err(Error::InvalidNativeTokenCount)?, + .map_err(NativeTokenError::Count)?, )) } @@ -259,9 +285,9 @@ impl NativeTokens { } #[inline] -fn verify_unique_sorted(native_tokens: &[NativeToken]) -> Result<(), Error> { - if VERIFY && !is_unique_sorted(native_tokens.iter().map(NativeToken::token_id)) { - Err(Error::NativeTokensNotUniqueSorted) +fn verify_unique_sorted(native_tokens: &[NativeToken]) -> Result<(), NativeTokenError> { + if !is_unique_sorted(native_tokens.iter().map(NativeToken::token_id)) { + Err(NativeTokenError::NotUniqueSorted) } else { Ok(()) } diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index da0146aa14..da879a6166 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -18,12 +18,11 @@ use crate::types::block::{ verify_allowed_unlock_conditions, verify_restricted_addresses, AddressUnlockCondition, StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, - BasicOutputBuilder, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, - StorageScoreParameters, + BasicOutputBuilder, ChainId, DecayedMana, MinimumOutputAmount, Output, OutputBuilderAmount, OutputError, + OutputId, StorageScore, StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - semantic::TransactionFailureReason, - Error, + slot::SlotIndex, }; crate::impl_id!( @@ -71,6 +70,11 @@ impl NftOutputBuilder { Self::new(OutputBuilderAmount::Amount(amount), nft_id) } + /// Creates an [`NftOutputBuilder`] with a provided amount, unless it is below the minimum. + pub fn new_with_amount_or_minimum(amount: u64, nft_id: NftId, params: StorageScoreParameters) -> Self { + Self::new(OutputBuilderAmount::AmountOrMinimum(amount, params), nft_id) + } + /// Creates an [`NftOutputBuilder`] with provided storage score parameters. /// The amount will be set to the minimum required amount of the resulting output. pub fn new_with_minimum_amount(params: StorageScoreParameters, nft_id: NftId) -> Self { @@ -95,6 +99,13 @@ impl NftOutputBuilder { self } + /// Sets the amount to the provided value, unless it is below the minimum. + #[inline(always)] + pub fn with_amount_or_minimum(mut self, amount: u64, params: StorageScoreParameters) -> Self { + self.amount = OutputBuilderAmount::AmountOrMinimum(amount, params); + self + } + /// Sets the amount to the minimum required amount. #[inline(always)] pub fn with_minimum_amount(mut self, params: StorageScoreParameters) -> Self { @@ -207,7 +218,7 @@ impl NftOutputBuilder { mut self, return_address: impl Into
, params: StorageScoreParameters, - ) -> Result { + ) -> Result { Ok(match self.amount { OutputBuilderAmount::Amount(amount) => { let return_address = return_address.into(); @@ -244,12 +255,13 @@ impl NftOutputBuilder { self } } + OutputBuilderAmount::AmountOrMinimum(_, _) => self, OutputBuilderAmount::MinimumAmount(_) => self, }) } /// - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; verify_unlock_conditions(&unlock_conditions, &self.nft_id)?; @@ -274,6 +286,7 @@ impl NftOutputBuilder { output.amount = match self.amount { OutputBuilderAmount::Amount(amount) => amount, + OutputBuilderAmount::AmountOrMinimum(amount, params) => output.minimum_amount(params).max(amount), OutputBuilderAmount::MinimumAmount(params) => output.minimum_amount(params), }; @@ -281,7 +294,7 @@ impl NftOutputBuilder { } /// Finishes the [`NftOutputBuilder`] into an [`Output`]. - pub fn finish_output(self) -> Result { + pub fn finish_output(self) -> Result { Ok(Output::Nft(self.finish()?)) } } @@ -406,13 +419,38 @@ impl NftOutput { NftAddress::new(self.nft_id_non_null(output_id)) } - // Transition, just without full SemanticValidationContext - pub(crate) fn transition_inner(current_state: &Self, next_state: &Self) -> Result<(), TransactionFailureReason> { - if current_state.immutable_features != next_state.immutable_features { - return Err(TransactionFailureReason::ChainOutputImmutableFeaturesChanged); - } - - Ok(()) + /// Returns all the mana held by the output, which is potential + stored, all decayed. + pub fn available_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let decayed_mana = self.decayed_mana(protocol_parameters, creation_index, target_index)?; + + decayed_mana + .stored + .checked_add(decayed_mana.potential) + .ok_or(OutputError::ConsumedManaOverflow) + } + + /// Returns the decayed stored and potential mana of the output. + pub fn decayed_mana( + &self, + protocol_parameters: &ProtocolParameters, + creation_index: SlotIndex, + target_index: SlotIndex, + ) -> Result { + let min_deposit = self.minimum_amount(protocol_parameters.storage_score_parameters()); + let generation_amount = self.amount().saturating_sub(min_deposit); + let stored_mana = protocol_parameters.mana_with_decay(self.mana(), creation_index, target_index)?; + let potential_mana = + protocol_parameters.generate_mana_with_decay(generation_amount, creation_index, target_index)?; + + Ok(DecayedMana { + stored: stored_mana, + potential: potential_mana, + }) } } @@ -439,7 +477,7 @@ impl WorkScore for NftOutput { impl MinimumOutputAmount for NftOutput {} impl Packable for NftOutput { - type UnpackError = Error; + type UnpackError = OutputError; type UnpackVisitor = ProtocolParameters; fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -453,34 +491,38 @@ impl Packable for NftOutput { Ok(()) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { - let amount = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let amount = u64::unpack_inner(unpacker, visitor).coerce()?; - let mana = u64::unpack::<_, VERIFY>(unpacker, &()).coerce()?; + let mana = u64::unpack_inner(unpacker, visitor).coerce()?; - let nft_id = NftId::unpack::<_, VERIFY>(unpacker, &()).coerce()?; - let unlock_conditions = UnlockConditions::unpack::<_, VERIFY>(unpacker, visitor)?; + let nft_id = NftId::unpack_inner(unpacker, visitor).coerce()?; + let unlock_conditions = UnlockConditions::unpack(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_unlock_conditions(&unlock_conditions, &nft_id).map_err(UnpackError::Packable)?; } - let features = Features::unpack::<_, VERIFY>(unpacker, &())?; + let features = Features::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) - .map_err(UnpackError::Packable)?; - verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; + .map_err(UnpackError::Packable) + .coerce()?; + verify_allowed_features(&features, Self::ALLOWED_FEATURES) + .map_err(UnpackError::Packable) + .coerce()?; } - let immutable_features = Features::unpack::<_, VERIFY>(unpacker, &())?; + let immutable_features = Features::unpack_inner(unpacker, visitor).coerce()?; - if VERIFY { + if visitor.is_some() { verify_allowed_features(&immutable_features, Self::ALLOWED_IMMUTABLE_FEATURES) - .map_err(UnpackError::Packable)?; + .map_err(UnpackError::Packable) + .coerce()?; } Ok(Self { @@ -494,18 +536,21 @@ impl Packable for NftOutput { } } -fn verify_unlock_conditions(unlock_conditions: &UnlockConditions, nft_id: &NftId) -> Result<(), Error> { +fn verify_unlock_conditions(unlock_conditions: &UnlockConditions, nft_id: &NftId) -> Result<(), OutputError> { if let Some(unlock_condition) = unlock_conditions.address() { if let Address::Nft(nft_address) = unlock_condition.address() { if !nft_id.is_null() && nft_address.nft_id() == nft_id { - return Err(Error::SelfDepositNft(*nft_id)); + return Err(OutputError::SelfDepositNft(*nft_id)); } } } else { - return Err(Error::MissingAddressUnlockCondition); + return Err(OutputError::MissingAddressUnlockCondition); } - verify_allowed_unlock_conditions(unlock_conditions, NftOutput::ALLOWED_UNLOCK_CONDITIONS) + Ok(verify_allowed_unlock_conditions( + unlock_conditions, + NftOutput::ALLOWED_UNLOCK_CONDITIONS, + )?) } #[cfg(feature = "serde")] @@ -515,10 +560,7 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::{ - types::block::{output::unlock_condition::UnlockCondition, Error}, - utils::serde::string, - }; + use crate::{types::block::output::unlock_condition::UnlockCondition, utils::serde::string}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -552,7 +594,7 @@ mod dto { } impl TryFrom for NftOutput { - type Error = Error; + type Error = OutputError; fn try_from(dto: NftOutputDto) -> Result { let mut builder = NftOutputBuilder::new_with_amount(dto.amount, dto.nft_id) @@ -571,18 +613,18 @@ mod dto { crate::impl_serde_typed_dto!(NftOutput, NftOutputDto, "nft output"); } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod tests { use pretty_assertions::assert_eq; use super::*; use crate::types::block::{ - output::nft::dto::NftOutputDto, protocol::protocol_parameters, rand::output::rand_nft_output, + output::nft::dto::NftOutputDto, protocol::iota_mainnet_protocol_parameters, rand::output::rand_nft_output, }; #[test] fn to_from_dto() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let nft_output = rand_nft_output(protocol_parameters.token_supply()); let dto = NftOutputDto::from(&nft_output); let output = Output::Nft(NftOutput::try_from(dto).unwrap()); diff --git a/sdk/src/types/block/output/output_id.rs b/sdk/src/types/block/output/output_id.rs index 70d2b47cba..9c6c9c94c7 100644 --- a/sdk/src/types/block/output/output_id.rs +++ b/sdk/src/types/block/output/output_id.rs @@ -5,15 +5,16 @@ use alloc::string::ToString; use core::str::FromStr; use crypto::hashes::{blake2b::Blake2b256, Digest}; -use packable::{bounded::BoundedU16, PackableExt}; +use packable::bounded::BoundedU16; -use crate::types::block::{output::OUTPUT_INDEX_RANGE, payload::signed_transaction::TransactionId, Error}; +use crate::types::block::{ + error::IdentifierError, output::OUTPUT_INDEX_RANGE, payload::signed_transaction::TransactionId, +}; pub(crate) type OutputIndex = BoundedU16<{ *OUTPUT_INDEX_RANGE.start() }, { *OUTPUT_INDEX_RANGE.end() }>; /// The identifier of an [`Output`](crate::types::block::output::Output). #[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, packable::Packable)] -#[packable(unpack_error = Error)] pub struct OutputId { transaction_id: TransactionId, index: u16, @@ -49,38 +50,49 @@ impl OutputId { /// Hash the [`OutputId`] with BLAKE2b-256. #[inline(always)] pub fn hash(&self) -> [u8; 32] { - Blake2b256::digest(self.pack_to_vec()).into() + Blake2b256::digest(self.to_bytes()).into() + } + + pub fn to_bytes(&self) -> [u8; Self::LENGTH] { + let mut buffer = [0u8; Self::LENGTH]; + let (transaction_id, index) = buffer.split_at_mut(TransactionId::LENGTH); + transaction_id.copy_from_slice(self.transaction_id.as_ref()); + index.copy_from_slice(&self.index().to_le_bytes()); + buffer } } #[cfg(feature = "serde")] crate::string_serde_impl!(OutputId); -#[allow(clippy::fallible_impl_from)] impl From<[u8; Self::LENGTH]> for OutputId { fn from(bytes: [u8; Self::LENGTH]) -> Self { - // Unwrap is fine because size is already known and valid. - Self::unpack_unverified(bytes).unwrap() + #[repr(C)] + #[allow(dead_code)] + struct Layout { + transaction_id: TransactionId, + index: [u8; 2], + } + unsafe { + let Layout { transaction_id, index } = core::mem::transmute(bytes); + Self::new(transaction_id, u16::from_le_bytes(index)) + } } } impl FromStr for OutputId { - type Err = Error; + type Err = IdentifierError; fn from_str(s: &str) -> Result { Ok(Self::from( - prefix_hex::decode::<[u8; Self::LENGTH]>(s).map_err(Error::Hex)?, + prefix_hex::decode::<[u8; Self::LENGTH]>(s).map_err(IdentifierError)?, )) } } impl core::fmt::Display for OutputId { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut buffer = [0u8; Self::LENGTH]; - let (transaction_id, index) = buffer.split_at_mut(TransactionId::LENGTH); - transaction_id.copy_from_slice(self.transaction_id.as_ref()); - index.copy_from_slice(&self.index().to_le_bytes()); - write!(f, "{}", prefix_hex::encode(buffer)) + write!(f, "{}", prefix_hex::encode(self.to_bytes())) } } diff --git a/sdk/src/types/block/output/output_id_proof.rs b/sdk/src/types/block/output/output_id_proof.rs index 7f7bab6933..c067a9f85c 100644 --- a/sdk/src/types/block/output/output_id_proof.rs +++ b/sdk/src/types/block/output/output_id_proof.rs @@ -1,90 +1,336 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{boxed::Box, string::String}; +use alloc::boxed::Box; +use core::convert::Infallible; + +use crypto::hashes::{blake2b::Blake2b256, Digest}; +use packable::{Packable, PackableExt}; #[cfg(feature = "serde")] -use {crate::utils::serde::prefix_hex_bytes, alloc::format, serde::de::Deserialize, serde_json::Value}; +use crate::utils::serde::prefix_hex_bytes; +use crate::{ + types::block::{output::Output, slot::SlotIndex}, + utils::merkle_hasher::{largest_power_of_two, LEAF_HASH_PREFIX, NODE_HASH_PREFIX}, +}; + +#[derive(Debug, derive_more::Display)] +pub enum ProofError { + #[display(fmt = "invalid output ID proof kind: {_0}")] + Kind(u8), + #[display(fmt = "index {index} is out of range, outputs length {len}")] + IndexOutOfRange { index: u16, len: u16 }, + #[display(fmt = "no outputs provided")] + NoOutputs, +} -use crate::types::block::slot::SlotIndex; +#[cfg(feature = "std")] +impl std::error::Error for ProofError {} + +impl From for ProofError { + fn from(error: Infallible) -> Self { + match error {} + } +} /// The proof of the output identifier. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Packable)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] +#[packable(unpack_error = ProofError)] pub struct OutputIdProof { pub slot: SlotIndex, pub output_index: u16, - pub transaction_commitment: String, + #[cfg_attr(feature = "serde", serde(with = "prefix_hex_bytes"))] + pub transaction_commitment: [u8; 32], + #[packable(verify_with = verify_output_commitment_type)] pub output_commitment_proof: OutputCommitmentProof, } -#[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] -pub enum OutputCommitmentProof { - HashableNode(HashableNode), - LeafHash(LeafHash), - ValueHash(ValueHash), +impl OutputCommitmentProof { + pub fn new(outputs: &[Output], index: u16) -> Result { + let num_outputs = outputs.len() as u16; + if num_outputs == 0 { + Err(ProofError::NoOutputs) + } else if index >= num_outputs { + Err(ProofError::IndexOutOfRange { + index, + len: num_outputs, + }) + } else { + Ok(Self::proof(outputs, index)) + } + } + + fn proof(outputs: &[Output], index: u16) -> Self { + if let [output] = outputs { + Self::value(&output.pack_to_vec()) + } else { + let num_outputs = outputs.len() as u16; + + // Select a `pivot` element to split `data` into two slices `left` and `right`. + let pivot = largest_power_of_two(num_outputs as _) as u16; + let (left, right) = outputs.split_at(pivot as _); + + if index < pivot { + // `value` is contained in the left subtree, and the `right` subtree can be hashed together. + Self::node(Self::proof(left, index), Self::hash(right)) + } else { + // `value` is contained in the right subtree, and the `left` subtree can be hashed together. + Self::node(Self::hash(left), Self::proof(right, index - pivot)) + } + } + } + + /// Get the merkle tree hash for a list of outputs + fn hash(outputs: &[Output]) -> LeafHash { + match outputs { + [] => LeafHash::empty(), + [output] => LeafHash::new(&output.pack_to_vec()), + _ => { + let pivot = largest_power_of_two(outputs.len() as _); + let (left, right) = outputs.split_at(pivot as _); + let left = Self::hash(left).0; + let right = Self::hash(right).0; + + let mut hasher = Blake2b256::default(); + + hasher.update([NODE_HASH_PREFIX]); + hasher.update(left); + hasher.update(right); + LeafHash(hasher.finalize().into()) + } + } + } + + fn node(left: impl Into, right: impl Into) -> Self { + Self::Node(HashableNode::new(left, right)) + } + + fn value(bytes: &[u8]) -> Self { + Self::Value(ValueHash::new(bytes)) + } } -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for OutputCommitmentProof { - fn deserialize>(d: D) -> Result { - let value = Value::deserialize(d)?; - Ok( - match value - .get("type") - .and_then(Value::as_u64) - .ok_or_else(|| serde::de::Error::custom("invalid output commitment proof type"))? - as u8 - { - 0 => Self::HashableNode( - serde_json::from_value::(value) - .map_err(|e| serde::de::Error::custom(format!("cannot deserialize hashable node: {e}")))?, - ), - 1 => Self::LeafHash( - serde_json::from_value::(value) - .map_err(|e| serde::de::Error::custom(format!("cannot deserialize leaf hash: {e}")))?, - ), - 2 => Self::ValueHash( - serde_json::from_value::(value) - .map_err(|e| serde::de::Error::custom(format!("cannot deserialize value hash: {e}")))?, - ), - _ => return Err(serde::de::Error::custom("invalid output commitment proof")), - }, - ) +fn verify_output_commitment_type(proof: &OutputCommitmentProof) -> Result<(), ProofError> { + match proof { + OutputCommitmentProof::Leaf(_) => Err(ProofError::Kind(LeafHash::KIND)), + _ => Ok(()), } } -/// Node contains the hashes of the left and right children of a node in the tree. -#[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Eq, PartialEq, derive_more::From, Packable)] +#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] +#[packable(tag_type = u8, with_error = ProofError::Kind)] +#[packable(unpack_error = ProofError)] +pub enum OutputCommitmentProof { + #[packable(tag = HashableNode::KIND)] + Node(HashableNode), + #[packable(tag = LeafHash::KIND)] + Leaf(LeafHash), + #[packable(tag = ValueHash::KIND)] + Value(ValueHash), +} + +/// Contains the hashes of the left and right children of a node in the OutputCommitmentProof tree. +#[derive(Clone, Debug, Eq, PartialEq, Packable)] +#[packable(unpack_visitor = ())] pub struct HashableNode { - #[cfg_attr(feature = "serde", serde(rename = "type"))] - pub kind: u8, pub l: Box, pub r: Box, } -/// Leaf Hash contains the hash of a leaf in the tree. -#[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct LeafHash { - #[cfg_attr(feature = "serde", serde(rename = "type"))] - pub kind: u8, - #[cfg_attr(feature = "serde", serde(with = "prefix_hex_bytes"))] - pub hash: [u8; 32], +impl HashableNode { + const KIND: u8 = 0; + + pub fn new(left: impl Into, right: impl Into) -> Self { + Self { + l: Box::new(left.into()), + r: Box::new(right.into()), + } + } } -/// Value Hash contains the hash of the value for which the proof is being computed. -#[derive(Clone, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ValueHash { - #[cfg_attr(feature = "serde", serde(rename = "type"))] - pub kind: u8, - #[cfg_attr(feature = "serde", serde(with = "prefix_hex_bytes"))] - pub hash: [u8; 32], +/// Contains the hash of a leaf in the OutputCommitmentProof tree. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct LeafHash(pub [u8; 32]); + +impl LeafHash { + const KIND: u8 = 1; + + pub fn new(bytes: &[u8]) -> Self { + let mut hasher = Blake2b256::default(); + + hasher.update([LEAF_HASH_PREFIX]); + hasher.update(bytes); + Self(hasher.finalize().into()) + } + + pub fn empty() -> Self { + Self(Blake2b256::digest([]).into()) + } +} + +impl Packable for LeafHash { + type UnpackError = Infallible; + type UnpackVisitor = (); + + fn pack(&self, packer: &mut P) -> Result<(), P::Error> { + 32_u8.pack(packer)?; + self.0.pack(packer)?; + Ok(()) + } + + fn unpack( + unpacker: &mut U, + visitor: Option<&Self::UnpackVisitor>, + ) -> Result> { + let _len = u8::unpack(unpacker, visitor)?; + let bytes = Packable::unpack(unpacker, visitor)?; + Ok(Self(bytes)) + } +} + +/// Contains the hash of the value for which the OutputCommitmentProof is being computed. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ValueHash(pub [u8; 32]); + +impl ValueHash { + const KIND: u8 = 2; + + pub fn new(bytes: &[u8]) -> Self { + let mut hasher = Blake2b256::default(); + + hasher.update([LEAF_HASH_PREFIX]); + hasher.update(bytes); + Self(hasher.finalize().into()) + } +} + +impl Packable for ValueHash { + type UnpackError = Infallible; + type UnpackVisitor = (); + + fn pack(&self, packer: &mut P) -> Result<(), P::Error> { + 32_u8.pack(packer)?; + self.0.pack(packer)?; + Ok(()) + } + + fn unpack( + unpacker: &mut U, + visitor: Option<&Self::UnpackVisitor>, + ) -> Result> { + let _len = u8::unpack(unpacker, visitor)?; + let bytes = Packable::unpack(unpacker, visitor)?; + Ok(Self(bytes)) + } +} + +#[cfg(feature = "serde")] +mod serialization { + use alloc::format; + + use serde::{Deserialize, Serialize}; + use serde_json::Value; + + use super::*; + use crate::{impl_serde_typed_dto, utils::serde::prefix_hex_bytes}; + + #[derive(Serialize, Deserialize)] + struct NodeDto { + #[serde(rename = "type")] + kind: u8, + l: Box, + r: Box, + } + + #[derive(Serialize, Deserialize)] + struct HashDto { + #[serde(rename = "type")] + kind: u8, + #[serde(with = "prefix_hex_bytes")] + hash: [u8; 32], + } + + impl From for HashableNode { + fn from(value: NodeDto) -> Self { + Self { l: value.l, r: value.r } + } + } + + impl From<&HashableNode> for NodeDto { + fn from(value: &HashableNode) -> Self { + Self { + kind: HashableNode::KIND, + l: value.l.clone(), + r: value.r.clone(), + } + } + } + + impl From for ValueHash { + fn from(value: HashDto) -> Self { + Self(value.hash) + } + } + + impl From<&ValueHash> for HashDto { + fn from(value: &ValueHash) -> Self { + Self { + kind: ValueHash::KIND, + hash: value.0, + } + } + } + + impl From for LeafHash { + fn from(value: HashDto) -> Self { + Self(value.hash) + } + } + + impl From<&LeafHash> for HashDto { + fn from(value: &LeafHash) -> Self { + Self { + kind: LeafHash::KIND, + hash: value.0, + } + } + } + + impl_serde_typed_dto!(HashableNode, NodeDto, "hashable node"); + impl_serde_typed_dto!(ValueHash, HashDto, "value hash"); + impl_serde_typed_dto!(LeafHash, HashDto, "leaf hash"); + + impl<'de> Deserialize<'de> for OutputCommitmentProof { + fn deserialize>(d: D) -> Result { + let value = Value::deserialize(d)?; + Ok( + match value + .get("type") + .and_then(Value::as_u64) + .ok_or_else(|| serde::de::Error::custom("invalid output commitment proof type"))? + as u8 + { + HashableNode::KIND => Self::Node( + serde_json::from_value::(value) + .map_err(|e| serde::de::Error::custom(format!("cannot deserialize hashable node: {e}")))?, + ), + LeafHash::KIND => Self::Leaf( + serde_json::from_value::(value) + .map_err(|e| serde::de::Error::custom(format!("cannot deserialize leaf hash: {e}")))?, + ), + ValueHash::KIND => Self::Value( + serde_json::from_value::(value) + .map_err(|e| serde::de::Error::custom(format!("cannot deserialize value hash: {e}")))?, + ), + _ => return Err(serde::de::Error::custom("invalid output commitment proof")), + }, + ) + } + } } diff --git a/sdk/src/types/block/output/storage_score.rs b/sdk/src/types/block/output/storage_score.rs index 27e795a745..97ecb38551 100644 --- a/sdk/src/types/block/output/storage_score.rs +++ b/sdk/src/types/block/output/storage_score.rs @@ -18,13 +18,6 @@ use crate::types::block::{ BlockId, }; -const DEFAULT_STORAGE_COST: u64 = 100; -const DEFAULT_FACTOR_DATA: u8 = 1; -const DEFAULT_OFFSET_OUTPUT_OVERHEAD: u64 = 10; -const DEFAULT_OFFSET_ED25519_BLOCK_ISSUER_KEY: u64 = 100; -const DEFAULT_OFFSET_STAKING_FEATURE: u64 = 100; -const DEFAULT_OFFSET_DELEGATION: u64 = 100; - // Parameters of storage score calculations on objects which take node resources. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable)] #[cfg_attr( @@ -35,34 +28,21 @@ const DEFAULT_OFFSET_DELEGATION: u64 = 100; pub struct StorageScoreParameters { /// Number of IOTA tokens required per unit of storage score. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - storage_cost: u64, + pub(crate) storage_cost: u64, /// Factor to be used for data only fields. - factor_data: u8, + pub(crate) factor_data: u8, /// Offset to be applied to all outputs for the overhead of handling them in storage. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - offset_output_overhead: u64, + pub(crate) offset_output_overhead: u64, /// Offset to be used for Ed25519-based block issuer keys. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - offset_ed25519_block_issuer_key: u64, + pub(crate) offset_ed25519_block_issuer_key: u64, /// Offset to be used for staking feature. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - offset_staking_feature: u64, + pub(crate) offset_staking_feature: u64, /// Offset to be used for delegation. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - offset_delegation: u64, -} - -impl Default for StorageScoreParameters { - fn default() -> Self { - Self { - storage_cost: DEFAULT_STORAGE_COST, - factor_data: DEFAULT_FACTOR_DATA, - offset_output_overhead: DEFAULT_OFFSET_OUTPUT_OVERHEAD, - offset_ed25519_block_issuer_key: DEFAULT_OFFSET_ED25519_BLOCK_ISSUER_KEY, - offset_staking_feature: DEFAULT_OFFSET_STAKING_FEATURE, - offset_delegation: DEFAULT_OFFSET_DELEGATION, - } - } + pub(crate) offset_delegation: u64, } impl StorageScoreParameters { diff --git a/sdk/src/types/block/output/token_scheme/error.rs b/sdk/src/types/block/output/token_scheme/error.rs new file mode 100644 index 0000000000..b945e735ae --- /dev/null +++ b/sdk/src/types/block/output/token_scheme/error.rs @@ -0,0 +1,24 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use primitive_types::U256; + +#[derive(Debug, PartialEq, Eq, derive_more::Display)] +#[allow(missing_docs)] +pub enum TokenSchemeError { + #[display(fmt = "invalid token scheme kind {_0}")] + Kind(u8), + #[display(fmt = "invalid foundry output supply: minted {minted}, melted {melted} max {max}")] + Supply { minted: U256, melted: U256, max: U256 }, +} + +#[cfg(feature = "std")] +impl std::error::Error for TokenSchemeError {} + +impl From for TokenSchemeError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/output/token_scheme/mod.rs b/sdk/src/types/block/output/token_scheme/mod.rs index 4131f8d99e..3f354a413d 100644 --- a/sdk/src/types/block/output/token_scheme/mod.rs +++ b/sdk/src/types/block/output/token_scheme/mod.rs @@ -1,18 +1,16 @@ // Copyright 2020-2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod error; mod simple; -pub use self::simple::SimpleTokenScheme; -use crate::types::block::{ - protocol::{WorkScore, WorkScoreParameters}, - Error, -}; +pub use self::{error::TokenSchemeError, simple::SimpleTokenScheme}; +use crate::types::block::protocol::{WorkScore, WorkScoreParameters}; /// #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, derive_more::From, packable::Packable)] -#[packable(unpack_error = Error)] -#[packable(tag_type = u8, with_error = Error::InvalidTokenSchemeKind)] +#[packable(unpack_error = TokenSchemeError)] +#[packable(tag_type = u8, with_error = TokenSchemeError::Kind)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] pub enum TokenScheme { /// diff --git a/sdk/src/types/block/output/token_scheme/simple.rs b/sdk/src/types/block/output/token_scheme/simple.rs index 328adf80c5..e990cfc402 100644 --- a/sdk/src/types/block/output/token_scheme/simple.rs +++ b/sdk/src/types/block/output/token_scheme/simple.rs @@ -5,13 +5,13 @@ use packable::Packable; use primitive_types::U256; use crate::types::block::{ + output::token_scheme::TokenSchemeError, protocol::{WorkScore, WorkScoreParameters}, - Error, }; /// #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = TokenSchemeError)] #[packable(verify_with = verify_simple_token_scheme)] pub struct SimpleTokenScheme { // Amount of tokens minted by a foundry. @@ -32,7 +32,7 @@ impl SimpleTokenScheme { minted_tokens: impl Into, melted_tokens: impl Into, maximum_supply: impl Into, - ) -> Result { + ) -> Result { let minted_tokens = minted_tokens.into(); let melted_tokens = melted_tokens.into(); let maximum_supply = maximum_supply.into(); @@ -43,7 +43,7 @@ impl SimpleTokenScheme { maximum_supply, }; - verify_simple_token_scheme::(&token_scheme)?; + verify_simple_token_scheme(&token_scheme)?; Ok(token_scheme) } @@ -80,13 +80,12 @@ impl WorkScore for SimpleTokenScheme { } #[inline] -fn verify_simple_token_scheme(token_scheme: &SimpleTokenScheme) -> Result<(), Error> { - if VERIFY - && (token_scheme.maximum_supply.is_zero() - || token_scheme.melted_tokens > token_scheme.minted_tokens - || token_scheme.minted_tokens - token_scheme.melted_tokens > token_scheme.maximum_supply) +fn verify_simple_token_scheme(token_scheme: &SimpleTokenScheme) -> Result<(), TokenSchemeError> { + if token_scheme.maximum_supply.is_zero() + || token_scheme.melted_tokens > token_scheme.minted_tokens + || token_scheme.minted_tokens - token_scheme.melted_tokens > token_scheme.maximum_supply { - return Err(Error::InvalidFoundryOutputSupply { + return Err(TokenSchemeError::Supply { minted: token_scheme.minted_tokens, melted: token_scheme.melted_tokens, max: token_scheme.maximum_supply, @@ -101,7 +100,6 @@ pub(crate) mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::types::block::Error; /// Describes a foundry output that is controlled by an account. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -129,7 +127,7 @@ pub(crate) mod dto { } impl TryFrom for SimpleTokenScheme { - type Error = Error; + type Error = TokenSchemeError; fn try_from(value: SimpleTokenSchemeDto) -> Result { Self::new(value.minted_tokens, value.melted_tokens, value.maximum_supply) diff --git a/sdk/src/types/block/output/unlock_condition/error.rs b/sdk/src/types/block/output/unlock_condition/error.rs new file mode 100644 index 0000000000..48f0bd8861 --- /dev/null +++ b/sdk/src/types/block/output/unlock_condition/error.rs @@ -0,0 +1,36 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use crate::types::block::{address::AddressError, output::unlock_condition::UnlockConditionCount}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum UnlockConditionError { + #[display(fmt = "invalid unlock condition kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid unlock condition count: {_0}")] + Count(>::Error), + #[display(fmt = "expiration unlock condition with milestone index and timestamp set to 0")] + ExpirationZero, + #[display(fmt = "timelock unlock condition with milestone index and timestamp set to 0")] + TimelockZero, + #[display(fmt = "unlock conditions are not unique and/or sorted")] + NotUniqueSorted, + #[display(fmt = "disallowed unlock condition at index {index} with kind {kind}")] + Disallowed { index: usize, kind: u8 }, + #[display(fmt = "missing slot index")] + MissingSlotIndex, + #[from] + Address(AddressError), +} + +#[cfg(feature = "std")] +impl std::error::Error for UnlockConditionError {} + +impl From for UnlockConditionError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/output/unlock_condition/expiration.rs b/sdk/src/types/block/output/unlock_condition/expiration.rs index 537a330a38..ba1da9f61c 100644 --- a/sdk/src/types/block/output/unlock_condition/expiration.rs +++ b/sdk/src/types/block/output/unlock_condition/expiration.rs @@ -4,16 +4,16 @@ use derive_more::From; use crate::types::block::{ - address::Address, - output::{StorageScore, StorageScoreParameters}, + address::{Address, AddressError}, + output::{unlock_condition::UnlockConditionError, StorageScore, StorageScoreParameters}, protocol::CommittableAgeRange, slot::SlotIndex, - Error, }; /// Defines an expiration slot index. Before the slot index is reached, only the Address defined in the Address /// Unlock Condition is allowed to unlock the output. Afterward, only the Return Address can unlock it. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] +#[packable(unpack_error = UnlockConditionError)] pub struct ExpirationUnlockCondition { // The address that can unlock the expired output. #[packable(verify_with = verify_return_address)] @@ -29,12 +29,15 @@ impl ExpirationUnlockCondition { /// Creates a new [`ExpirationUnlockCondition`]. #[inline(always)] - pub fn new(return_address: impl Into
, slot_index: impl Into) -> Result { + pub fn new( + return_address: impl Into
, + slot_index: impl Into, + ) -> Result { let slot_index = slot_index.into(); let return_address = return_address.into(); - verify_slot_index::(&slot_index)?; - verify_return_address::(&return_address)?; + verify_slot_index(&slot_index)?; + verify_return_address(&return_address)?; Ok(Self { return_address, @@ -95,18 +98,18 @@ impl StorageScore for ExpirationUnlockCondition { } #[inline] -fn verify_return_address(return_address: &Address) -> Result<(), Error> { - if VERIFY && return_address.is_implicit_account_creation() { - Err(Error::InvalidAddressKind(return_address.kind())) +fn verify_return_address(return_address: &Address) -> Result<(), UnlockConditionError> { + if return_address.is_implicit_account_creation() { + Err(AddressError::Kind(return_address.kind()).into()) } else { Ok(()) } } #[inline] -fn verify_slot_index(slot_index: &SlotIndex) -> Result<(), Error> { - if VERIFY && *slot_index == 0 { - Err(Error::ExpirationUnlockConditionZero) +fn verify_slot_index(slot_index: &SlotIndex) -> Result<(), UnlockConditionError> { + if *slot_index == 0 { + Err(UnlockConditionError::ExpirationZero) } else { Ok(()) } @@ -117,7 +120,6 @@ pub(crate) mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::types::block::Error; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -139,10 +141,10 @@ pub(crate) mod dto { } impl TryFrom for ExpirationUnlockCondition { - type Error = Error; + type Error = UnlockConditionError; - fn try_from(value: ExpirationUnlockConditionDto) -> Result { - Self::new(value.return_address, value.slot).map_err(|_| Error::InvalidField("expirationUnlockCondition")) + fn try_from(value: ExpirationUnlockConditionDto) -> Result { + Self::new(value.return_address, value.slot) } } diff --git a/sdk/src/types/block/output/unlock_condition/immutable_account_address.rs b/sdk/src/types/block/output/unlock_condition/immutable_account_address.rs index 8554f1c1b3..d2539be2a5 100644 --- a/sdk/src/types/block/output/unlock_condition/immutable_account_address.rs +++ b/sdk/src/types/block/output/unlock_condition/immutable_account_address.rs @@ -4,13 +4,13 @@ use derive_more::From; use crate::types::block::{ - address::{AccountAddress, Address}, - error::Error, - output::{StorageScore, StorageScoreParameters}, + address::{AccountAddress, Address, AddressError}, + output::{unlock_condition::UnlockConditionError, StorageScore, StorageScoreParameters}, }; /// Defines the permanent [`AccountAddress`] that owns this output. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] +#[packable(unpack_error = UnlockConditionError)] pub struct ImmutableAccountAddressUnlockCondition(#[packable(verify_with = verify_address)] Address); impl ImmutableAccountAddressUnlockCondition { @@ -37,9 +37,9 @@ impl StorageScore for ImmutableAccountAddressUnlockCondition { } #[inline] -fn verify_address(address: &Address) -> Result<(), Error> { - if VERIFY && !address.is_account() { - Err(Error::InvalidAddressKind(address.kind())) +fn verify_address(address: &Address) -> Result<(), UnlockConditionError> { + if !address.is_account() { + Err(AddressError::Kind(address.kind()).into()) } else { Ok(()) } diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index 489bd883c8..896f2ecd86 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 mod address; +mod error; mod expiration; mod governor_address; mod immutable_account_address; @@ -17,29 +18,28 @@ use iterator_sorted::is_unique_sorted; use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable}; pub use self::{ - address::AddressUnlockCondition, expiration::ExpirationUnlockCondition, + address::AddressUnlockCondition, error::UnlockConditionError, expiration::ExpirationUnlockCondition, governor_address::GovernorAddressUnlockCondition, immutable_account_address::ImmutableAccountAddressUnlockCondition, state_controller_address::StateControllerAddressUnlockCondition, storage_deposit_return::StorageDepositReturnUnlockCondition, timelock::TimelockUnlockCondition, }; use crate::types::block::{ - address::{Address, AddressCapabilityFlag, RestrictedAddress}, + address::{Address, AddressCapabilityFlag, AddressError, RestrictedAddress}, output::{ feature::NativeTokenFeature, AccountOutput, AnchorOutput, DelegationOutput, NftOutput, StorageScore, StorageScoreParameters, }, protocol::{CommittableAgeRange, ProtocolParameters, WorkScore}, slot::SlotIndex, - Error, }; /// #[derive(Clone, Eq, PartialEq, Hash, From, Packable)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] -#[packable(unpack_error = Error)] +#[packable(unpack_error = UnlockConditionError)] #[packable(unpack_visitor = ProtocolParameters)] -#[packable(tag_type = u8, with_error = Error::InvalidUnlockConditionKind)] +#[packable(tag_type = u8, with_error = UnlockConditionError::Kind)] pub enum UnlockCondition { /// An address unlock condition. #[packable(tag = AddressUnlockCondition::KIND)] @@ -163,14 +163,14 @@ pub(crate) type UnlockConditionCount = BoundedU8<0, { UnlockConditionFlags::ALL_ /// #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Deref, Packable)] -#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidUnlockConditionCount(p.into())))] +#[packable(unpack_error = UnlockConditionError, with = |e| e.unwrap_item_err_or_else(|p| UnlockConditionError::Count(p.into())))] #[packable(unpack_visitor = ProtocolParameters)] pub struct UnlockConditions( #[packable(verify_with = verify_unique_sorted_packable)] BoxedSlicePrefix, ); impl TryFrom> for UnlockConditions { - type Error = Error; + type Error = UnlockConditionError; #[inline(always)] fn try_from(unlock_conditions: Vec) -> Result { @@ -179,7 +179,7 @@ impl TryFrom> for UnlockConditions { } impl TryFrom> for UnlockConditions { - type Error = Error; + type Error = UnlockConditionError; #[inline(always)] fn try_from(unlock_conditions: BTreeSet) -> Result { @@ -198,26 +198,26 @@ impl IntoIterator for UnlockConditions { impl UnlockConditions { /// Creates a new [`UnlockConditions`] from a vec. - pub fn from_vec(unlock_conditions: Vec) -> Result { + pub fn from_vec(unlock_conditions: Vec) -> Result { let mut unlock_conditions = BoxedSlicePrefix::::try_from(unlock_conditions.into_boxed_slice()) - .map_err(Error::InvalidUnlockConditionCount)?; + .map_err(UnlockConditionError::Count)?; unlock_conditions.sort_by_key(UnlockCondition::kind); // Sort is obviously fine now but uniqueness still needs to be checked. - verify_unique_sorted::(&unlock_conditions)?; + verify_unique_sorted(&unlock_conditions)?; Ok(Self(unlock_conditions)) } /// Creates a new [`UnlockConditions`] from an ordered set. - pub fn from_set(unlock_conditions: BTreeSet) -> Result { + pub fn from_set(unlock_conditions: BTreeSet) -> Result { Ok(Self( unlock_conditions .into_iter() .collect::>() .try_into() - .map_err(Error::InvalidUnlockConditionCount)?, + .map_err(UnlockConditionError::Count)?, )) } @@ -307,9 +307,9 @@ impl UnlockConditions { address: &'a Address, slot_index: impl Into>, committable_age_range: CommittableAgeRange, - ) -> Result, Error> { + ) -> Result, UnlockConditionError> { let address = if let Some(expiration) = self.expiration() { - let slot_index = slot_index.into().ok_or(Error::MissingSlotIndex)?; + let slot_index = slot_index.into().ok_or(UnlockConditionError::MissingSlotIndex)?; expiration.return_address_expired(address, slot_index, committable_age_range) } else { Some(address) @@ -342,29 +342,29 @@ impl StorageScore for UnlockConditions { } #[inline] -fn verify_unique_sorted(unlock_conditions: &[UnlockCondition]) -> Result<(), Error> { - if VERIFY && !is_unique_sorted(unlock_conditions.iter().map(UnlockCondition::kind)) { - Err(Error::UnlockConditionsNotUniqueSorted) +fn verify_unique_sorted(unlock_conditions: &[UnlockCondition]) -> Result<(), UnlockConditionError> { + if !is_unique_sorted(unlock_conditions.iter().map(UnlockCondition::kind)) { + Err(UnlockConditionError::NotUniqueSorted) } else { Ok(()) } } #[inline] -fn verify_unique_sorted_packable( +fn verify_unique_sorted_packable( unlock_conditions: &[UnlockCondition], _: &ProtocolParameters, -) -> Result<(), Error> { - verify_unique_sorted::(unlock_conditions) +) -> Result<(), UnlockConditionError> { + verify_unique_sorted(unlock_conditions) } pub(crate) fn verify_allowed_unlock_conditions( unlock_conditions: &UnlockConditions, allowed_unlock_conditions: UnlockConditionFlags, -) -> Result<(), Error> { +) -> Result<(), UnlockConditionError> { for (index, unlock_condition) in unlock_conditions.iter().enumerate() { if !allowed_unlock_conditions.contains(unlock_condition.flag()) { - return Err(Error::UnallowedUnlockCondition { + return Err(UnlockConditionError::Disallowed { index, kind: unlock_condition.kind(), }); @@ -379,61 +379,52 @@ pub(crate) fn verify_restricted_addresses( output_kind: u8, native_token: Option<&NativeTokenFeature>, mana: u64, -) -> Result<(), Error> { +) -> Result<(), UnlockConditionError> { let addresses = unlock_conditions.restricted_addresses(); for address in addresses { if native_token.is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) { - return Err(Error::RestrictedAddressCapability( - AddressCapabilityFlag::OutputsWithNativeTokens, - )); + return Err( + AddressError::RestrictedAddressCapability(AddressCapabilityFlag::OutputsWithNativeTokens).into(), + ); } if mana > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { - return Err(Error::RestrictedAddressCapability( - AddressCapabilityFlag::OutputsWithMana, - )); + return Err(AddressError::RestrictedAddressCapability(AddressCapabilityFlag::OutputsWithMana).into()); } if unlock_conditions.timelock().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) { - return Err(Error::RestrictedAddressCapability( - AddressCapabilityFlag::OutputsWithTimelock, - )); + return Err(AddressError::RestrictedAddressCapability(AddressCapabilityFlag::OutputsWithTimelock).into()); } if unlock_conditions.expiration().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) { - return Err(Error::RestrictedAddressCapability( - AddressCapabilityFlag::OutputsWithExpiration, - )); + return Err(AddressError::RestrictedAddressCapability(AddressCapabilityFlag::OutputsWithExpiration).into()); } if unlock_conditions.storage_deposit_return().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) { - return Err(Error::RestrictedAddressCapability( + return Err(AddressError::RestrictedAddressCapability( AddressCapabilityFlag::OutputsWithStorageDepositReturn, - )); + ) + .into()); } match output_kind { AccountOutput::KIND if !address.has_capability(AddressCapabilityFlag::AccountOutputs) => { - return Err(Error::RestrictedAddressCapability( - AddressCapabilityFlag::AccountOutputs, - )); + return Err(AddressError::RestrictedAddressCapability(AddressCapabilityFlag::AccountOutputs).into()); } AnchorOutput::KIND if !address.has_capability(AddressCapabilityFlag::AnchorOutputs) => { - return Err(Error::RestrictedAddressCapability(AddressCapabilityFlag::AnchorOutputs)); + return Err(AddressError::RestrictedAddressCapability(AddressCapabilityFlag::AnchorOutputs).into()); } NftOutput::KIND if !address.has_capability(AddressCapabilityFlag::NftOutputs) => { - return Err(Error::RestrictedAddressCapability(AddressCapabilityFlag::NftOutputs)); + return Err(AddressError::RestrictedAddressCapability(AddressCapabilityFlag::NftOutputs).into()); } DelegationOutput::KIND if !address.has_capability(AddressCapabilityFlag::DelegationOutputs) => { - return Err(Error::RestrictedAddressCapability( - AddressCapabilityFlag::DelegationOutputs, - )); + return Err(AddressError::RestrictedAddressCapability(AddressCapabilityFlag::DelegationOutputs).into()); } _ => {} } diff --git a/sdk/src/types/block/output/unlock_condition/storage_deposit_return.rs b/sdk/src/types/block/output/unlock_condition/storage_deposit_return.rs index abe396db4a..12dc6c51ea 100644 --- a/sdk/src/types/block/output/unlock_condition/storage_deposit_return.rs +++ b/sdk/src/types/block/output/unlock_condition/storage_deposit_return.rs @@ -2,13 +2,16 @@ // SPDX-License-Identifier: Apache-2.0 use crate::types::block::{ - address::Address, - output::storage_score::{StorageScore, StorageScoreParameters}, - Error, + address::{Address, AddressError}, + output::{ + storage_score::{StorageScore, StorageScoreParameters}, + unlock_condition::UnlockConditionError, + }, }; /// Defines the amount of IOTAs used as storage deposit that have to be returned to the return [`Address`]. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable)] +#[packable(unpack_error = UnlockConditionError)] pub struct StorageDepositReturnUnlockCondition { // The [`Address`] to return the amount to. #[packable(verify_with = verify_return_address)] @@ -24,10 +27,10 @@ impl StorageDepositReturnUnlockCondition { /// Creates a new [`StorageDepositReturnUnlockCondition`]. #[inline(always)] - pub fn new(return_address: impl Into
, amount: u64) -> Result { + pub fn new(return_address: impl Into
, amount: u64) -> Result { let return_address = return_address.into(); - verify_return_address::(&return_address)?; + verify_return_address(&return_address)?; Ok(Self { return_address, amount }) } @@ -52,9 +55,9 @@ impl StorageScore for StorageDepositReturnUnlockCondition { } #[inline] -fn verify_return_address(return_address: &Address) -> Result<(), Error> { - if VERIFY && return_address.is_implicit_account_creation() { - Err(Error::InvalidAddressKind(return_address.kind())) +fn verify_return_address(return_address: &Address) -> Result<(), UnlockConditionError> { + if return_address.is_implicit_account_creation() { + Err(AddressError::Kind(return_address.kind()).into()) } else { Ok(()) } diff --git a/sdk/src/types/block/output/unlock_condition/timelock.rs b/sdk/src/types/block/output/unlock_condition/timelock.rs index cf17805ee0..fd7d8be9d1 100644 --- a/sdk/src/types/block/output/unlock_condition/timelock.rs +++ b/sdk/src/types/block/output/unlock_condition/timelock.rs @@ -3,11 +3,14 @@ use derive_more::From; -use crate::types::block::{output::StorageScore, slot::SlotIndex, Error}; +use crate::types::block::{ + output::{unlock_condition::UnlockConditionError, StorageScore}, + slot::SlotIndex, +}; /// Defines a slot index until which the output can not be unlocked. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, From, packable::Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = UnlockConditionError)] pub struct TimelockUnlockCondition(#[packable(verify_with = verify_slot_index)] SlotIndex); impl TimelockUnlockCondition { @@ -16,10 +19,10 @@ impl TimelockUnlockCondition { /// Creates a new [`TimelockUnlockCondition`]. #[inline(always)] - pub fn new(slot_index: impl Into) -> Result { + pub fn new(slot_index: impl Into) -> Result { let slot_index = slot_index.into(); - verify_slot_index::(&slot_index)?; + verify_slot_index(&slot_index)?; Ok(Self(slot_index)) } @@ -39,9 +42,9 @@ impl TimelockUnlockCondition { impl StorageScore for TimelockUnlockCondition {} #[inline] -fn verify_slot_index(slot_index: &SlotIndex) -> Result<(), Error> { - if VERIFY && *slot_index == 0 { - Err(Error::TimelockUnlockConditionZero) +fn verify_slot_index(slot_index: &SlotIndex) -> Result<(), UnlockConditionError> { + if *slot_index == 0 { + Err(UnlockConditionError::TimelockZero) } else { Ok(()) } @@ -52,7 +55,6 @@ pub(crate) mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::types::block::Error; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -72,10 +74,10 @@ pub(crate) mod dto { } impl TryFrom for TimelockUnlockCondition { - type Error = Error; + type Error = UnlockConditionError; - fn try_from(value: TimelockUnlockConditionDto) -> Result { - Self::new(value.slot).map_err(|_| Error::InvalidField("timelockUnlockCondition")) + fn try_from(value: TimelockUnlockConditionDto) -> Result { + Self::new(value.slot) } } diff --git a/sdk/src/types/block/payload/error.rs b/sdk/src/types/block/payload/error.rs new file mode 100644 index 0000000000..0ee5331cd8 --- /dev/null +++ b/sdk/src/types/block/payload/error.rs @@ -0,0 +1,90 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::string::String; +use core::convert::Infallible; + +use crate::types::block::{ + capabilities::CapabilityError, + context_input::ContextInputError, + input::{InputError, UtxoInput}, + mana::ManaError, + output::{ + feature::FeatureError, unlock_condition::UnlockConditionError, ChainId, NativeTokenError, OutputError, + TokenSchemeError, + }, + payload::{ + tagged_data::{TagLength, TaggedDataLength}, + InputCount, OutputCount, + }, + semantic::TransactionFailureReason, + unlock::UnlockError, +}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum PayloadError { + #[display(fmt = "invalid payload kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid payload length: expected {expected} but got {actual}")] + Length { expected: usize, actual: usize }, + #[display(fmt = "invalid timestamp: {_0}")] + Timestamp(String), + #[display(fmt = "invalid network id: {_0}")] + NetworkId(String), + #[display(fmt = "network ID mismatch: expected {expected} but got {actual}")] + NetworkIdMismatch { expected: u64, actual: u64 }, + #[display(fmt = "invalid tagged data length: {_0}")] + TaggedDataLength(>::Error), + #[display(fmt = "invalid tag length: {_0}")] + TagLength(>::Error), + #[display(fmt = "invalid input count: {_0}")] + InputCount(>::Error), + #[display(fmt = "invalid output count: {_0}")] + OutputCount(>::Error), + #[display(fmt = "invalid transaction amount sum: {_0}")] + TransactionAmountSum(u128), + #[display(fmt = "duplicate output chain: {_0}")] + DuplicateOutputChain(ChainId), + #[display(fmt = "duplicate UTXO {_0} in inputs")] + DuplicateUtxo(UtxoInput), + #[display(fmt = "missing creation slot")] + MissingCreationSlot, + #[display(fmt = "input count and unlock count mismatch: {input_count} != {unlock_count}")] + InputUnlockCountMismatch { input_count: usize, unlock_count: usize }, + #[display(fmt = "missing commitment context input for staking feature")] + MissingCommitmentInputForStakingFeature, + #[display(fmt = "missing commitment context input for block issuer feature")] + MissingCommitmentInputForBlockIssuerFeature, + #[display(fmt = "missing commitment context input for delegation output")] + MissingCommitmentInputForDelegationOutput, + #[from] + TransactionSemantic(TransactionFailureReason), + #[from] + Input(InputError), + #[from] + Output(OutputError), + #[from] + Unlock(UnlockError), + #[from] + ContextInput(ContextInputError), + #[from] + Capabilities(CapabilityError), +} + +#[cfg(feature = "std")] +impl std::error::Error for PayloadError {} + +crate::impl_from_error_via!(PayloadError via OutputError: + NativeTokenError, + ManaError, + UnlockConditionError, + FeatureError, + TokenSchemeError, +); + +impl From for PayloadError { + fn from(value: Infallible) -> Self { + match value {} + } +} diff --git a/sdk/src/types/block/payload/mod.rs b/sdk/src/types/block/payload/mod.rs index ceb5775cca..c2033ead0d 100644 --- a/sdk/src/types/block/payload/mod.rs +++ b/sdk/src/types/block/payload/mod.rs @@ -4,6 +4,7 @@ //! The payload module defines the core data types for representing block payloads. pub mod candidacy_announcement; +mod error; pub mod signed_transaction; pub mod tagged_data; @@ -20,19 +21,16 @@ use packable::{ pub(crate) use self::signed_transaction::{InputCount, OutputCount}; pub use self::{ - candidacy_announcement::CandidacyAnnouncementPayload, signed_transaction::SignedTransactionPayload, - tagged_data::TaggedDataPayload, -}; -use crate::types::block::{ - protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, - Error, + candidacy_announcement::CandidacyAnnouncementPayload, error::PayloadError, + signed_transaction::SignedTransactionPayload, tagged_data::TaggedDataPayload, }; +use crate::types::block::protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}; /// A generic payload that can represent different types defining block payloads. #[derive(Clone, Eq, PartialEq, From, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = PayloadError)] #[packable(unpack_visitor = ProtocolParameters)] -#[packable(tag_type = u8, with_error = Error::InvalidPayloadKind)] +#[packable(tag_type = u8, with_error = PayloadError::Kind)] pub enum Payload { /// A tagged data payload. #[packable(tag = TaggedDataPayload::KIND)] @@ -123,7 +121,7 @@ impl Deref for OptionalPayload { } impl Packable for OptionalPayload { - type UnpackError = Error; + type UnpackError = PayloadError; type UnpackVisitor = ProtocolParameters; fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -133,18 +131,18 @@ impl Packable for OptionalPayload { } } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { - let len = u32::unpack::<_, VERIFY>(unpacker, &()).coerce()? as usize; + let len = u32::unpack_inner(unpacker, visitor).coerce()? as usize; if len > 0 { unpacker.ensure_bytes(len)?; let start_opt = unpacker.read_bytes(); - let payload = Payload::unpack::<_, VERIFY>(unpacker, visitor)?; + let payload = Payload::unpack(unpacker, visitor)?; let actual_len = if let (Some(start), Some(end)) = (start_opt, unpacker.read_bytes()) { end - start @@ -153,7 +151,7 @@ impl Packable for OptionalPayload { }; if len != actual_len { - Err(UnpackError::Packable(Error::InvalidPayloadLength { + Err(UnpackError::Packable(PayloadError::Length { expected: len, actual: actual_len, })) @@ -172,7 +170,7 @@ pub mod dto { pub use super::signed_transaction::dto::SignedTransactionPayloadDto; use super::*; - use crate::types::{block::Error, TryFromDto}; + use crate::types::TryFromDto; /// Describes all the different payload types. #[derive(Clone, Debug, Eq, PartialEq, Serialize)] @@ -239,7 +237,7 @@ pub mod dto { } impl TryFromDto for Payload { - type Error = Error; + type Error = PayloadError; fn try_from_dto_with_params_inner( dto: PayloadDto, diff --git a/sdk/src/types/block/payload/signed_transaction/mod.rs b/sdk/src/types/block/payload/signed_transaction/mod.rs index 262315b4af..5f07908aa2 100644 --- a/sdk/src/types/block/payload/signed_transaction/mod.rs +++ b/sdk/src/types/block/payload/signed_transaction/mod.rs @@ -14,14 +14,14 @@ pub use self::{ transaction_id::{TransactionHash, TransactionId, TransactionSigningHash}, }; use crate::types::block::{ + payload::PayloadError, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, unlock::Unlocks, - Error, }; /// A signed transaction to move funds. #[derive(Clone, Debug, Eq, PartialEq, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = PayloadError)] #[packable(verify_with = verify_signed_transaction_payload)] pub struct SignedTransactionPayload { transaction: Transaction, @@ -33,10 +33,10 @@ impl SignedTransactionPayload { pub const KIND: u8 = 1; /// Creates a new [`SignedTransactionPayload`]. - pub fn new(transaction: Transaction, unlocks: Unlocks) -> Result { + pub fn new(transaction: Transaction, unlocks: Unlocks) -> Result { let payload = Self { transaction, unlocks }; - verify_signed_transaction_payload::(&payload)?; + verify_signed_transaction_payload(&payload)?; Ok(payload) } @@ -61,9 +61,9 @@ impl WorkScore for SignedTransactionPayload { } } -fn verify_signed_transaction_payload(payload: &SignedTransactionPayload) -> Result<(), Error> { +fn verify_signed_transaction_payload(payload: &SignedTransactionPayload) -> Result<(), PayloadError> { if payload.transaction.inputs().len() != payload.unlocks.len() { - return Err(Error::InputUnlockCountMismatch { + return Err(PayloadError::InputUnlockCountMismatch { input_count: payload.transaction.inputs().len(), unlock_count: payload.unlocks.len(), }); @@ -80,10 +80,7 @@ pub mod dto { pub use super::transaction::dto::TransactionDto; use super::*; - use crate::types::{ - block::{unlock::Unlock, Error}, - TryFromDto, - }; + use crate::types::{block::unlock::Unlock, TryFromDto}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct SignedTransactionPayloadDto { @@ -104,7 +101,7 @@ pub mod dto { } impl TryFromDto for SignedTransactionPayload { - type Error = Error; + type Error = PayloadError; fn try_from_dto_with_params_inner( dto: SignedTransactionPayloadDto, diff --git a/sdk/src/types/block/payload/signed_transaction/transaction.rs b/sdk/src/types/block/payload/signed_transaction/transaction.rs index c1744e5b7e..3f7b688708 100644 --- a/sdk/src/types/block/payload/signed_transaction/transaction.rs +++ b/sdk/src/types/block/payload/signed_transaction/transaction.rs @@ -5,22 +5,25 @@ use alloc::{collections::BTreeSet, vec::Vec}; use crypto::hashes::{blake2b::Blake2b256, Digest}; use hashbrown::HashSet; -use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable, PackableExt}; +use packable::{ + bounded::BoundedU16, + prefix::{BoxedSlicePrefix, UnpackPrefixError}, + Packable, PackableExt, +}; use crate::{ types::block::{ capabilities::{Capabilities, CapabilityFlag}, context_input::{ContextInput, ContextInputs}, - input::{Input, INPUT_COUNT_RANGE}, + input::{Input, InputError, INPUT_COUNT_RANGE}, mana::{verify_mana_allotments_sum, ManaAllotment, ManaAllotments}, - output::{Output, OUTPUT_COUNT_RANGE}, + output::{Output, OutputCommitmentProof, OutputError, OutputIdProof, ProofError, OUTPUT_COUNT_RANGE}, payload::{ signed_transaction::{TransactionHash, TransactionId, TransactionSigningHash}, - OptionalPayload, Payload, + OptionalPayload, Payload, PayloadError, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, slot::SlotIndex, - Error, }, utils::merkle_hasher, }; @@ -128,12 +131,12 @@ impl TransactionBuilder { pub fn finish_with_params<'a>( self, params: impl Into>, - ) -> Result { + ) -> Result { let params = params.into(); if let Some(protocol_parameters) = params { if self.network_id != protocol_parameters.network_id() { - return Err(Error::NetworkIdMismatch { + return Err(PayloadError::NetworkIdMismatch { expected: protocol_parameters.network_id(), actual: self.network_id, }); @@ -156,13 +159,13 @@ impl TransactionBuilder { let creation_slot = None; creation_slot }) - .ok_or(Error::InvalidField("creation slot"))?; + .ok_or(PayloadError::MissingCreationSlot)?; let inputs: BoxedSlicePrefix = self .inputs .into_boxed_slice() .try_into() - .map_err(Error::InvalidInputCount)?; + .map_err(PayloadError::InputCount)?; verify_inputs(&inputs)?; @@ -178,13 +181,13 @@ impl TransactionBuilder { .outputs .into_boxed_slice() .try_into() - .map_err(Error::InvalidOutputCount)?; + .map_err(PayloadError::OutputCount)?; if let Some(protocol_parameters) = params { - verify_outputs::(&outputs, protocol_parameters)?; + verify_outputs(&outputs, protocol_parameters)?; } - Ok(Transaction { + let transaction = Transaction { network_id: self.network_id, creation_slot, context_inputs: ContextInputs::from_vec(self.context_inputs)?, @@ -193,12 +196,16 @@ impl TransactionBuilder { capabilities: self.capabilities, payload: self.payload, outputs, - }) + }; + + verify_transaction(&transaction)?; + + Ok(transaction) } /// Finishes a [`TransactionBuilder`] into a [`Transaction`] without protocol /// validation. - pub fn finish(self) -> Result { + pub fn finish(self) -> Result { self.finish_with_params(None) } } @@ -208,8 +215,9 @@ pub(crate) type OutputCount = BoundedU16<{ *OUTPUT_COUNT_RANGE.start() }, { *OUT /// A transaction consuming inputs, creating outputs and carrying an optional payload. #[derive(Clone, Debug, Eq, PartialEq, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = PayloadError)] #[packable(unpack_visitor = ProtocolParameters)] +#[packable(verify_with = verify_transaction_packable)] pub struct Transaction { /// The unique value denoting whether the block was meant for mainnet, testnet, or a private network. #[packable(verify_with = verify_network_id)] @@ -218,17 +226,35 @@ pub struct Transaction { creation_slot: SlotIndex, context_inputs: ContextInputs, #[packable(verify_with = verify_inputs_packable)] - #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidInputCount(p.into())))] + #[packable(unpack_error_with = unpack_inputs_err)] inputs: BoxedSlicePrefix, allotments: ManaAllotments, capabilities: TransactionCapabilities, #[packable(verify_with = verify_payload_packable)] payload: OptionalPayload, #[packable(verify_with = verify_outputs)] - #[packable(unpack_error_with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidOutputCount(p.into())))] + #[packable(unpack_error_with = unpack_outputs_err)] outputs: BoxedSlicePrefix, } +fn unpack_inputs_err>::Error>>( + e: UnpackPrefixError, +) -> PayloadError { + match e { + UnpackPrefixError::Item(i) => i.into(), + UnpackPrefixError::Prefix(p) => PayloadError::InputCount(p.into()), + } +} + +fn unpack_outputs_err>::Error>>( + e: UnpackPrefixError, +) -> PayloadError { + match e { + UnpackPrefixError::Item(i) => i.into(), + UnpackPrefixError::Prefix(p) => PayloadError::OutputCount(p.into()), + } +} + impl Transaction { /// Creates a new [`TransactionBuilder`] to build a [`Transaction`]. pub fn builder(network_id: u64) -> TransactionBuilder { @@ -313,6 +339,18 @@ impl Transaction { merkle_hasher::MerkleHasher::digest::(&outputs_serialized).into() } + /// Returns a proof for the output in the transaction at the given index, + /// if the transaction has an output at that index. + pub fn output_id_proof(&self, index: u16) -> Result { + let output_commitment_proof = OutputCommitmentProof::new(&self.outputs, index)?; + Ok(OutputIdProof { + slot: self.creation_slot(), + output_index: index, + transaction_commitment: self.transaction_commitment(), + output_commitment_proof, + }) + } + /// Computes the identifier of a [`Transaction`]. pub fn id(&self) -> TransactionId { self.hash().into_transaction_id(self.creation_slot()) @@ -328,89 +366,102 @@ impl WorkScore for Transaction { } } -fn verify_network_id(network_id: &u64, visitor: &ProtocolParameters) -> Result<(), Error> { - if VERIFY { - let expected = visitor.network_id(); +fn verify_network_id(network_id: &u64, visitor: &ProtocolParameters) -> Result<(), PayloadError> { + let expected = visitor.network_id(); - if *network_id != expected { - return Err(Error::NetworkIdMismatch { - expected, - actual: *network_id, - }); - } + if *network_id != expected { + return Err(PayloadError::NetworkIdMismatch { + expected, + actual: *network_id, + }); } Ok(()) } -fn verify_inputs(inputs: &[Input]) -> Result<(), Error> { +fn verify_inputs(inputs: &[Input]) -> Result<(), PayloadError> { let mut seen_utxos = HashSet::new(); for input in inputs.iter() { let Input::Utxo(utxo) = input; if !seen_utxos.insert(utxo) { - return Err(Error::DuplicateUtxo(*utxo)); + return Err(PayloadError::DuplicateUtxo(*utxo)); } } Ok(()) } -fn verify_inputs_packable(inputs: &[Input], _visitor: &ProtocolParameters) -> Result<(), Error> { - if VERIFY { - verify_inputs(inputs)?; - } +fn verify_inputs_packable(inputs: &[Input], _visitor: &ProtocolParameters) -> Result<(), PayloadError> { + verify_inputs(inputs)?; Ok(()) } -fn verify_payload(payload: &OptionalPayload) -> Result<(), Error> { +fn verify_payload(payload: &OptionalPayload) -> Result<(), PayloadError> { match &payload.0 { Some(Payload::TaggedData(_)) | None => Ok(()), - Some(payload) => Err(Error::InvalidPayloadKind(payload.kind())), + Some(payload) => Err(PayloadError::Kind(payload.kind())), } } -fn verify_payload_packable( - payload: &OptionalPayload, - _visitor: &ProtocolParameters, -) -> Result<(), Error> { - if VERIFY { - verify_payload(payload)?; - } +fn verify_payload_packable(payload: &OptionalPayload, _visitor: &ProtocolParameters) -> Result<(), PayloadError> { + verify_payload(payload)?; Ok(()) } -fn verify_outputs(outputs: &[Output], visitor: &ProtocolParameters) -> Result<(), Error> { - if VERIFY { - let mut amount_sum: u64 = 0; - let mut chain_ids = HashSet::new(); - - for output in outputs.iter() { - let (amount, chain_id) = match output { - Output::Basic(output) => (output.amount(), None), - Output::Account(output) => (output.amount(), Some(output.chain_id())), - Output::Anchor(output) => (output.amount(), Some(output.chain_id())), - Output::Foundry(output) => (output.amount(), Some(output.chain_id())), - Output::Nft(output) => (output.amount(), Some(output.chain_id())), - Output::Delegation(output) => (output.amount(), Some(output.chain_id())), - }; +fn verify_outputs(outputs: &[Output], visitor: &ProtocolParameters) -> Result<(), PayloadError> { + let mut amount_sum: u64 = 0; + let mut chain_ids = HashSet::new(); + + for output in outputs.iter() { + let (amount, chain_id) = match output { + Output::Basic(output) => (output.amount(), None), + Output::Account(output) => (output.amount(), Some(output.chain_id())), + Output::Anchor(output) => (output.amount(), Some(output.chain_id())), + Output::Foundry(output) => (output.amount(), Some(output.chain_id())), + Output::Nft(output) => (output.amount(), Some(output.chain_id())), + Output::Delegation(output) => (output.amount(), Some(output.chain_id())), + }; + + amount_sum = amount_sum + .checked_add(amount) + .ok_or(PayloadError::TransactionAmountSum(amount_sum as u128 + amount as u128))?; + + // Accumulated output balance must not exceed the total supply of tokens. + if amount_sum > visitor.token_supply() { + return Err(PayloadError::TransactionAmountSum(amount_sum as u128)); + } + + if let Some(chain_id) = chain_id { + if !chain_id.is_null() && !chain_ids.insert(chain_id) { + return Err(PayloadError::DuplicateOutputChain(chain_id)); + } + } + + output.verify_storage_deposit(visitor.storage_score_parameters())?; + } - amount_sum = amount_sum - .checked_add(amount) - .ok_or(Error::InvalidTransactionAmountSum(amount_sum as u128 + amount as u128))?; + Ok(()) +} - // Accumulated output balance must not exceed the total supply of tokens. - if amount_sum > visitor.token_supply() { - return Err(Error::InvalidTransactionAmountSum(amount_sum as u128)); +fn verify_transaction_packable(transaction: &Transaction, _: &ProtocolParameters) -> Result<(), PayloadError> { + verify_transaction(transaction) +} + +fn verify_transaction(transaction: &Transaction) -> Result<(), PayloadError> { + if transaction.context_inputs().commitment().is_none() { + for output in transaction.outputs.iter() { + if output.features().is_some_and(|f| f.staking().is_some()) { + return Err(PayloadError::MissingCommitmentInputForStakingFeature); } - if let Some(chain_id) = chain_id { - if !chain_id.is_null() && !chain_ids.insert(chain_id) { - return Err(Error::DuplicateOutputChain(chain_id)); - } + if output.features().is_some_and(|f| f.block_issuer().is_some()) { + return Err(PayloadError::MissingCommitmentInputForBlockIssuerFeature); } - output.verify_storage_deposit(visitor.storage_score_parameters())?; + if output.is_delegation() { + return Err(PayloadError::MissingCommitmentInputForDelegationOutput); + } } } @@ -485,7 +536,7 @@ pub(crate) mod dto { use super::*; use crate::types::{ - block::{payload::dto::PayloadDto, Error}, + block::payload::{dto::PayloadDto, CandidacyAnnouncementPayload, SignedTransactionPayload}, TryFromDto, }; @@ -526,7 +577,7 @@ pub(crate) mod dto { } impl TryFromDto for Transaction { - type Error = Error; + type Error = PayloadError; fn try_from_dto_with_params_inner( dto: TransactionDto, @@ -535,7 +586,7 @@ pub(crate) mod dto { let network_id = dto .network_id .parse::() - .map_err(|_| Error::InvalidField("network_id"))?; + .map_err(|e| PayloadError::NetworkId(e.to_string()))?; let mut builder = Self::builder(network_id) .with_creation_slot(dto.creation_slot) @@ -546,10 +597,14 @@ pub(crate) mod dto { .with_outputs(dto.outputs); builder = if let Some(p) = dto.payload { - if let PayloadDto::TaggedData(i) = p { - builder.with_payload(*i) - } else { - return Err(Error::InvalidField("payload")); + match p { + PayloadDto::TaggedData(i) => builder.with_payload(*i), + PayloadDto::SignedTransaction(_) => { + return Err(PayloadError::Kind(SignedTransactionPayload::KIND)); + } + PayloadDto::CandidacyAnnouncement => { + return Err(PayloadError::Kind(CandidacyAnnouncementPayload::KIND)); + } } } else { builder diff --git a/sdk/src/types/block/payload/tagged_data.rs b/sdk/src/types/block/payload/tagged_data.rs index 0bdec46c8d..c562d04124 100644 --- a/sdk/src/types/block/payload/tagged_data.rs +++ b/sdk/src/types/block/payload/tagged_data.rs @@ -14,8 +14,8 @@ use packable::{ }; use crate::types::block::{ + payload::PayloadError, protocol::{WorkScore, WorkScoreParameters}, - Error, }; pub(crate) type TagLength = @@ -25,11 +25,11 @@ pub(crate) type TaggedDataLength = /// A payload which holds optional data with an optional tag. #[derive(Clone, Eq, PartialEq, Packable)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = PayloadError)] pub struct TaggedDataPayload { - #[packable(unpack_error_with = |err| Error::InvalidTagLength(err.into_prefix_err().into()))] + #[packable(unpack_error_with = |err| PayloadError::TagLength(err.into_prefix_err().into()))] tag: BoxedSlicePrefix, - #[packable(unpack_error_with = |err| Error::InvalidTaggedDataLength(err.into_prefix_err().into()))] + #[packable(unpack_error_with = |err| PayloadError::TaggedDataLength(err.into_prefix_err().into()))] data: BoxedSlicePrefix, } @@ -42,10 +42,10 @@ impl TaggedDataPayload { pub const DATA_LENGTH_RANGE: RangeInclusive = 0..=8192; /// Creates a new [`TaggedDataPayload`]. - pub fn new(tag: impl Into>, data: impl Into>) -> Result { + pub fn new(tag: impl Into>, data: impl Into>) -> Result { Ok(Self { - tag: tag.into().try_into().map_err(Error::InvalidTagLength)?, - data: data.into().try_into().map_err(Error::InvalidTaggedDataLength)?, + tag: tag.into().try_into().map_err(PayloadError::TagLength)?, + data: data.into().try_into().map_err(PayloadError::TaggedDataLength)?, }) } @@ -81,7 +81,7 @@ pub mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::{types::block::Error, utils::serde::prefix_hex_bytes}; + use crate::utils::serde::prefix_hex_bytes; /// The payload type to define a tagged data payload. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -105,7 +105,7 @@ pub mod dto { } impl TryFrom for TaggedDataPayload { - type Error = Error; + type Error = PayloadError; fn try_from(value: TaggedDataPayloadDto) -> Result { Self::new(value.tag, value.data) diff --git a/sdk/src/types/block/protocol/error.rs b/sdk/src/types/block/protocol/error.rs new file mode 100644 index 0000000000..661e148a47 --- /dev/null +++ b/sdk/src/types/block/protocol/error.rs @@ -0,0 +1,35 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::string::FromUtf8Error; +use core::convert::Infallible; + +use crate::types::block::{address::AddressError, mana::ManaError, protocol::ProtocolParametersHash}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum ProtocolParametersError { + #[display(fmt = "invalid network name: {_0}")] + NetworkName(FromUtf8Error), + #[display(fmt = "invalid mana decay factors")] + ManaDecayFactors, + StringPrefix(>::Error), + #[display(fmt = "invalid protocol parameters hash: expected {expected} but got {actual}")] + Hash { + expected: ProtocolParametersHash, + actual: ProtocolParametersHash, + }, + #[from] + ManaParameters(ManaError), + #[from] + Address(AddressError), +} + +#[cfg(feature = "std")] +impl std::error::Error for ProtocolParametersError {} + +impl From for ProtocolParametersError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/protocol/mod.rs b/sdk/src/types/block/protocol/mod.rs index f167ca14e9..92757829bf 100644 --- a/sdk/src/types/block/protocol/mod.rs +++ b/sdk/src/types/block/protocol/mod.rs @@ -1,31 +1,34 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod error; +#[cfg(feature = "protocol_parameters_samples")] +mod samples; mod work_score; -use alloc::string::String; use core::borrow::Borrow; use crypto::hashes::{blake2b::Blake2b256, Digest}; use getset::{CopyGetters, Getters}; use packable::{prefix::StringPrefix, Packable, PackableExt}; +#[cfg(feature = "protocol_parameters_samples")] +pub use samples::{iota_mainnet_protocol_parameters, shimmer_mainnet_protocol_parameters}; -pub use self::work_score::{WorkScore, WorkScoreParameters}; -use crate::{ - types::block::{ - address::Hrp, - helper::network_name_to_id, - mana::{ManaParameters, RewardsParameters}, - output::{StorageScore, StorageScoreParameters}, - slot::{EpochIndex, SlotCommitmentId, SlotIndex}, - Error, PROTOCOL_VERSION, - }, - utils::ConvertTo, +pub use self::{ + error::ProtocolParametersError, + work_score::{WorkScore, WorkScoreParameters}, +}; +use crate::types::block::{ + address::Hrp, + helper::network_name_to_id, + mana::{ManaParameters, RewardsParameters}, + output::{StorageScore, StorageScoreParameters}, + slot::{EpochIndex, SlotCommitmentId, SlotIndex}, }; /// Defines the parameters of the protocol at a particular version. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, Getters, CopyGetters)] -#[packable(unpack_error = Error)] +#[packable(unpack_error = ProtocolParametersError)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), @@ -39,7 +42,7 @@ pub struct ProtocolParameters { /// The version of the protocol running. pub(crate) version: u8, /// The human friendly name of the network. - #[packable(unpack_error_with = |err| Error::InvalidNetworkName(err.into_item_err()))] + #[packable(unpack_error_with = |err| ProtocolParametersError::NetworkName(err.into_item_err()))] #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string_prefix"))] #[getset(skip)] pub(crate) network_name: StringPrefix, @@ -103,65 +106,7 @@ impl Borrow<()> for ProtocolParameters { } } -impl Default for ProtocolParameters { - fn default() -> Self { - Self { - kind: 0, - version: PROTOCOL_VERSION, - // Unwrap: Known to be valid - network_name: String::from("iota-core-testnet").try_into().unwrap(), - bech32_hrp: Hrp::from_str_unchecked("smr"), - storage_score_parameters: Default::default(), - work_score_parameters: Default::default(), - token_supply: 1_813_620_509_061_365, - genesis_slot: 0, - genesis_unix_timestamp: 1582328545, - slot_duration_in_seconds: 10, - epoch_nearing_threshold: 20, - slots_per_epoch_exponent: Default::default(), - mana_parameters: Default::default(), - staking_unbonding_period: 10, - validation_blocks_per_slot: 10, - punishment_epochs: 9, - liveness_threshold_lower_bound: 15, - liveness_threshold_upper_bound: 30, - min_committable_age: 10, - max_committable_age: 20, - congestion_control_parameters: Default::default(), - version_signaling_parameters: Default::default(), - rewards_parameters: Default::default(), - target_committee_size: 32, - chain_switching_threshold: 3, - } - } -} - impl ProtocolParameters { - /// Creates a new [`ProtocolParameters`]. - #[allow(clippy::too_many_arguments)] - pub fn new( - version: u8, - network_name: impl Into, - bech32_hrp: impl ConvertTo, - storage_score_parameters: StorageScoreParameters, - token_supply: u64, - genesis_unix_timestamp: u64, - slot_duration_in_seconds: u8, - epoch_nearing_threshold: u32, - ) -> Result { - Ok(Self { - version, - network_name: >::try_from(network_name.into()).map_err(Error::InvalidStringPrefix)?, - bech32_hrp: bech32_hrp.convert()?, - storage_score_parameters, - token_supply, - genesis_unix_timestamp, - slot_duration_in_seconds, - epoch_nearing_threshold, - ..Default::default() - }) - } - /// Returns the network name of the [`ProtocolParameters`]. pub fn network_name(&self) -> &str { &self.network_name @@ -273,6 +218,11 @@ impl ProtocolParameters { slot_commitment_id.past_bounded_slot(self.max_committable_age()) } + /// Calculates the past bounded epoch for the given slot of the SlotCommitment. + pub fn past_bounded_epoch(&self, slot_commitment_id: SlotCommitmentId) -> EpochIndex { + self.epoch_index_of(self.past_bounded_slot(slot_commitment_id)) + } + /// Calculates the future bounded slot for the given slot of the SlotCommitment. /// Given any slot index of a commitment input, the result of this function is a slot index /// that is at most equal to the slot of the block in which it was issued, or lower. @@ -282,6 +232,11 @@ impl ProtocolParameters { slot_commitment_id.future_bounded_slot(self.min_committable_age()) } + /// Calculates the future bounded epoch for the given slot of the SlotCommitment. + pub fn future_bounded_epoch(&self, slot_commitment_id: SlotCommitmentId) -> EpochIndex { + self.epoch_index_of(self.future_bounded_slot(slot_commitment_id)) + } + /// Returns the slot at the end of which the validator and delegator registration ends and the voting power /// for the epoch is calculated. pub fn registration_slot(&self, epoch_index: EpochIndex) -> SlotIndex { @@ -294,29 +249,29 @@ impl ProtocolParameters { /// Gets the start epoch for a delegation with the given slot commitment id. pub fn delegation_start_epoch(&self, slot_commitment_id: SlotCommitmentId) -> EpochIndex { - let past_bounded_slot_index = self.past_bounded_slot(slot_commitment_id); - let past_bounded_epoch_index = self.epoch_index_of(past_bounded_slot_index); + let past_bounded_slot = self.past_bounded_slot(slot_commitment_id); + let past_bounded_epoch = self.epoch_index_of(past_bounded_slot); - let registration_slot = self.registration_slot(past_bounded_epoch_index + 1); + let registration_slot = self.registration_slot(past_bounded_epoch + 1); - if past_bounded_slot_index <= registration_slot { - past_bounded_epoch_index + 1 + if past_bounded_slot <= registration_slot { + past_bounded_epoch + 1 } else { - past_bounded_epoch_index + 2 + past_bounded_epoch + 2 } } /// Gets the end epoch for a delegation with the given slot commitment id pub fn delegation_end_epoch(&self, slot_commitment_id: SlotCommitmentId) -> EpochIndex { - let future_bounded_slot_index = self.future_bounded_slot(slot_commitment_id); - let future_bounded_epoch_index = self.epoch_index_of(future_bounded_slot_index); + let future_bounded_slot = self.future_bounded_slot(slot_commitment_id); + let future_bounded_epoch = self.epoch_index_of(future_bounded_slot); - let registration_slot = self.registration_slot(future_bounded_epoch_index + 1); + let registration_slot = self.registration_slot(future_bounded_epoch + 1); - if future_bounded_slot_index <= registration_slot { - future_bounded_epoch_index + if future_bounded_slot <= registration_slot { + future_bounded_epoch } else { - future_bounded_epoch_index + 1 + future_bounded_epoch + 1 } } @@ -348,43 +303,28 @@ pub struct CommittableAgeRange { derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] -#[packable(unpack_error = Error)] +#[packable(unpack_error = ProtocolParametersError)] #[getset(get_copy = "pub")] pub struct CongestionControlParameters { /// Minimum value of the reference Mana cost. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - min_reference_mana_cost: u64, + pub(crate) min_reference_mana_cost: u64, /// Increase step size of the RMC. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - increase: u64, + pub(crate) increase: u64, /// Decrease step size of the RMC. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] - decrease: u64, + pub(crate) decrease: u64, /// Threshold for increasing the RMC. - increase_threshold: u32, + pub(crate) increase_threshold: u32, /// Threshold for decreasing the RMC. - decrease_threshold: u32, + pub(crate) decrease_threshold: u32, /// Rate at which the scheduler runs (in workscore units per second). - scheduler_rate: u32, + pub(crate) scheduler_rate: u32, /// Maximum size of the buffer in the scheduler. - max_buffer_size: u32, + pub(crate) max_buffer_size: u32, /// Maximum number of blocks in the validation buffer. - max_validation_buffer_size: u32, -} - -impl Default for CongestionControlParameters { - fn default() -> Self { - Self { - min_reference_mana_cost: 500, - increase: 500, - decrease: 500, - increase_threshold: 800000, - decrease_threshold: 500000, - scheduler_rate: 100000, - max_buffer_size: 3276800, - max_validation_buffer_size: 100, - } - } + pub(crate) max_validation_buffer_size: u32, } /// Defines the parameters used to signal a protocol parameters upgrade. @@ -394,42 +334,16 @@ impl Default for CongestionControlParameters { derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] -#[packable(unpack_error = Error)] +#[packable(unpack_error = ProtocolParametersError)] #[getset(get_copy = "pub")] pub struct VersionSignalingParameters { /// The size of the window in epochs that is used to find which version of protocol parameters was /// most signaled, from `current_epoch - window_size` to `current_epoch`. - window_size: u8, + pub(crate) window_size: u8, /// The number of supporters required for a version to win within a `window_size`. - window_target_ratio: u8, + pub(crate) window_target_ratio: u8, /// The offset in epochs required to activate the new version of protocol parameters. - activation_offset: u8, -} - -impl Default for VersionSignalingParameters { - fn default() -> Self { - Self { - window_size: 7, - window_target_ratio: 5, - activation_offset: 7, - } - } -} - -/// Returns a [`ProtocolParameters`] for testing purposes. -#[cfg(any(feature = "test", feature = "rand"))] -pub fn protocol_parameters() -> ProtocolParameters { - ProtocolParameters::new( - 2, - "testnet", - "rms", - crate::types::block::output::StorageScoreParameters::new(500, 1, 10, 1, 1, 1), - 1_813_620_509_061_365, - 1582328545, - 10, - 20, - ) - .unwrap() + pub(crate) activation_offset: u8, } crate::impl_id!( diff --git a/sdk/src/types/block/protocol/samples.rs b/sdk/src/types/block/protocol/samples.rs new file mode 100644 index 0000000000..822c1204ec --- /dev/null +++ b/sdk/src/types/block/protocol/samples.rs @@ -0,0 +1,252 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::OnceLock; + +use crate::types::block::{ + address::Hrp, + mana::{ManaParameters, RewardsParameters}, + output::StorageScoreParameters, + protocol::{CongestionControlParameters, ProtocolParameters, VersionSignalingParameters, WorkScoreParameters}, + PROTOCOL_VERSION, +}; + +/// Returns IOTA mainnet [`ProtocolParameters`] for testing purposes. +pub fn iota_mainnet_protocol_parameters() -> &'static ProtocolParameters { + static PARAMS: OnceLock = OnceLock::new(); + PARAMS.get_or_init(|| { + ProtocolParameters { + kind: 0, + version: PROTOCOL_VERSION, + network_name: String::from("iota-mainnet").try_into().unwrap(), + bech32_hrp: Hrp::from_str_unchecked("iota"), + storage_score_parameters: StorageScoreParameters { + storage_cost: 100, + factor_data: 1, + offset_output_overhead: 10, + offset_ed25519_block_issuer_key: 100, + offset_staking_feature: 100, + offset_delegation: 100, + }, + work_score_parameters: WorkScoreParameters { + data_byte: 1, + block: 1500, + input: 10, + context_input: 20, + output: 20, + native_token: 20, + staking: 5000, + block_issuer: 1000, + allotment: 1000, + signature_ed25519: 1000, + }, + token_supply: 1_813_620_509_061_365, + genesis_slot: 0, + #[cfg(not(target_family = "wasm"))] + genesis_unix_timestamp: time::OffsetDateTime::now_utc().unix_timestamp() as _, + #[cfg(target_family = "wasm")] + genesis_unix_timestamp: instant::SystemTime::now() + .duration_since(instant::SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(), + slot_duration_in_seconds: 10, + epoch_nearing_threshold: 60, + slots_per_epoch_exponent: 13, + mana_parameters: ManaParameters { + bits_count: 63, + generation_rate: 1, + generation_rate_exponent: 17, + // Derived + decay_factors: Default::default(), + decay_factors_exponent: 32, + // Derived + decay_factor_epochs_sum: Default::default(), + decay_factor_epochs_sum_exponent: 21, + annual_decay_factor_percentage: 70, + }, + staking_unbonding_period: 10, + validation_blocks_per_slot: 10, + punishment_epochs: 10, + liveness_threshold_lower_bound: 15, + liveness_threshold_upper_bound: 30, + min_committable_age: 10, + max_committable_age: 20, + congestion_control_parameters: CongestionControlParameters { + min_reference_mana_cost: 1, + increase: 10, + decrease: 10, + increase_threshold: 800000, + decrease_threshold: 500000, + scheduler_rate: 100000, + max_buffer_size: 1000, + max_validation_buffer_size: 100, + }, + version_signaling_parameters: VersionSignalingParameters { + window_size: 7, + window_target_ratio: 5, + activation_offset: 7, + }, + rewards_parameters: RewardsParameters { + profit_margin_exponent: 8, + // Derived + bootstrapping_duration: Default::default(), + reward_to_generation_ratio: 2, + // Derived + initial_target_rewards_rate: Default::default(), + // Derived + final_target_rewards_rate: Default::default(), + pool_coefficient_exponent: 11, + retention_period: 384, + }, + target_committee_size: 32, + chain_switching_threshold: 3, + } + .with_derived_values() + }) +} + +/// Returns Shimmer mainnet [`ProtocolParameters`] for testing purposes. +pub fn shimmer_mainnet_protocol_parameters() -> &'static ProtocolParameters { + static PARAMS: OnceLock = OnceLock::new(); + PARAMS.get_or_init(|| { + ProtocolParameters { + kind: 0, + version: PROTOCOL_VERSION, + network_name: String::from("shimmer-mainnet").try_into().unwrap(), + bech32_hrp: Hrp::from_str_unchecked("smr"), + storage_score_parameters: StorageScoreParameters { + storage_cost: 100, + factor_data: 1, + offset_output_overhead: 10, + offset_ed25519_block_issuer_key: 100, + offset_staking_feature: 100, + offset_delegation: 100, + }, + work_score_parameters: WorkScoreParameters { + data_byte: 0, + block: 1, + input: 0, + context_input: 0, + output: 0, + native_token: 0, + staking: 0, + block_issuer: 0, + allotment: 0, + signature_ed25519: 0, + }, + token_supply: 1_813_620_509_061_365, + genesis_slot: 0, + #[cfg(not(target_family = "wasm"))] + genesis_unix_timestamp: time::OffsetDateTime::now_utc().unix_timestamp() as _, + #[cfg(target_family = "wasm")] + genesis_unix_timestamp: instant::SystemTime::now() + .duration_since(instant::SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(), + slot_duration_in_seconds: 10, + epoch_nearing_threshold: 60, + slots_per_epoch_exponent: 13, + mana_parameters: ManaParameters { + bits_count: 63, + generation_rate: 1, + generation_rate_exponent: 17, + // Derived + decay_factors: Default::default(), + decay_factors_exponent: 32, + // Derived + decay_factor_epochs_sum: Default::default(), + decay_factor_epochs_sum_exponent: 21, + annual_decay_factor_percentage: 70, + }, + staking_unbonding_period: 10, + validation_blocks_per_slot: 10, + punishment_epochs: 10, + liveness_threshold_lower_bound: 15, + liveness_threshold_upper_bound: 30, + min_committable_age: 10, + max_committable_age: 20, + congestion_control_parameters: CongestionControlParameters { + min_reference_mana_cost: 1, + increase: 10, + decrease: 10, + increase_threshold: 800000, + decrease_threshold: 500000, + scheduler_rate: 100000, + max_buffer_size: 1000, + max_validation_buffer_size: 100, + }, + version_signaling_parameters: VersionSignalingParameters { + window_size: 7, + window_target_ratio: 5, + activation_offset: 7, + }, + rewards_parameters: RewardsParameters { + profit_margin_exponent: 8, + // Derived + bootstrapping_duration: Default::default(), + reward_to_generation_ratio: 2, + // Derived + initial_target_rewards_rate: Default::default(), + // Derived + final_target_rewards_rate: Default::default(), + pool_coefficient_exponent: 11, + retention_period: 384, + }, + target_committee_size: 32, + chain_switching_threshold: 3, + } + .with_derived_values() + }) +} + +impl ProtocolParameters { + pub(crate) fn with_derived_values(mut self) -> Self { + self.derive_mana_decay_factors(); + self.derive_mana_decay_factors_epochs_sum(); + self.derive_bootstrapping_duration(); + self.derive_target_rewards_rates(); + self + } + + pub(crate) fn derive_mana_decay_factors(&mut self) { + self.mana_parameters.decay_factors = { + let epochs_in_table = (u16::MAX as usize).min(self.epochs_per_year().floor() as usize); + let decay_per_epoch = self.decay_per_epoch(); + (1..=epochs_in_table) + .map(|epoch| { + (decay_per_epoch.powi(epoch as _) * 2f64.powi(self.mana_parameters().decay_factors_exponent() as _)) + .floor() as u32 + }) + .collect::>() + } + .try_into() + .unwrap(); + } + + pub(crate) fn derive_mana_decay_factors_epochs_sum(&mut self) { + self.mana_parameters.decay_factor_epochs_sum = { + let delta = self.epochs_per_year().recip(); + let annual_decay_factor = self.mana_parameters().annual_decay_factor(); + (annual_decay_factor.powf(delta) / (1.0 - annual_decay_factor.powf(delta)) + * (2f64.powi(self.mana_parameters().decay_factor_epochs_sum_exponent() as _))) + .floor() as _ + }; + } + + pub(crate) fn derive_bootstrapping_duration(&mut self) { + self.rewards_parameters.bootstrapping_duration = + (self.epochs_per_year() / -self.mana_parameters().annual_decay_factor().ln()).floor() as _; + } + + pub(crate) fn derive_target_rewards_rates(&mut self) { + self.rewards_parameters.final_target_rewards_rate = (self.token_supply() + * self.rewards_parameters().reward_to_generation_ratio() as u64 + * self.mana_parameters().generation_rate() as u64) + >> (self.mana_parameters().generation_rate_exponent() - self.slots_per_epoch_exponent()); + let bootstrapping_duration_years = + self.rewards_parameters().bootstrapping_duration() as f64 * self.epochs_per_year().exp(); + self.rewards_parameters.initial_target_rewards_rate = (self.rewards_parameters.final_target_rewards_rate as f64 + * (self.mana_parameters().annual_decay_factor() * bootstrapping_duration_years).exp()) + .floor() as _; + } +} diff --git a/sdk/src/types/block/protocol/work_score.rs b/sdk/src/types/block/protocol/work_score.rs index fc96021a64..290ae64e3c 100644 --- a/sdk/src/types/block/protocol/work_score.rs +++ b/sdk/src/types/block/protocol/work_score.rs @@ -4,54 +4,34 @@ use getset::CopyGetters; use packable::Packable; -use crate::types::block::Error; - #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable, CopyGetters)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(rename_all = "camelCase") )] -#[packable(unpack_error = Error)] #[getset(get_copy = "pub")] pub struct WorkScoreParameters { /// Accounts for the network traffic per byte. - data_byte: u32, + pub(crate) data_byte: u32, /// Accounts for work done to process a block in the node software. - block: u32, + pub(crate) block: u32, /// Accounts for loading the UTXO from the database and performing the mana balance check. - input: u32, + pub(crate) input: u32, /// Accounts for loading and checking the context input. - context_input: u32, + pub(crate) context_input: u32, /// Accounts for storing the UTXO in the database. - output: u32, + pub(crate) output: u32, /// Accounts for native token balance checks which use big integers. - native_token: u32, + pub(crate) native_token: u32, /// Accounts for the cost of updating the staking vector when a staking feature is present. - staking: u32, + pub(crate) staking: u32, /// Accounts for the cost of updating the block issuer keys when a block issuer feature is present. - block_issuer: u32, + pub(crate) block_issuer: u32, /// Accounts for accessing the account based ledger to transform the allotted mana to block issuance credits. - allotment: u32, + pub(crate) allotment: u32, /// Accounts for an Ed25519 signature check. - signature_ed25519: u32, -} - -impl Default for WorkScoreParameters { - fn default() -> Self { - Self { - data_byte: 0, - block: 100, - input: 20, - context_input: 20, - output: 20, - native_token: 20, - staking: 100, - block_issuer: 100, - allotment: 100, - signature_ed25519: 200, - } - } + pub(crate) signature_ed25519: u32, } /// A trait to facilitate the computation of the work score of a block, which is central to mana cost calculation. diff --git a/sdk/src/types/block/semantic/error.rs b/sdk/src/types/block/semantic/error.rs index b0b2f1ac32..a9ac1ba4a8 100644 --- a/sdk/src/types/block/semantic/error.rs +++ b/sdk/src/types/block/semantic/error.rs @@ -1,266 +1,193 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::fmt; +use core::convert::Infallible; -use crate::types::block::Error; +#[derive(Debug, PartialEq, Eq, derive_more::Display)] +#[display(fmt = "invalid transaction failure reason: {_0}")] +pub struct InvalidTransactionFailureReasonError(u8); + +impl From for InvalidTransactionFailureReasonError { + fn from(value: Infallible) -> Self { + match value {} + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionFailureReason {} /// Describes the reason of a transaction failure. #[repr(u8)] #[derive( - Debug, Copy, Clone, Eq, PartialEq, packable::Packable, strum::FromRepr, strum::EnumString, strum::AsRefStr, + Debug, + Copy, + Clone, + Eq, + PartialEq, + packable::Packable, + strum::FromRepr, + strum::EnumString, + derive_more::Display, + strum::AsRefStr, )] #[cfg_attr(feature = "serde", derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr))] #[strum(serialize_all = "camelCase")] -#[packable(unpack_error = Error)] -#[packable(tag_type = u8, with_error = Error::InvalidTransactionFailureReason)] +#[packable(unpack_error = InvalidTransactionFailureReasonError)] +#[packable(tag_type = u8, with_error = InvalidTransactionFailureReasonError)] #[non_exhaustive] pub enum TransactionFailureReason { + #[display(fmt = "none")] None = 0, + #[display(fmt = "transaction was conflicting and was rejected")] ConflictRejected = 1, + #[display(fmt = "input already spent")] InputAlreadySpent = 2, + #[display(fmt = "input creation slot after tx creation slot")] InputCreationAfterTxCreation = 3, + #[display(fmt = "signature in unlock is invalid")] UnlockSignatureInvalid = 4, + #[display(fmt = "invalid unlock for chain address")] ChainAddressUnlockInvalid = 5, + #[display(fmt = "invalid unlock for direct unlockable address")] DirectUnlockableAddressUnlockInvalid = 6, + #[display(fmt = "invalid unlock for multi address")] MultiAddressUnlockInvalid = 7, + #[display(fmt = "commitment input references an invalid or non-existent commitment")] CommitmentInputReferenceInvalid = 8, + #[display(fmt = "BIC input reference cannot be loaded")] BicInputReferenceInvalid = 9, + #[display(fmt = "reward input does not reference a staking account or a delegation output")] RewardInputReferenceInvalid = 10, + #[display(fmt = "staking rewards could not be calculated due to storage issues or overflow")] StakingRewardCalculationFailure = 11, + #[display(fmt = "delegation rewards could not be calculated due to storage issues or overflow")] DelegationRewardCalculationFailure = 12, + #[display(fmt = "inputs and outputs do not spend/deposit the same amount of base tokens")] InputOutputBaseTokenMismatch = 13, + #[display(fmt = "under- or overflow in Mana calculations")] ManaOverflow = 14, + #[display(fmt = "inputs and outputs do not contain the same amount of mana")] InputOutputManaMismatch = 15, + #[display(fmt = "mana decay creation slot/epoch index exceeds target slot/epoch index")] ManaDecayCreationIndexExceedsTargetIndex = 16, + #[display(fmt = "native token sums are unbalanced")] NativeTokenSumUnbalanced = 17, + #[display(fmt = "simple token scheme's minted or melted tokens decreased")] SimpleTokenSchemeMintedMeltedTokenDecrease = 18, + #[strum( + to_string = "simple token scheme's minted tokens did not increase by the minted amount or melted tokens changed" + )] SimpleTokenSchemeMintingInvalid = 19, + #[strum( + to_string = "simple token scheme's melted tokens did not increase by the melted amount or minted tokens changed" + )] SimpleTokenSchemeMeltingInvalid = 20, + #[display(fmt = "simple token scheme's maximum supply cannot change during transition")] SimpleTokenSchemeMaximumSupplyChanged = 21, + #[strum( + to_string = "newly created simple token scheme's melted tokens are not zero or minted tokens do not equal native token amount in transaction" + )] SimpleTokenSchemeGenesisInvalid = 22, + #[display(fmt = "multi address length and multi unlock length do not match")] MultiAddressLengthUnlockLengthMismatch = 23, + #[display(fmt = "multi address unlock threshold not reached")] MultiAddressUnlockThresholdNotReached = 24, + #[display(fmt = "sender feature is not unlocked")] SenderFeatureNotUnlocked = 25, + #[display(fmt = "issuer feature is not unlocked")] IssuerFeatureNotUnlocked = 26, + #[display(fmt = "staking feature removal or resetting requires a reward input")] StakingRewardInputMissing = 27, - StakingBlockIssuerFeatureMissing = 28, - StakingCommitmentInputMissing = 29, - StakingRewardClaimingInvalid = 30, - StakingFeatureRemovedBeforeUnbonding = 31, - StakingFeatureModifiedBeforeUnbonding = 32, - StakingStartEpochInvalid = 33, - StakingEndEpochTooEarly = 34, - BlockIssuerCommitmentInputMissing = 35, - BlockIssuanceCreditInputMissing = 36, - BlockIssuerNotExpired = 37, - BlockIssuerExpiryTooEarly = 38, - ManaMovedOffBlockIssuerAccount = 39, - AccountLocked = 40, - TimelockCommitmentInputMissing = 41, - TimelockNotExpired = 42, - ExpirationCommitmentInputMissing = 43, - ExpirationNotUnlockable = 44, - ReturnAmountNotFulFilled = 45, - NewChainOutputHasNonZeroedId = 46, - ChainOutputImmutableFeaturesChanged = 47, - ImplicitAccountDestructionDisallowed = 48, - MultipleImplicitAccountCreationAddresses = 49, - AccountInvalidFoundryCounter = 50, - AnchorInvalidStateTransition = 51, - AnchorInvalidGovernanceTransition = 52, - FoundryTransitionWithoutAccount = 53, - FoundrySerialInvalid = 54, - DelegationCommitmentInputMissing = 55, - DelegationRewardInputMissing = 56, - DelegationRewardsClaimingInvalid = 57, - DelegationOutputTransitionedTwice = 58, - DelegationModified = 59, - DelegationStartEpochInvalid = 60, - DelegationAmountMismatch = 61, - DelegationEndEpochNotZero = 62, - DelegationEndEpochInvalid = 63, - CapabilitiesNativeTokenBurningNotAllowed = 64, - CapabilitiesManaBurningNotAllowed = 65, - CapabilitiesAccountDestructionNotAllowed = 66, - CapabilitiesAnchorDestructionNotAllowed = 67, - CapabilitiesFoundryDestructionNotAllowed = 68, - CapabilitiesNftDestructionNotAllowed = 69, + #[display(fmt = "staking feature validation requires a commitment input")] + StakingCommitmentInputMissing = 28, + #[display(fmt = "staking feature must be removed or reset in order to claim rewards")] + StakingRewardClaimingInvalid = 29, + #[display(fmt = "staking feature can only be removed after the unbonding period")] + StakingFeatureRemovedBeforeUnbonding = 30, + #[display(fmt = "staking start epoch, fixed cost and staked amount cannot be modified while bonded")] + StakingFeatureModifiedBeforeUnbonding = 31, + #[display(fmt = "staking start epoch must be the epoch of the transaction")] + StakingStartEpochInvalid = 32, + #[display(fmt = "staking end epoch must be set to the transaction epoch plus the unbonding period")] + StakingEndEpochTooEarly = 33, + #[display(fmt = "commitment input missing for block issuer feature")] + BlockIssuerCommitmentInputMissing = 34, + #[display(fmt = "block issuance credit input missing for account with block issuer feature")] + BlockIssuanceCreditInputMissing = 35, + #[display(fmt = "block issuer feature has not expired")] + BlockIssuerNotExpired = 36, + #[display(fmt = "block issuer feature expiry set too early")] + BlockIssuerExpiryTooEarly = 37, + #[display(fmt = "mana cannot be moved off block issuer accounts except with manalocks")] + ManaMovedOffBlockIssuerAccount = 38, + #[display(fmt = "account is locked due to negative block issuance credits")] + AccountLocked = 39, + #[display(fmt = "transaction's containing a timelock condition require a commitment input")] + TimelockCommitmentInputMissing = 40, + #[display(fmt = "timelock not expired")] + TimelockNotExpired = 41, + #[display(fmt = "transaction's containing an expiration condition require a commitment input")] + ExpirationCommitmentInputMissing = 42, + #[display(fmt = "expiration unlock condition cannot be unlocked")] + ExpirationNotUnlockable = 43, + #[display(fmt = "return amount not fulfilled")] + ReturnAmountNotFulFilled = 44, + #[display(fmt = "new chain output has non-zeroed ID")] + NewChainOutputHasNonZeroedId = 45, + #[display(fmt = "immutable features in chain output modified during transition")] + ChainOutputImmutableFeaturesChanged = 46, + #[display(fmt = "cannot destroy implicit account; must be transitioned to account")] + ImplicitAccountDestructionDisallowed = 47, + #[display(fmt = "multiple implicit account creation addresses on the input side")] + MultipleImplicitAccountCreationAddresses = 48, + #[display(fmt = "foundry counter in account decreased or did not increase by the number of new foundries")] + AccountInvalidFoundryCounter = 49, + #[display(fmt = "invalid anchor state transition")] + AnchorInvalidStateTransition = 50, + #[display(fmt = "invalid anchor governance transition")] + AnchorInvalidGovernanceTransition = 51, + #[display(fmt = "foundry output transitioned without accompanying account on input or output side")] + FoundryTransitionWithoutAccount = 52, + #[display(fmt = "foundry output serial number is invalid")] + FoundrySerialInvalid = 53, + #[display(fmt = "delegation output validation requires a commitment input")] + DelegationCommitmentInputMissing = 54, + #[display(fmt = "delegation output cannot be destroyed without a reward input")] + DelegationRewardInputMissing = 55, + #[display(fmt = "invalid delegation mana rewards claiming")] + DelegationRewardsClaimingInvalid = 56, + #[display(fmt = "attempted to transition delegation output twice")] + DelegationOutputTransitionedTwice = 57, + #[display(fmt = "delegated amount, validator ID and start epoch cannot be modified")] + DelegationModified = 58, + #[display(fmt = "delegation output has invalid start epoch")] + DelegationStartEpochInvalid = 59, + #[display(fmt = "delegated amount does not match amount")] + DelegationAmountMismatch = 60, + #[display(fmt = "end epoch must be set to zero at output genesis")] + DelegationEndEpochNotZero = 61, + #[display(fmt = "delegation end epoch does not match current epoch")] + DelegationEndEpochInvalid = 62, + #[display(fmt = "native token burning is not allowed by the transaction capabilities")] + CapabilitiesNativeTokenBurningNotAllowed = 63, + #[display(fmt = "mana burning is not allowed by the transaction capabilities")] + CapabilitiesManaBurningNotAllowed = 64, + #[display(fmt = "account destruction is not allowed by the transaction capabilities")] + CapabilitiesAccountDestructionNotAllowed = 65, + #[display(fmt = "anchor destruction is not allowed by the transaction capabilities")] + CapabilitiesAnchorDestructionNotAllowed = 66, + #[display(fmt = "foundry destruction is not allowed by the transaction capabilities")] + CapabilitiesFoundryDestructionNotAllowed = 67, + #[display(fmt = "NFT destruction is not allowed by the transaction capabilities")] + CapabilitiesNftDestructionNotAllowed = 68, + #[display(fmt = "semantic validation failed")] SemanticValidationFailed = 255, } -impl fmt::Display for TransactionFailureReason { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::None => write!(f, "none."), - Self::ConflictRejected => write!(f, "transaction was conflicting and was rejected."), - Self::InputAlreadySpent => write!(f, "input already spent."), - Self::InputCreationAfterTxCreation => write!(f, "input creation slot after tx creation slot."), - Self::UnlockSignatureInvalid => write!(f, "signature in unlock is invalid."), - Self::ChainAddressUnlockInvalid => write!(f, "invalid unlock for chain address."), - Self::DirectUnlockableAddressUnlockInvalid => write!(f, "invalid unlock for direct unlockable address."), - Self::MultiAddressUnlockInvalid => write!(f, "invalid unlock for multi address."), - Self::CommitmentInputReferenceInvalid => { - write!(f, "commitment input references an invalid or non-existent commitment.") - } - Self::BicInputReferenceInvalid => write!(f, "BIC input reference cannot be loaded."), - Self::RewardInputReferenceInvalid => write!( - f, - "reward input does not reference a staking account or a delegation output." - ), - Self::StakingRewardCalculationFailure => write!( - f, - "staking rewards could not be calculated due to storage issues or overflow." - ), - Self::DelegationRewardCalculationFailure => write!( - f, - "delegation rewards could not be calculated due to storage issues or overflow." - ), - Self::InputOutputBaseTokenMismatch => write!( - f, - "inputs and outputs do not spend/deposit the same amount of base tokens." - ), - Self::ManaOverflow => write!(f, "under- or overflow in Mana calculations."), - Self::InputOutputManaMismatch => write!(f, "inputs and outputs do not contain the same amount of Mana."), - Self::ManaDecayCreationIndexExceedsTargetIndex => write!( - f, - "mana decay creation slot/epoch index exceeds target slot/epoch index." - ), - Self::NativeTokenSumUnbalanced => write!(f, "native token sums are unbalanced."), - Self::SimpleTokenSchemeMintedMeltedTokenDecrease => { - write!(f, "simple token scheme's minted or melted tokens decreased.") - } - Self::SimpleTokenSchemeMintingInvalid => write!( - f, - "simple token scheme's minted tokens did not increase by the minted amount or melted tokens changed." - ), - Self::SimpleTokenSchemeMeltingInvalid => write!( - f, - "simple token scheme's melted tokens did not increase by the melted amount or minted tokens changed." - ), - Self::SimpleTokenSchemeMaximumSupplyChanged => write!( - f, - "simple token scheme's maximum supply cannot change during transition." - ), - Self::SimpleTokenSchemeGenesisInvalid => write!( - f, - "newly created simple token scheme's melted tokens are not zero or minted tokens do not equal native token amount in transaction." - ), - Self::MultiAddressLengthUnlockLengthMismatch => { - write!(f, "multi address length and multi unlock length do not match.") - } - Self::MultiAddressUnlockThresholdNotReached => write!(f, "multi address unlock threshold not reached."), - Self::SenderFeatureNotUnlocked => write!(f, "sender feature is not unlocked."), - Self::IssuerFeatureNotUnlocked => write!(f, "issuer feature is not unlocked."), - Self::StakingRewardInputMissing => { - write!(f, "staking feature removal or resetting requires a reward input.") - } - Self::StakingBlockIssuerFeatureMissing => { - write!(f, "block issuer feature missing for account with staking feature.") - } - Self::StakingCommitmentInputMissing => write!(f, "staking feature validation requires a commitment input."), - Self::StakingRewardClaimingInvalid => { - write!(f, "staking feature must be removed or reset in order to claim rewards.") - } - Self::StakingFeatureRemovedBeforeUnbonding => { - write!(f, "staking feature can only be removed after the unbonding period.") - } - Self::StakingFeatureModifiedBeforeUnbonding => write!( - f, - "staking start epoch, fixed cost and staked amount cannot be modified while bonded." - ), - Self::StakingStartEpochInvalid => write!(f, "staking start epoch must be the epoch of the transaction."), - Self::StakingEndEpochTooEarly => write!( - f, - "staking end epoch must be set to the transaction epoch plus the unbonding period." - ), - Self::BlockIssuerCommitmentInputMissing => write!(f, "commitment input missing for block issuer feature."), - Self::BlockIssuanceCreditInputMissing => write!( - f, - "block issuance credit input missing for account with block issuer feature." - ), - Self::BlockIssuerNotExpired => write!(f, "block issuer feature has not expired."), - Self::BlockIssuerExpiryTooEarly => write!(f, "block issuer feature expiry set too early."), - Self::ManaMovedOffBlockIssuerAccount => write!( - f, - "mana cannot be moved off block issuer accounts except with manalocks." - ), - Self::AccountLocked => write!(f, "account is locked due to negative block issuance credits."), - Self::TimelockCommitmentInputMissing => write!( - f, - "transaction's containing a timelock condition require a commitment input." - ), - Self::TimelockNotExpired => write!(f, "timelock not expired."), - Self::ExpirationCommitmentInputMissing => write!( - f, - "transaction's containing an expiration condition require a commitment input." - ), - Self::ExpirationNotUnlockable => write!(f, "expiration unlock condition cannot be unlocked."), - Self::ReturnAmountNotFulFilled => write!(f, "return amount not fulfilled."), - Self::NewChainOutputHasNonZeroedId => write!(f, "new chain output has non-zeroed ID."), - Self::ChainOutputImmutableFeaturesChanged => { - write!(f, "immutable features in chain output modified during transition.") - } - Self::ImplicitAccountDestructionDisallowed => { - write!(f, "cannot destroy implicit account; must be transitioned to account.") - } - Self::MultipleImplicitAccountCreationAddresses => { - write!(f, "multiple implicit account creation addresses on the input side.") - } - Self::AccountInvalidFoundryCounter => write!( - f, - "foundry counter in account decreased or did not increase by the number of new foundries." - ), - Self::AnchorInvalidStateTransition => write!(f, "invalid anchor state transition."), - Self::AnchorInvalidGovernanceTransition => write!(f, "invalid anchor governance transition."), - Self::FoundryTransitionWithoutAccount => write!( - f, - "foundry output transitioned without accompanying account on input or output side." - ), - Self::FoundrySerialInvalid => write!(f, "foundry output serial number is invalid."), - Self::DelegationCommitmentInputMissing => { - write!(f, "delegation output validation requires a commitment input.") - } - Self::DelegationRewardInputMissing => { - write!(f, "delegation output cannot be destroyed without a reward input.") - } - Self::DelegationRewardsClaimingInvalid => write!(f, "invalid delegation mana rewards claiming."), - Self::DelegationOutputTransitionedTwice => { - write!(f, "delegation output attempted to be transitioned twice.") - } - Self::DelegationModified => write!(f, "delegated amount, validator ID and start epoch cannot be modified."), - Self::DelegationStartEpochInvalid => write!(f, "delegation output has invalid start epoch."), - Self::DelegationAmountMismatch => write!(f, "delegated amount does not match amount."), - Self::DelegationEndEpochNotZero => write!(f, "end epoch must be set to zero at output genesis."), - Self::DelegationEndEpochInvalid => write!(f, "delegation end epoch does not match current epoch."), - Self::CapabilitiesNativeTokenBurningNotAllowed => write!( - f, - "native token burning is not allowed by the transaction capabilities." - ), - Self::CapabilitiesManaBurningNotAllowed => { - write!(f, "mana burning is not allowed by the transaction capabilities.") - } - Self::CapabilitiesAccountDestructionNotAllowed => { - write!(f, "account destruction is not allowed by the transaction capabilities.") - } - Self::CapabilitiesAnchorDestructionNotAllowed => { - write!(f, "anchor destruction is not allowed by the transaction capabilities.") - } - Self::CapabilitiesFoundryDestructionNotAllowed => { - write!(f, "foundry destruction is not allowed by the transaction capabilities.") - } - Self::CapabilitiesNftDestructionNotAllowed => { - write!(f, "NFT destruction is not allowed by the transaction capabilities.") - } - Self::SemanticValidationFailed => write!(f, "semantic validation failed."), - } - } -} - impl TryFrom for TransactionFailureReason { - type Error = Error; + type Error = InvalidTransactionFailureReasonError; fn try_from(c: u8) -> Result { - Self::from_repr(c).ok_or(Self::Error::InvalidTransactionFailureReason(c)) + Self::from_repr(c).ok_or(InvalidTransactionFailureReasonError(c)) } } diff --git a/sdk/src/types/block/semantic/mod.rs b/sdk/src/types/block/semantic/mod.rs index 5475267949..10f8270cab 100644 --- a/sdk/src/types/block/semantic/mod.rs +++ b/sdk/src/types/block/semantic/mod.rs @@ -14,12 +14,11 @@ pub use self::{error::TransactionFailureReason, state_transition::StateTransitio use crate::types::block::{ address::Address, context_input::RewardContextInput, - output::{feature::Features, AccountId, AnchorOutput, ChainId, FoundryId, Output, OutputId, TokenId}, + output::{feature::Features, AccountId, ChainId, FoundryId, Output, OutputId, TokenId}, payload::signed_transaction::{Transaction, TransactionCapabilityFlag, TransactionId, TransactionSigningHash}, protocol::ProtocolParameters, slot::SlotCommitmentId, unlock::Unlock, - Error, }; /// @@ -31,7 +30,7 @@ pub struct SemanticValidationContext<'a> { pub(crate) unlocks: Option<&'a [Unlock]>, pub(crate) input_amount: u64, pub(crate) input_mana: u64, - pub(crate) mana_rewards: BTreeMap, + pub(crate) mana_rewards: Option<&'a BTreeMap>, pub(crate) commitment_context_input: Option, pub(crate) reward_context_inputs: HashMap, pub(crate) input_native_tokens: BTreeMap, @@ -44,7 +43,7 @@ pub struct SemanticValidationContext<'a> { pub(crate) unlocked_addresses: HashSet
, pub(crate) storage_deposit_returns: HashMap, pub(crate) simple_deposits: HashMap, - pub(crate) protocol_parameters: ProtocolParameters, + pub(crate) protocol_parameters: &'a ProtocolParameters, } impl<'a> SemanticValidationContext<'a> { @@ -53,8 +52,8 @@ impl<'a> SemanticValidationContext<'a> { transaction: &'a Transaction, inputs: &'a [(&'a OutputId, &'a Output)], unlocks: Option<&'a [Unlock]>, - mana_rewards: BTreeMap, - protocol_parameters: ProtocolParameters, + mana_rewards: Option<&'a BTreeMap>, + protocol_parameters: &'a ProtocolParameters, ) -> Self { let transaction_id = transaction.id(); let input_chains = inputs @@ -90,7 +89,10 @@ impl<'a> SemanticValidationContext<'a> { input_amount: 0, input_mana: 0, mana_rewards, - commitment_context_input: None, + commitment_context_input: transaction + .context_inputs() + .commitment() + .map(|c| c.slot_commitment_id()), reward_context_inputs: Default::default(), input_native_tokens: BTreeMap::::new(), input_chains, @@ -107,35 +109,46 @@ impl<'a> SemanticValidationContext<'a> { } /// - pub fn validate(mut self) -> Result, Error> { - self.commitment_context_input = self - .transaction - .context_inputs() - .commitment() - .map(|c| c.slot_commitment_id()); + pub fn validate(mut self) -> Result<(), TransactionFailureReason> { + self.validate_reward_context_inputs()?; - let bic_context_inputs = self - .transaction - .context_inputs() - .block_issuance_credits() - .map(|bic| *bic.account_id()) - .collect::>(); + self.validate_inputs()?; + + self.validate_outputs()?; + + self.validate_storage_deposit_returns()?; + + self.validate_balances()?; + self.validate_transitions()?; + + Ok(()) + } + + fn validate_reward_context_inputs(&mut self) -> Result<(), TransactionFailureReason> { for reward_context_input in self.transaction.context_inputs().rewards() { if let Some(output_id) = self.inputs.get(reward_context_input.index() as usize).map(|v| v.0) { self.reward_context_inputs.insert(*output_id, *reward_context_input); } else { - return Ok(Some(TransactionFailureReason::RewardInputReferenceInvalid)); + return Err(TransactionFailureReason::RewardInputReferenceInvalid); } } + Ok(()) + } - // Validation of inputs. + fn validate_inputs(&mut self) -> Result<(), TransactionFailureReason> { + let bic_context_inputs = self + .transaction + .context_inputs() + .block_issuance_credits() + .map(|bic| *bic.account_id()) + .collect::>(); let mut has_implicit_account_creation_address = false; for (index, (output_id, consumed_output)) in self.inputs.iter().enumerate() { if output_id.transaction_id().slot_index() > self.transaction.creation_slot() { - return Ok(Some(TransactionFailureReason::InputCreationAfterTxCreation)); + return Err(TransactionFailureReason::InputCreationAfterTxCreation); } let (amount, consumed_native_token, unlock_conditions) = match consumed_output { @@ -145,25 +158,33 @@ impl<'a> SemanticValidationContext<'a> { let account_id = output.account_id_non_null(output_id); if self.commitment_context_input.is_none() { - return Ok(Some(TransactionFailureReason::BlockIssuerCommitmentInputMissing)); + return Err(TransactionFailureReason::BlockIssuerCommitmentInputMissing); } if !bic_context_inputs.contains(&account_id) { - return Ok(Some(TransactionFailureReason::BlockIssuanceCreditInputMissing)); + return Err(TransactionFailureReason::BlockIssuanceCreditInputMissing); } let entry = self.block_issuer_mana.entry(account_id).or_default(); entry.0 = entry .0 - .checked_add(consumed_output.available_mana( - &self.protocol_parameters, - output_id.transaction_id().slot_index(), - self.transaction.creation_slot(), - )?) - .ok_or(Error::ConsumedManaOverflow)?; + .checked_add( + consumed_output + .available_mana( + self.protocol_parameters, + output_id.transaction_id().slot_index(), + self.transaction.creation_slot(), + ) + // Unwrap is fine as we already checked both slot indices against each others. + .unwrap(), + ) + .ok_or(TransactionFailureReason::ManaOverflow)?; + } + if output.features().staking().is_some() && self.commitment_context_input.is_none() { + return Err(TransactionFailureReason::StakingCommitmentInputMissing); } (output.amount(), None, output.unlock_conditions()) } - Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), + Output::Anchor(_) => return Err(TransactionFailureReason::SemanticValidationFailed), Output::Foundry(output) => (output.amount(), output.native_token(), output.unlock_conditions()), Output::Nft(output) => (output.amount(), None, output.unlock_conditions()), Output::Delegation(output) => (output.amount(), None, output.unlock_conditions()), @@ -171,7 +192,7 @@ impl<'a> SemanticValidationContext<'a> { if unlock_conditions.addresses().any(Address::is_implicit_account_creation) { if has_implicit_account_creation_address { - return Ok(Some(TransactionFailureReason::MultipleImplicitAccountCreationAddresses)); + return Err(TransactionFailureReason::MultipleImplicitAccountCreationAddresses); } else { has_implicit_account_creation_address = true; } @@ -182,10 +203,10 @@ impl<'a> SemanticValidationContext<'a> { if let Some(timelock) = unlock_conditions.timelock() { if let Some(commitment_slot_index) = commitment_slot_index { if timelock.is_timelocked(commitment_slot_index, self.protocol_parameters.min_committable_age()) { - return Ok(Some(TransactionFailureReason::TimelockNotExpired)); + return Err(TransactionFailureReason::TimelockNotExpired); } } else { - return Ok(Some(TransactionFailureReason::TimelockCommitmentInputMissing)); + return Err(TransactionFailureReason::TimelockCommitmentInputMissing); } } @@ -202,35 +223,41 @@ impl<'a> SemanticValidationContext<'a> { *amount = amount .checked_add(storage_deposit_return.amount()) - .ok_or(Error::StorageDepositReturnOverflow)?; + .ok_or(TransactionFailureReason::SemanticValidationFailed)?; } } - None => return Ok(Some(TransactionFailureReason::ExpirationNotUnlockable)), + None => return Err(TransactionFailureReason::ExpirationNotUnlockable), _ => {} } } else { - return Ok(Some(TransactionFailureReason::ExpirationCommitmentInputMissing)); + return Err(TransactionFailureReason::ExpirationCommitmentInputMissing); } } self.input_amount = self .input_amount .checked_add(amount) - .ok_or(Error::ConsumedAmountOverflow)?; + .ok_or(TransactionFailureReason::SemanticValidationFailed)?; self.input_mana = self .input_mana - .checked_add(consumed_output.available_mana( - &self.protocol_parameters, - output_id.transaction_id().slot_index(), - self.transaction.creation_slot(), - )?) - .ok_or(Error::ConsumedManaOverflow)?; - - if let Some(mana_rewards) = self.mana_rewards.get(*output_id) { - self.input_mana + .checked_add( + consumed_output + .available_mana( + self.protocol_parameters, + output_id.transaction_id().slot_index(), + self.transaction.creation_slot(), + ) + // Unwrap is fine as we already checked both slot indices against each others. + .unwrap(), + ) + .ok_or(TransactionFailureReason::ManaOverflow)?; + + if let Some(mana_rewards) = self.mana_rewards.as_ref().and_then(|r| r.get(*output_id)) { + self.input_mana = self + .input_mana .checked_add(*mana_rewards) - .ok_or(Error::ConsumedManaOverflow)?; + .ok_or(TransactionFailureReason::ManaOverflow)?; } if let Some(consumed_native_token) = consumed_native_token { @@ -241,29 +268,37 @@ impl<'a> SemanticValidationContext<'a> { *native_token_amount = native_token_amount .checked_add(consumed_native_token.amount()) - .ok_or(Error::ConsumedNativeTokensAmountOverflow)?; + .ok_or(TransactionFailureReason::SemanticValidationFailed)?; } if let Some(unlocks) = self.unlocks { if unlocks.len() != self.inputs.len() { - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); + return Err(TransactionFailureReason::SemanticValidationFailed); } - if let Err(conflict) = self.output_unlock(consumed_output, output_id, &unlocks[index]) { - return Ok(Some(conflict)); - } + self.output_unlock(consumed_output, output_id, &unlocks[index])? } } + Ok(()) + } + + fn validate_outputs(&mut self) -> Result<(), TransactionFailureReason> { + let bic_context_inputs = self + .transaction + .context_inputs() + .block_issuance_credits() + .map(|bic| *bic.account_id()) + .collect::>(); + // Add allotted mana for mana_allotment in self.transaction.allotments().iter() { self.output_mana = self .output_mana .checked_add(mana_allotment.mana()) - .ok_or(Error::CreatedManaOverflow)?; + .ok_or(TransactionFailureReason::ManaOverflow)?; } - // Validation of outputs. for (index, created_output) in self.transaction.outputs().iter().enumerate() { let (amount, mana, created_native_token, features) = match created_output { Output::Basic(output) => { @@ -272,7 +307,7 @@ impl<'a> SemanticValidationContext<'a> { *amount = amount .checked_add(output.amount()) - .ok_or(Error::CreatedAmountOverflow)?; + .ok_or(TransactionFailureReason::SemanticValidationFailed)?; } ( @@ -286,35 +321,37 @@ impl<'a> SemanticValidationContext<'a> { if output.features().block_issuer().is_some() { let account_id = output.account_id_non_null(&OutputId::new(self.transaction_id, index as u16)); - if self.commitment_context_input.is_none() { - return Ok(Some(TransactionFailureReason::BlockIssuerCommitmentInputMissing)); - } if !bic_context_inputs.contains(&account_id) { - return Ok(Some(TransactionFailureReason::BlockIssuanceCreditInputMissing)); + return Err(TransactionFailureReason::BlockIssuanceCreditInputMissing); } let entry = self.block_issuer_mana.entry(account_id).or_default(); - entry.1 = entry.1.checked_add(output.mana()).ok_or(Error::CreatedManaOverflow)?; + entry.1 = entry + .1 + .checked_add(output.mana()) + .ok_or(TransactionFailureReason::ManaOverflow)?; if let Some(allotment) = self.transaction.allotments().get(&account_id) { entry.1 = entry .1 .checked_add(allotment.mana()) - .ok_or(Error::CreatedManaOverflow)?; + .ok_or(TransactionFailureReason::ManaOverflow)?; } } (output.amount(), output.mana(), None, Some(output.features())) } - Output::Anchor(_) => return Err(Error::UnsupportedOutputKind(AnchorOutput::KIND)), + Output::Anchor(_) => return Err(TransactionFailureReason::SemanticValidationFailed), Output::Foundry(output) => (output.amount(), 0, output.native_token(), Some(output.features())), Output::Nft(output) => (output.amount(), output.mana(), None, Some(output.features())), Output::Delegation(output) => (output.amount(), 0, None, None), }; - if let Some(sender) = features.and_then(Features::sender) { - if !self.unlocked_addresses.contains(sender.address()) { - return Ok(Some(TransactionFailureReason::SenderFeatureNotUnlocked)); + if self.unlocks.is_some() { + if let Some(sender) = features.and_then(Features::sender) { + if !self.unlocked_addresses.contains(sender.address()) { + return Err(TransactionFailureReason::SenderFeatureNotUnlocked); + } } } @@ -323,19 +360,19 @@ impl<'a> SemanticValidationContext<'a> { if let Address::Account(account_address) = address.address() { if let Some(entry) = self.block_issuer_mana.get_mut(account_address.account_id()) { if let Some(commitment_context_input) = self.commitment_context_input { - let past_bounded_slot_index = + let past_bounded_slot = self.protocol_parameters.past_bounded_slot(commitment_context_input); if timelock.slot_index() - >= past_bounded_slot_index + self.protocol_parameters.max_committable_age() + >= past_bounded_slot + self.protocol_parameters.max_committable_age() { entry.1 = entry .1 .checked_add(created_output.mana()) - .ok_or(Error::CreatedAmountOverflow)?; + .ok_or(TransactionFailureReason::SemanticValidationFailed)?; } } else { - return Ok(Some(TransactionFailureReason::BlockIssuerCommitmentInputMissing)); + return Err(TransactionFailureReason::BlockIssuerCommitmentInputMissing); } } } @@ -345,10 +382,13 @@ impl<'a> SemanticValidationContext<'a> { self.output_amount = self .output_amount .checked_add(amount) - .ok_or(Error::CreatedAmountOverflow)?; + .ok_or(TransactionFailureReason::SemanticValidationFailed)?; // Add stored mana - self.output_mana = self.output_mana.checked_add(mana).ok_or(Error::CreatedManaOverflow)?; + self.output_mana = self + .output_mana + .checked_add(mana) + .ok_or(TransactionFailureReason::ManaOverflow)?; if let Some(created_native_token) = created_native_token { let native_token_amount = self @@ -358,40 +398,44 @@ impl<'a> SemanticValidationContext<'a> { *native_token_amount = native_token_amount .checked_add(created_native_token.amount()) - // TODO should be a tx failure reason ? - .ok_or(Error::CreatedNativeTokensAmountOverflow)?; + .ok_or(TransactionFailureReason::SemanticValidationFailed)?; } } + Ok(()) + } - // Validation of storage deposit returns. + fn validate_storage_deposit_returns(&mut self) -> Result<(), TransactionFailureReason> { for (return_address, return_amount) in self.storage_deposit_returns.iter() { if let Some(deposit_amount) = self.simple_deposits.get(return_address) { if deposit_amount < return_amount { - return Ok(Some(TransactionFailureReason::ReturnAmountNotFulFilled)); + return Err(TransactionFailureReason::ReturnAmountNotFulFilled); } } else { - return Ok(Some(TransactionFailureReason::ReturnAmountNotFulFilled)); + return Err(TransactionFailureReason::ReturnAmountNotFulFilled); } } + Ok(()) + } - // Validation of amounts. + fn validate_balances(&mut self) -> Result<(), TransactionFailureReason> { + // Validation of amounts if self.input_amount != self.output_amount { - return Ok(Some(TransactionFailureReason::InputOutputBaseTokenMismatch)); + return Err(TransactionFailureReason::InputOutputBaseTokenMismatch); } if self.input_mana != self.output_mana { if self.input_mana > self.output_mana { if !self.transaction.has_capability(TransactionCapabilityFlag::BurnMana) { - return Ok(Some(TransactionFailureReason::CapabilitiesManaBurningNotAllowed)); + return Err(TransactionFailureReason::CapabilitiesManaBurningNotAllowed); } - } else { - return Ok(Some(TransactionFailureReason::InputOutputManaMismatch)); + } else if self.mana_rewards.is_some() || self.reward_context_inputs.is_empty() { + return Err(TransactionFailureReason::InputOutputManaMismatch); } } for (account_input_mana, account_output_mana) in self.block_issuer_mana.values() { if self.input_mana - account_input_mana < self.output_mana - account_output_mana { - return Ok(Some(TransactionFailureReason::ManaMovedOffBlockIssuerAccount)); + return Err(TransactionFailureReason::ManaMovedOffBlockIssuerAccount); } } @@ -404,29 +448,27 @@ impl<'a> SemanticValidationContext<'a> { .output_chains .contains_key(&ChainId::from(FoundryId::from(*token_id))) { - return Ok(Some(TransactionFailureReason::NativeTokenSumUnbalanced)); + return Err(TransactionFailureReason::NativeTokenSumUnbalanced); } } + Ok(()) + } + fn validate_transitions(&mut self) -> Result<(), TransactionFailureReason> { // Validation of state transitions and destructions. for (chain_id, current_state) in self.input_chains.iter() { - if let Err(e) = self.verify_state_transition( + self.verify_state_transition( Some(*current_state), self.output_chains.get(chain_id).map(|(id, o)| (id, *o)), - ) { - return Ok(Some(e)); - } + )?; } // Validation of state creations. for (chain_id, next_state) in self.output_chains.iter() { if self.input_chains.get(chain_id).is_none() { - if let Err(e) = self.verify_state_transition(None, Some((&next_state.0, next_state.1))) { - return Ok(Some(e)); - } + self.verify_state_transition(None, Some((&next_state.0, next_state.1)))?; } } - - Ok(None) + Ok(()) } } diff --git a/sdk/src/types/block/semantic/state_transition.rs b/sdk/src/types/block/semantic/state_transition.rs index d5d440c589..fa287e6e7d 100644 --- a/sdk/src/types/block/semantic/state_transition.rs +++ b/sdk/src/types/block/semantic/state_transition.rs @@ -1,6 +1,8 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use core::cmp::Ordering; + use crate::types::block::{ output::{ AccountOutput, AnchorOutput, BasicOutput, ChainId, DelegationOutput, FoundryOutput, NftOutput, Output, @@ -130,9 +132,11 @@ impl BasicOutput { return Err(TransactionFailureReason::BlockIssuerNotExpired); } - if let Some(issuer) = next_state.immutable_features().issuer() { - if !context.unlocked_addresses.contains(issuer.address()) { - return Err(TransactionFailureReason::IssuerFeatureNotUnlocked); + if context.unlocks.is_some() { + if let Some(issuer) = next_state.immutable_features().issuer() { + if !context.unlocked_addresses.contains(issuer.address()) { + return Err(TransactionFailureReason::IssuerFeatureNotUnlocked); + } } } @@ -151,18 +155,32 @@ impl StateTransitionVerifier for AccountOutput { } if let Some(block_issuer) = next_state.features().block_issuer() { - let past_bounded_slot_index = context + let past_bounded_slot = context .protocol_parameters .past_bounded_slot(context.commitment_context_input.unwrap()); - if block_issuer.expiry_slot() < past_bounded_slot_index { + if block_issuer.expiry_slot() < past_bounded_slot { return Err(TransactionFailureReason::BlockIssuerExpiryTooEarly); } } + if let Some(staking) = next_state.features().staking() { + let past_bounded_epoch = context + .protocol_parameters + .past_bounded_epoch(context.commitment_context_input.unwrap()); + + if staking.start_epoch() != past_bounded_epoch { + return Err(TransactionFailureReason::StakingStartEpochInvalid); + } + if staking.end_epoch() < past_bounded_epoch + context.protocol_parameters.staking_unbonding_period { + return Err(TransactionFailureReason::StakingEndEpochTooEarly); + } + } - if let Some(issuer) = next_state.immutable_features().issuer() { - if !context.unlocked_addresses.contains(issuer.address()) { - return Err(TransactionFailureReason::IssuerFeatureNotUnlocked); + if context.unlocks.is_some() { + if let Some(issuer) = next_state.immutable_features().issuer() { + if !context.unlocked_addresses.contains(issuer.address()) { + return Err(TransactionFailureReason::IssuerFeatureNotUnlocked); + } } } @@ -170,22 +188,67 @@ impl StateTransitionVerifier for AccountOutput { } fn transition( - _current_output_id: &OutputId, + current_output_id: &OutputId, current_state: &Self, _next_output_id: &OutputId, next_state: &Self, context: &SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { + if current_state.immutable_features() != next_state.immutable_features() { + return Err(TransactionFailureReason::ChainOutputImmutableFeaturesChanged); + } + + // TODO update when TIP is updated + // // Governance transition. + // if current_state.amount != next_state.amount + // || current_state.foundry_counter != next_state.foundry_counter + // { + // return Err(StateTransitionError::MutatedFieldWithoutRights); + // } + + // // State transition. + // if current_state.features.metadata() != next_state.features.metadata() { + // return Err(StateTransitionError::MutatedFieldWithoutRights); + // } + + let created_foundries = context.transaction.outputs().iter().filter_map(|output| { + if let Output::Foundry(foundry) = output { + if foundry.account_address().account_id() == next_state.account_id() + && !context.input_chains.contains_key(&foundry.chain_id()) + { + Some(foundry) + } else { + None + } + } else { + None + } + }); + + let mut created_foundries_count = 0; + + for foundry in created_foundries { + created_foundries_count += 1; + + if foundry.serial_number() != current_state.foundry_counter() + created_foundries_count { + return Err(TransactionFailureReason::FoundrySerialInvalid); + } + } + + if current_state.foundry_counter() + created_foundries_count != next_state.foundry_counter() { + return Err(TransactionFailureReason::AccountInvalidFoundryCounter); + } + match ( current_state.features().block_issuer(), next_state.features().block_issuer(), ) { (None, Some(block_issuer_output)) => { - let past_bounded_slot_index = context + let past_bounded_slot = context .protocol_parameters .past_bounded_slot(context.commitment_context_input.unwrap()); - if block_issuer_output.expiry_slot() < past_bounded_slot_index { + if block_issuer_output.expiry_slot() < past_bounded_slot { return Err(TransactionFailureReason::BlockIssuerExpiryTooEarly); } } @@ -198,31 +261,96 @@ impl StateTransitionVerifier for AccountOutput { } (Some(block_issuer_input), Some(block_issuer_output)) => { let commitment_index = context.commitment_context_input.unwrap(); - let past_bounded_slot_index = context.protocol_parameters.past_bounded_slot(commitment_index); + let past_bounded_slot = context.protocol_parameters.past_bounded_slot(commitment_index); if block_issuer_input.expiry_slot() >= commitment_index.slot_index() { if block_issuer_input.expiry_slot() != block_issuer_output.expiry_slot() - && block_issuer_input.expiry_slot() < past_bounded_slot_index + && block_issuer_input.expiry_slot() < past_bounded_slot { return Err(TransactionFailureReason::BlockIssuerNotExpired); } - } else if block_issuer_output.expiry_slot() < past_bounded_slot_index { + } else if block_issuer_output.expiry_slot() < past_bounded_slot { return Err(TransactionFailureReason::BlockIssuerExpiryTooEarly); } } _ => {} } - Self::transition_inner( - current_state, - next_state, - &context.input_chains, - context.transaction.outputs(), - ) + match (current_state.features().staking(), next_state.features().staking()) { + (None, Some(staking_output)) => { + let past_bounded_epoch = context + .protocol_parameters + .past_bounded_epoch(context.commitment_context_input.unwrap()); + + if staking_output.start_epoch() != past_bounded_epoch { + return Err(TransactionFailureReason::StakingStartEpochInvalid); + } + if staking_output.end_epoch() + < past_bounded_epoch + context.protocol_parameters.staking_unbonding_period + { + return Err(TransactionFailureReason::StakingEndEpochTooEarly); + } + } + (Some(staking_input), None) => { + let future_bounded_epoch = context + .protocol_parameters + .future_bounded_epoch(context.commitment_context_input.unwrap()); + + if staking_input.end_epoch() >= future_bounded_epoch { + return Err(TransactionFailureReason::StakingFeatureRemovedBeforeUnbonding); + } else if context + .mana_rewards + .as_ref() + .is_some_and(|r| !r.contains_key(current_output_id)) + || !context.reward_context_inputs.contains_key(current_output_id) + { + return Err(TransactionFailureReason::StakingRewardClaimingInvalid); + } + } + (Some(staking_input), Some(staking_output)) => { + let past_bounded_epoch = context + .protocol_parameters + .past_bounded_epoch(context.commitment_context_input.unwrap()); + let future_bounded_epoch = context + .protocol_parameters + .future_bounded_epoch(context.commitment_context_input.unwrap()); + + if staking_input.end_epoch() >= future_bounded_epoch { + if staking_input.staked_amount() != staking_output.staked_amount() + || staking_input.start_epoch() != staking_output.start_epoch() + || staking_input.fixed_cost() != staking_output.fixed_cost() + { + return Err(TransactionFailureReason::StakingFeatureModifiedBeforeUnbonding); + } + if staking_input.end_epoch() != staking_output.end_epoch() + && staking_input.end_epoch() + < past_bounded_epoch + context.protocol_parameters.staking_unbonding_period + { + return Err(TransactionFailureReason::StakingEndEpochTooEarly); + } + } else if (staking_input.staked_amount() != staking_output.staked_amount() + || staking_input.start_epoch() != staking_output.start_epoch() + || staking_input.fixed_cost() != staking_output.fixed_cost()) + && (staking_input.start_epoch() != past_bounded_epoch + || staking_input.end_epoch() + < past_bounded_epoch + context.protocol_parameters.staking_unbonding_period + || context + .mana_rewards + .as_ref() + .is_some_and(|r| !r.contains_key(current_output_id)) + || !context.reward_context_inputs.contains_key(current_output_id)) + { + return Err(TransactionFailureReason::StakingRewardClaimingInvalid); + } + } + _ => {} + } + + Ok(()) } fn destruction( - _output_id: &OutputId, + output_id: &OutputId, current_state: &Self, context: &SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { @@ -238,6 +366,22 @@ impl StateTransitionVerifier for AccountOutput { return Err(TransactionFailureReason::BlockIssuerNotExpired); } } + if let Some(staking) = current_state.features().staking() { + let future_bounded_epoch = context + .protocol_parameters + .future_bounded_epoch(context.commitment_context_input.unwrap()); + + if staking.end_epoch() >= future_bounded_epoch { + return Err(TransactionFailureReason::StakingFeatureRemovedBeforeUnbonding); + } else if context + .mana_rewards + .as_ref() + .is_some_and(|r| !r.contains_key(output_id)) + || !context.reward_context_inputs.contains_key(output_id) + { + return Err(TransactionFailureReason::StakingRewardClaimingInvalid); + } + } Ok(()) } @@ -253,9 +397,11 @@ impl StateTransitionVerifier for AnchorOutput { return Err(TransactionFailureReason::NewChainOutputHasNonZeroedId); } - if let Some(issuer) = next_state.immutable_features().issuer() { - if !context.unlocked_addresses.contains(issuer.address()) { - return Err(TransactionFailureReason::IssuerFeatureNotUnlocked); + if context.unlocks.is_some() { + if let Some(issuer) = next_state.immutable_features().issuer() { + if !context.unlocked_addresses.contains(issuer.address()) { + return Err(TransactionFailureReason::IssuerFeatureNotUnlocked); + } } } @@ -267,14 +413,32 @@ impl StateTransitionVerifier for AnchorOutput { current_state: &Self, _next_output_id: &OutputId, next_state: &Self, - context: &SemanticValidationContext<'_>, + _context: &SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { - Self::transition_inner( - current_state, - next_state, - &context.input_chains, - context.transaction.outputs(), - ) + if current_state.immutable_features() != next_state.immutable_features() { + return Err(TransactionFailureReason::ChainOutputImmutableFeaturesChanged); + } + + if next_state.state_index() == current_state.state_index() + 1 { + // State transition. + if current_state.state_controller_address() != next_state.state_controller_address() + || current_state.governor_address() != next_state.governor_address() + || current_state.features().metadata() != next_state.features().metadata() + { + return Err(TransactionFailureReason::AnchorInvalidStateTransition); + } + } else if next_state.state_index() == current_state.state_index() { + // Governance transition. + if current_state.amount() != next_state.amount() + || current_state.features().state_metadata() != next_state.features().state_metadata() + { + return Err(TransactionFailureReason::AnchorInvalidGovernanceTransition); + } + } else { + return Err(TransactionFailureReason::AnchorInvalidStateTransition); + } + + Ok(()) } fn destruction( @@ -338,13 +502,86 @@ impl StateTransitionVerifier for FoundryOutput { next_state: &Self, context: &SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { - Self::transition_inner( - current_state, - next_state, - &context.input_native_tokens, - &context.output_native_tokens, - context.transaction.capabilities(), - ) + if current_state.account_address() != next_state.account_address() + || current_state.serial_number() != next_state.serial_number() + || current_state.immutable_features() != next_state.immutable_features() + { + return Err(TransactionFailureReason::ChainOutputImmutableFeaturesChanged); + } + + let token_id = next_state.token_id(); + let input_tokens = context.input_native_tokens.get(&token_id).copied().unwrap_or_default(); + let output_tokens = context.output_native_tokens.get(&token_id).copied().unwrap_or_default(); + let TokenScheme::Simple(ref current_token_scheme) = current_state.token_scheme(); + let TokenScheme::Simple(ref next_token_scheme) = next_state.token_scheme(); + + if current_token_scheme.maximum_supply() != next_token_scheme.maximum_supply() { + return Err(TransactionFailureReason::SimpleTokenSchemeMaximumSupplyChanged); + } + + if current_token_scheme.minted_tokens() > next_token_scheme.minted_tokens() + || current_token_scheme.melted_tokens() > next_token_scheme.melted_tokens() + { + return Err(TransactionFailureReason::SimpleTokenSchemeMintedMeltedTokenDecrease); + } + + match input_tokens.cmp(&output_tokens) { + Ordering::Less => { + // Mint + + // This can't underflow as it is known that current_minted_tokens <= next_minted_tokens. + let minted_diff = next_token_scheme.minted_tokens() - current_token_scheme.minted_tokens(); + // This can't underflow as it is known that input_tokens < output_tokens (Ordering::Less). + let token_diff = output_tokens - input_tokens; + + if minted_diff != token_diff { + return Err(TransactionFailureReason::NativeTokenSumUnbalanced); + } + + if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens() { + return Err(TransactionFailureReason::NativeTokenSumUnbalanced); + } + } + Ordering::Equal => { + // Transition + + if current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens() + || current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens() + { + return Err(TransactionFailureReason::NativeTokenSumUnbalanced); + } + } + Ordering::Greater => { + // Melt / Burn + + if current_token_scheme.melted_tokens() != next_token_scheme.melted_tokens() + && current_token_scheme.minted_tokens() != next_token_scheme.minted_tokens() + { + return Err(TransactionFailureReason::NativeTokenSumUnbalanced); + } + + // This can't underflow as it is known that current_melted_tokens <= next_melted_tokens. + let melted_diff = next_token_scheme.melted_tokens() - current_token_scheme.melted_tokens(); + // This can't underflow as it is known that input_tokens > output_tokens (Ordering::Greater). + let token_diff = input_tokens - output_tokens; + + if melted_diff > token_diff { + return Err(TransactionFailureReason::NativeTokenSumUnbalanced); + } + + let burned_diff = token_diff - melted_diff; + + if !burned_diff.is_zero() + && !context + .transaction + .has_capability(TransactionCapabilityFlag::BurnNativeTokens) + { + return Err(TransactionFailureReason::CapabilitiesNativeTokenBurningNotAllowed)?; + } + } + } + + Ok(()) } fn destruction( @@ -389,9 +626,11 @@ impl StateTransitionVerifier for NftOutput { return Err(TransactionFailureReason::NewChainOutputHasNonZeroedId); } - if let Some(issuer) = next_state.immutable_features().issuer() { - if !context.unlocked_addresses.contains(issuer.address()) { - return Err(TransactionFailureReason::IssuerFeatureNotUnlocked); + if context.unlocks.is_some() { + if let Some(issuer) = next_state.immutable_features().issuer() { + if !context.unlocked_addresses.contains(issuer.address()) { + return Err(TransactionFailureReason::IssuerFeatureNotUnlocked); + } } } @@ -405,7 +644,11 @@ impl StateTransitionVerifier for NftOutput { next_state: &Self, _context: &SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { - Self::transition_inner(current_state, next_state) + if current_state.immutable_features() != next_state.immutable_features() { + return Err(TransactionFailureReason::ChainOutputImmutableFeaturesChanged); + } + + Ok(()) } fn destruction( @@ -462,15 +705,22 @@ impl StateTransitionVerifier for DelegationOutput { next_state: &Self, context: &SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { - Self::transition_inner(current_state, next_state)?; + if !current_state.delegation_id().is_null() || next_state.delegation_id().is_null() { + return Err(TransactionFailureReason::DelegationOutputTransitionedTwice); + } - let protocol_parameters = &context.protocol_parameters; + if current_state.delegated_amount() != next_state.delegated_amount() + || current_state.start_epoch() != next_state.start_epoch() + || current_state.validator_address() != next_state.validator_address() + { + return Err(TransactionFailureReason::DelegationModified); + } let slot_commitment_id = context .commitment_context_input .ok_or(TransactionFailureReason::DelegationCommitmentInputMissing)?; - if next_state.end_epoch() != protocol_parameters.delegation_end_epoch(slot_commitment_id) { + if next_state.end_epoch() != context.protocol_parameters.delegation_end_epoch(slot_commitment_id) { return Err(TransactionFailureReason::DelegationEndEpochInvalid); } @@ -482,7 +732,12 @@ impl StateTransitionVerifier for DelegationOutput { _current_state: &Self, context: &SemanticValidationContext<'_>, ) -> Result<(), TransactionFailureReason> { - if !context.mana_rewards.contains_key(output_id) || !context.reward_context_inputs.contains_key(output_id) { + if context + .mana_rewards + .as_ref() + .is_some_and(|r| !r.contains_key(output_id)) + || !context.reward_context_inputs.contains_key(output_id) + { return Err(TransactionFailureReason::DelegationRewardInputMissing); } diff --git a/sdk/src/types/block/signature/ed25519.rs b/sdk/src/types/block/signature/ed25519.rs index b27a628416..bd182bfae2 100644 --- a/sdk/src/types/block/signature/ed25519.rs +++ b/sdk/src/types/block/signature/ed25519.rs @@ -1,7 +1,7 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use core::{fmt, ops::Deref}; +use core::{convert::Infallible, fmt, ops::Deref}; use crypto::{ hashes::{blake2b::Blake2b256, Digest}, @@ -17,7 +17,7 @@ use packable::{ use crate::types::block::{ address::Ed25519Address, protocol::{WorkScore, WorkScoreParameters}, - Error, + signature::SignatureError, }; /// An Ed25519 signature. @@ -53,7 +53,7 @@ impl Ed25519Signature { pub fn try_from_bytes( public_key: [u8; Self::PUBLIC_KEY_LENGTH], signature: [u8; Self::SIGNATURE_LENGTH], - ) -> Result { + ) -> Result { Ok(Self::from_bytes(public_key, signature)) } @@ -93,18 +93,18 @@ impl Ed25519Signature { } /// Validates the [`Ed25519Signature`] for a message against an [`Ed25519Address`]. - pub fn validate(&self, message: &[u8], address: &Ed25519Address) -> Result<(), Error> { + pub fn validate(&self, message: &[u8], address: &Ed25519Address) -> Result<(), SignatureError> { let signature_address: [u8; Self::PUBLIC_KEY_LENGTH] = Blake2b256::digest(self.public_key).into(); if address.deref() != &signature_address { - return Err(Error::SignaturePublicKeyMismatch { + return Err(SignatureError::PublicKeyMismatch { expected: prefix_hex::encode(address.as_ref()), actual: prefix_hex::encode(signature_address), }); } - if !self.try_verify(message)? { - return Err(Error::InvalidSignature); + if !self.try_verify(message).map_err(SignatureError::SignatureBytes)? { + return Err(SignatureError::SignatureMismatch(prefix_hex::encode(message))); } Ok(()) @@ -142,7 +142,7 @@ impl WorkScore for Ed25519Signature { } impl Packable for Ed25519Signature { - type UnpackError = Error; + type UnpackError = Infallible; type UnpackVisitor = (); fn pack(&self, packer: &mut P) -> Result<(), P::Error> { @@ -152,12 +152,12 @@ impl Packable for Ed25519Signature { Ok(()) } - fn unpack( + fn unpack( unpacker: &mut U, - visitor: &Self::UnpackVisitor, + visitor: Option<&Self::UnpackVisitor>, ) -> Result> { - let public_key = <[u8; Self::PUBLIC_KEY_LENGTH]>::unpack::<_, VERIFY>(unpacker, visitor).coerce()?; - let signature = <[u8; Self::SIGNATURE_LENGTH]>::unpack::<_, VERIFY>(unpacker, visitor).coerce()?; + let public_key = <[u8; Self::PUBLIC_KEY_LENGTH]>::unpack(unpacker, visitor).coerce()?; + let signature = <[u8; Self::SIGNATURE_LENGTH]>::unpack(unpacker, visitor).coerce()?; Ok(Self::from_bytes(public_key, signature)) } @@ -170,7 +170,6 @@ pub(crate) mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::types::block::Error; /// Defines an Ed25519 signature. #[derive(Serialize, Deserialize)] @@ -193,12 +192,12 @@ pub(crate) mod dto { } impl TryFrom for Ed25519Signature { - type Error = Error; + type Error = SignatureError; fn try_from(value: Ed25519SignatureDto) -> Result { Ok(Self::from_bytes( - prefix_hex::decode(&value.public_key).map_err(|_| Error::InvalidField("publicKey"))?, - prefix_hex::decode(&value.signature).map_err(|_| Error::InvalidField("signature"))?, + prefix_hex::decode(&value.public_key).map_err(SignatureError::PublicKeyHex)?, + prefix_hex::decode(&value.signature).map_err(SignatureError::SignatureHex)?, )) } } diff --git a/sdk/src/types/block/signature/error.rs b/sdk/src/types/block/signature/error.rs new file mode 100644 index 0000000000..894b9cbd23 --- /dev/null +++ b/sdk/src/types/block/signature/error.rs @@ -0,0 +1,33 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::string::String; +use core::convert::Infallible; + +#[derive(Debug, PartialEq, Eq, derive_more::Display)] +#[allow(missing_docs)] +pub enum SignatureError { + #[display(fmt = "invalid signature kind: {_0}")] + Kind(u8), + #[display(fmt = "signature public key mismatch: expected {expected} but got {actual}")] + PublicKeyMismatch { expected: String, actual: String }, + #[display(fmt = "signature does not match the message: {_0}")] + SignatureMismatch(String), + #[display(fmt = "invalid public key hex: {_0}")] + PublicKeyHex(prefix_hex::Error), + #[display(fmt = "invalid signature hex: {_0}")] + SignatureHex(prefix_hex::Error), + #[display(fmt = "invalid public key bytes: {_0}")] + PublicKeyBytes(crypto::Error), + #[display(fmt = "invalid signature bytes: {_0}")] + SignatureBytes(crypto::Error), +} + +#[cfg(feature = "std")] +impl std::error::Error for SignatureError {} + +impl From for SignatureError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/signature/mod.rs b/sdk/src/types/block/signature/mod.rs index 2e81750a0f..4d5732426d 100644 --- a/sdk/src/types/block/signature/mod.rs +++ b/sdk/src/types/block/signature/mod.rs @@ -2,14 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 mod ed25519; +mod error; use derive_more::From; -pub use self::ed25519::Ed25519Signature; -use crate::types::block::{ - protocol::{WorkScore, WorkScoreParameters}, - Error, -}; +pub use self::{ed25519::Ed25519Signature, error::SignatureError}; +use crate::types::block::protocol::{WorkScore, WorkScoreParameters}; /// A `Signature` contains a signature which is used to unlock a transaction input. /// @@ -17,8 +15,8 @@ use crate::types::block::{ /// /// RFC: #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, packable::Packable, From)] -#[packable(unpack_error = Error)] -#[packable(tag_type = u8, with_error = Error::InvalidSignatureKind)] +#[packable(unpack_error = SignatureError)] +#[packable(tag_type = u8, with_error = SignatureError::Kind)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] pub enum Signature { /// An Ed25519 signature. diff --git a/sdk/src/types/block/slot/commitment.rs b/sdk/src/types/block/slot/commitment.rs index 45b3d2f6a5..5b23a1d200 100644 --- a/sdk/src/types/block/slot/commitment.rs +++ b/sdk/src/types/block/slot/commitment.rs @@ -4,15 +4,11 @@ use crypto::hashes::{blake2b::Blake2b256, Digest}; use packable::{Packable, PackableExt}; -use crate::types::block::{ - slot::{commitment_id::SlotCommitmentHash, RootsId, SlotCommitmentId, SlotIndex}, - Error, -}; +use crate::types::block::slot::{commitment_id::SlotCommitmentHash, RootsId, SlotCommitmentId, SlotIndex}; /// Contains a summary of a slot. /// It is linked to the commitment of the previous slot, which forms a commitment chain. #[derive(Clone, Debug, Eq, PartialEq, Hash, derive_more::From, Packable)] -#[packable(unpack_error = Error)] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), diff --git a/sdk/src/types/block/slot/epoch.rs b/sdk/src/types/block/slot/epoch.rs index 8fa9427457..d50d7988cb 100644 --- a/sdk/src/types/block/slot/epoch.rs +++ b/sdk/src/types/block/slot/epoch.rs @@ -141,20 +141,17 @@ impl core::ops::SubAssign for EpochIndex { } } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod test { use pretty_assertions::assert_eq; use super::*; - use crate::types::block::protocol::ProtocolParameters; + use crate::types::block::protocol::iota_mainnet_protocol_parameters; #[test] fn epoch_index_to_from_slot() { - let params = ProtocolParameters { - version: 3, - slots_per_epoch_exponent: 10, - ..Default::default() - }; + let mut params = iota_mainnet_protocol_parameters().clone(); + params.slots_per_epoch_exponent = 10; let slot_index = SlotIndex(3000); let epoch_index = EpochIndex::from_slot_index(slot_index, params.genesis_slot, params.slots_per_epoch_exponent()); diff --git a/sdk/src/types/block/slot/index.rs b/sdk/src/types/block/slot/index.rs index ad4c7ce496..5b705abe4e 100644 --- a/sdk/src/types/block/slot/index.rs +++ b/sdk/src/types/block/slot/index.rs @@ -124,15 +124,15 @@ impl From for u32 { } } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod test { use pretty_assertions::assert_eq; - use crate::types::block::protocol::ProtocolParameters; + use crate::types::block::protocol::iota_mainnet_protocol_parameters; #[test] fn to_from_timestamp() { - let protocol_params = ProtocolParameters::default(); + let protocol_params = iota_mainnet_protocol_parameters(); // Timestamp before the genesis let timestamp = protocol_params.genesis_unix_timestamp() - 100; diff --git a/sdk/src/types/block/unlock/account.rs b/sdk/src/types/block/unlock/account.rs index b33297478c..949840ddf3 100644 --- a/sdk/src/types/block/unlock/account.rs +++ b/sdk/src/types/block/unlock/account.rs @@ -1,11 +1,14 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::{protocol::WorkScore, unlock::UnlockIndex, Error}; +use crate::types::block::{ + protocol::WorkScore, + unlock::{UnlockError, UnlockIndex}, +}; /// Points to the unlock of a consumed account output. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = Error::InvalidAccountIndex)] +#[packable(unpack_error = UnlockError, with = UnlockError::AccountIndex)] pub struct AccountUnlock( /// Index of input and unlock corresponding to an [`AccountOutput`](crate::types::block::output::AccountOutput). UnlockIndex, @@ -17,8 +20,8 @@ impl AccountUnlock { /// Creates a new [`AccountUnlock`]. #[inline(always)] - pub fn new(index: u16) -> Result { - index.try_into().map(Self).map_err(Error::InvalidAccountIndex) + pub fn new(index: u16) -> Result { + index.try_into().map(Self).map_err(UnlockError::AccountIndex) } /// Return the index of an [`AccountUnlock`]. @@ -31,7 +34,7 @@ impl AccountUnlock { impl WorkScore for AccountUnlock {} impl TryFrom for AccountUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(index: u16) -> Result { Self::new(index) @@ -62,7 +65,7 @@ mod dto { } impl TryFrom for AccountUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(value: AccountUnlockDto) -> Result { Self::new(value.index) diff --git a/sdk/src/types/block/unlock/anchor.rs b/sdk/src/types/block/unlock/anchor.rs index 1b0a7a43b2..cbd99bc4cc 100644 --- a/sdk/src/types/block/unlock/anchor.rs +++ b/sdk/src/types/block/unlock/anchor.rs @@ -1,11 +1,14 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::{protocol::WorkScore, unlock::UnlockIndex, Error}; +use crate::types::block::{ + protocol::WorkScore, + unlock::{UnlockError, UnlockIndex}, +}; /// Points to the unlock of a consumed anchor output. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = Error::InvalidAnchorIndex)] +#[packable(unpack_error = UnlockError, with = UnlockError::AnchorIndex)] pub struct AnchorUnlock( /// Index of input and unlock corresponding to an [`AnchorOutput`](crate::types::block::output::AnchorOutput). UnlockIndex, @@ -17,8 +20,8 @@ impl AnchorUnlock { /// Creates a new [`AnchorUnlock`]. #[inline(always)] - pub fn new(index: u16) -> Result { - index.try_into().map(Self).map_err(Error::InvalidAnchorIndex) + pub fn new(index: u16) -> Result { + index.try_into().map(Self).map_err(UnlockError::AnchorIndex) } /// Return the index of an [`AnchorUnlock`]. @@ -31,7 +34,7 @@ impl AnchorUnlock { impl WorkScore for AnchorUnlock {} impl TryFrom for AnchorUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(index: u16) -> Result { Self::new(index) @@ -62,7 +65,7 @@ mod dto { } impl TryFrom for AnchorUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(value: AnchorUnlockDto) -> Result { Self::new(value.index) diff --git a/sdk/src/types/block/unlock/empty.rs b/sdk/src/types/block/unlock/empty.rs index 1778542695..e613144d7e 100644 --- a/sdk/src/types/block/unlock/empty.rs +++ b/sdk/src/types/block/unlock/empty.rs @@ -20,7 +20,6 @@ mod dto { use serde::{Deserialize, Serialize}; use super::*; - use crate::types::block::Error; #[derive(Serialize, Deserialize)] struct EmptyUnlockDto { @@ -36,11 +35,9 @@ mod dto { } } - impl TryFrom for EmptyUnlock { - type Error = Error; - - fn try_from(_: EmptyUnlockDto) -> Result { - Ok(Self) + impl From for EmptyUnlock { + fn from(_: EmptyUnlockDto) -> Self { + Self } } diff --git a/sdk/src/types/block/unlock/error.rs b/sdk/src/types/block/unlock/error.rs new file mode 100644 index 0000000000..e358a7f80c --- /dev/null +++ b/sdk/src/types/block/unlock/error.rs @@ -0,0 +1,51 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::Infallible; + +use crate::types::block::{ + signature::SignatureError, + unlock::{multi::UnlocksCount, UnlockCount, UnlockIndex}, +}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display, derive_more::From)] +#[allow(missing_docs)] +pub enum UnlockError { + #[display(fmt = "invalid unlock kind: {_0}")] + Kind(u8), + #[display(fmt = "invalid unlock count: {_0}")] + Count(>::Error), + #[display(fmt = "invalid unlock reference: {_0}")] + Reference(u16), + #[display(fmt = "invalid unlock account: {_0}")] + Account(u16), + #[display(fmt = "invalid unlock nft: {_0}")] + Nft(u16), + #[display(fmt = "invalid unlock anchor: {_0}")] + Anchor(u16), + #[display(fmt = "duplicate signature unlock at index: {_0}")] + DuplicateSignature(u16), + #[display(fmt = "multi unlock recursion")] + MultiUnlockRecursion, + #[display(fmt = "invalid account index: {_0}")] + AccountIndex(>::Error), + #[display(fmt = "invalid anchor index: {_0}")] + AnchorIndex(>::Error), + #[display(fmt = "invalid nft index: {_0}")] + NftIndex(>::Error), + #[display(fmt = "invalid reference index: {_0}")] + ReferenceIndex(>::Error), + #[display(fmt = "invalid multi unlock count: {_0}")] + MultiUnlockCount(>::Error), + #[from] + Signature(SignatureError), +} + +#[cfg(feature = "std")] +impl std::error::Error for UnlockError {} + +impl From for UnlockError { + fn from(error: Infallible) -> Self { + match error {} + } +} diff --git a/sdk/src/types/block/unlock/mod.rs b/sdk/src/types/block/unlock/mod.rs index 7e5f791ba9..852cdd38d3 100644 --- a/sdk/src/types/block/unlock/mod.rs +++ b/sdk/src/types/block/unlock/mod.rs @@ -4,6 +4,7 @@ mod account; mod anchor; mod empty; +mod error; mod multi; mod nft; mod reference; @@ -16,15 +17,13 @@ use derive_more::{Deref, From}; use hashbrown::HashSet; use packable::{bounded::BoundedU16, prefix::BoxedSlicePrefix, Packable}; -pub(crate) use self::multi::UnlocksCount; pub use self::{ - account::AccountUnlock, anchor::AnchorUnlock, empty::EmptyUnlock, multi::MultiUnlock, nft::NftUnlock, - reference::ReferenceUnlock, signature::SignatureUnlock, + account::AccountUnlock, anchor::AnchorUnlock, empty::EmptyUnlock, error::UnlockError, multi::MultiUnlock, + nft::NftUnlock, reference::ReferenceUnlock, signature::SignatureUnlock, }; use crate::types::block::{ input::{INPUT_COUNT_MAX, INPUT_COUNT_RANGE, INPUT_INDEX_MAX}, protocol::{WorkScore, WorkScoreParameters}, - Error, }; /// The maximum number of unlocks of a transaction. @@ -40,8 +39,8 @@ pub(crate) type UnlockIndex = BoundedU16<{ *UNLOCK_INDEX_RANGE.start() }, { *UNL /// Defines the mechanism by which a transaction input is authorized to be consumed. #[derive(Clone, Eq, PartialEq, Hash, From, Packable)] -#[packable(unpack_error = Error)] -#[packable(tag_type = u8, with_error = Error::InvalidUnlockKind)] +#[packable(unpack_error = UnlockError)] +#[packable(tag_type = u8, with_error = UnlockError::Kind)] #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))] pub enum Unlock { /// A signature unlock. @@ -123,16 +122,15 @@ pub(crate) type UnlockCount = BoundedU16<{ *UNLOCK_COUNT_RANGE.start() }, { *UNL /// A collection of unlocks. #[derive(Clone, Debug, Eq, PartialEq, Deref, Packable)] -#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidUnlockCount(p.into())))] +#[packable(unpack_error = UnlockError, with = |e| e.unwrap_item_err_or_else(|p| UnlockError::Count(p.into())))] pub struct Unlocks(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefix); impl Unlocks { /// Creates a new [`Unlocks`]. - pub fn new(unlocks: impl Into>) -> Result { - let unlocks: BoxedSlicePrefix = - unlocks.into().try_into().map_err(Error::InvalidUnlockCount)?; + pub fn new(unlocks: impl Into>) -> Result { + let unlocks: BoxedSlicePrefix = unlocks.into().try_into().map_err(UnlockError::Count)?; - verify_unlocks::(&unlocks)?; + verify_unlocks(&unlocks)?; Ok(Self(unlocks)) } @@ -155,11 +153,11 @@ fn verify_non_multi_unlock<'a>( unlock: &'a Unlock, index: u16, seen_signatures: &mut HashSet<&'a SignatureUnlock>, -) -> Result<(), Error> { +) -> Result<(), UnlockError> { match unlock { Unlock::Signature(signature) => { if !seen_signatures.insert(signature.as_ref()) { - return Err(Error::DuplicateSignatureUnlock(index)); + return Err(UnlockError::DuplicateSignature(index)); } } Unlock::Reference(reference) => { @@ -167,44 +165,42 @@ fn verify_non_multi_unlock<'a>( || reference.index() >= index || !matches!(unlocks[reference.index() as usize], Unlock::Signature(_)) { - return Err(Error::InvalidUnlockReference(index)); + return Err(UnlockError::Reference(index)); } } Unlock::Account(account) => { if index == 0 || account.index() >= index { - return Err(Error::InvalidUnlockAccount(index)); + return Err(UnlockError::Account(index)); } } Unlock::Anchor(anchor) => { if index == 0 || anchor.index() >= index { - return Err(Error::InvalidUnlockAnchor(index)); + return Err(UnlockError::Anchor(index)); } } Unlock::Nft(nft) => { if index == 0 || nft.index() >= index { - return Err(Error::InvalidUnlockNft(index)); + return Err(UnlockError::Nft(index)); } } - Unlock::Multi(_) => return Err(Error::MultiUnlockRecursion), + Unlock::Multi(_) => return Err(UnlockError::MultiUnlockRecursion), Unlock::Empty(_) => {} } Ok(()) } -fn verify_unlocks(unlocks: &[Unlock]) -> Result<(), Error> { - if VERIFY { - let mut seen_signatures = HashSet::new(); +fn verify_unlocks(unlocks: &[Unlock]) -> Result<(), UnlockError> { + let mut seen_signatures = HashSet::new(); - for (index, unlock) in (0u16..).zip(unlocks.iter()) { - match unlock { - Unlock::Multi(multi) => { - for unlock in multi.unlocks() { - verify_non_multi_unlock(unlocks, unlock, index, &mut seen_signatures)? - } + for (index, unlock) in (0u16..).zip(unlocks.iter()) { + match unlock { + Unlock::Multi(multi) => { + for unlock in multi.unlocks() { + verify_non_multi_unlock(unlocks, unlock, index, &mut seen_signatures)? } - _ => verify_non_multi_unlock(unlocks, unlock, index, &mut seen_signatures)?, } + _ => verify_non_multi_unlock(unlocks, unlock, index, &mut seen_signatures)?, } } diff --git a/sdk/src/types/block/unlock/multi.rs b/sdk/src/types/block/unlock/multi.rs index 9d47ca22ce..99396bc04a 100644 --- a/sdk/src/types/block/unlock/multi.rs +++ b/sdk/src/types/block/unlock/multi.rs @@ -9,15 +9,14 @@ use packable::{prefix::BoxedSlicePrefix, Packable}; use crate::types::block::{ address::WeightedAddressCount, protocol::{WorkScore, WorkScoreParameters}, - unlock::Unlock, - Error, + unlock::{Unlock, UnlockError}, }; pub(crate) type UnlocksCount = WeightedAddressCount; /// Unlocks a [`MultiAddress`](crate::types::block::address::MultiAddress) with a list of other unlocks. #[derive(Clone, Debug, Deref, Eq, PartialEq, Hash, Packable)] -#[packable(unpack_error = Error, with = |e| e.unwrap_item_err_or_else(|p| Error::InvalidMultiUnlockCount(p.into())))] +#[packable(unpack_error = UnlockError, with = |e| e.unwrap_item_err_or_else(|p| UnlockError::MultiUnlockCount(p.into())))] pub struct MultiUnlock(#[packable(verify_with = verify_unlocks)] BoxedSlicePrefix); impl MultiUnlock { @@ -26,13 +25,13 @@ impl MultiUnlock { /// Creates a new [`MultiUnlock`]. #[inline(always)] - pub fn new(unlocks: impl IntoIterator) -> Result { + pub fn new(unlocks: impl IntoIterator) -> Result { let unlocks = unlocks.into_iter().collect::>(); - verify_unlocks::(&unlocks)?; + verify_unlocks(&unlocks)?; Ok(Self( - BoxedSlicePrefix::::try_from(unlocks).map_err(Error::InvalidMultiUnlockCount)?, + BoxedSlicePrefix::::try_from(unlocks).map_err(UnlockError::MultiUnlockCount)?, )) } @@ -49,9 +48,9 @@ impl WorkScore for MultiUnlock { } } -fn verify_unlocks(unlocks: &[Unlock]) -> Result<(), Error> { - if VERIFY && unlocks.iter().any(Unlock::is_multi) { - Err(Error::MultiUnlockRecursion) +fn verify_unlocks(unlocks: &[Unlock]) -> Result<(), UnlockError> { + if unlocks.iter().any(Unlock::is_multi) { + Err(UnlockError::MultiUnlockRecursion) } else { Ok(()) } @@ -82,7 +81,7 @@ mod dto { } impl TryFrom for MultiUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(value: MultiUnlockDto) -> Result { Self::new(value.unlocks) diff --git a/sdk/src/types/block/unlock/nft.rs b/sdk/src/types/block/unlock/nft.rs index cc7b6d4e79..36c031940c 100644 --- a/sdk/src/types/block/unlock/nft.rs +++ b/sdk/src/types/block/unlock/nft.rs @@ -1,11 +1,14 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::{protocol::WorkScore, unlock::UnlockIndex, Error}; +use crate::types::block::{ + protocol::WorkScore, + unlock::{UnlockError, UnlockIndex}, +}; /// Points to the unlock of a consumed NFT output. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = Error::InvalidNftIndex)] +#[packable(unpack_error = UnlockError, with = UnlockError::NftIndex)] pub struct NftUnlock( /// Index of input and unlock corresponding to an [`NftOutput`](crate::types::block::output::NftOutput). UnlockIndex, @@ -17,8 +20,8 @@ impl NftUnlock { /// Creates a new [`NftUnlock`]. #[inline(always)] - pub fn new(index: u16) -> Result { - index.try_into().map(Self).map_err(Error::InvalidNftIndex) + pub fn new(index: u16) -> Result { + index.try_into().map(Self).map_err(UnlockError::NftIndex) } /// Return the index of a [`NftUnlock`]. @@ -31,7 +34,7 @@ impl NftUnlock { impl WorkScore for NftUnlock {} impl TryFrom for NftUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(index: u16) -> Result { Self::new(index) @@ -62,7 +65,7 @@ pub(crate) mod dto { } impl TryFrom for NftUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(value: NftUnlockDto) -> Result { Self::new(value.index) diff --git a/sdk/src/types/block/unlock/reference.rs b/sdk/src/types/block/unlock/reference.rs index feef4e55e5..2b91fd9053 100644 --- a/sdk/src/types/block/unlock/reference.rs +++ b/sdk/src/types/block/unlock/reference.rs @@ -1,15 +1,18 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::{protocol::WorkScore, unlock::UnlockIndex, Error}; +use crate::types::block::{ + protocol::WorkScore, + unlock::{UnlockError, UnlockIndex}, +}; /// An [`Unlock`](crate::types::block::unlock::Unlock) that refers to another unlock. #[derive(Clone, Debug, Eq, PartialEq, Hash, packable::Packable)] -#[packable(unpack_error = Error, with = Error::InvalidReferenceIndex)] +#[packable(unpack_error = UnlockError, with = UnlockError::ReferenceIndex)] pub struct ReferenceUnlock(UnlockIndex); impl TryFrom for ReferenceUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(index: u16) -> Result { Self::new(index) @@ -22,8 +25,8 @@ impl ReferenceUnlock { /// Creates a new [`ReferenceUnlock`]. #[inline(always)] - pub fn new(index: u16) -> Result { - index.try_into().map(Self).map_err(Error::InvalidReferenceIndex) + pub fn new(index: u16) -> Result { + index.try_into().map(Self).map_err(UnlockError::ReferenceIndex) } /// Return the index of a [`ReferenceUnlock`]. @@ -59,7 +62,7 @@ pub(crate) mod dto { } impl TryFrom for ReferenceUnlock { - type Error = Error; + type Error = UnlockError; fn try_from(value: ReferenceUnlockDto) -> Result { Self::new(value.index) diff --git a/sdk/src/types/fuzz/Cargo.toml b/sdk/src/types/fuzz/Cargo.toml index 1d084a0ec6..7c538a8487 100644 --- a/sdk/src/types/fuzz/Cargo.toml +++ b/sdk/src/types/fuzz/Cargo.toml @@ -12,7 +12,9 @@ cargo-fuzz = true iota-types = { path = "..", default-features = false } libfuzzer-sys = { version = "0.4.7", default-features = false } -packable = { version = "0.10.1", default-features = false } +packable = { version = "0.11.0", default-features = false, features = [ + "primitive-types", +] } # Prevent this from interfering with workspaces [workspace] diff --git a/sdk/src/utils/convert.rs b/sdk/src/utils/convert.rs index f04c67e14c..ba423d1f8d 100644 --- a/sdk/src/utils/convert.rs +++ b/sdk/src/utils/convert.rs @@ -1,10 +1,30 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use crate::types::block::Error; +#[cfg(not(feature = "std"))] +use alloc::string::{String, ToString}; + +#[derive(Debug, PartialEq, Eq, derive_more::Display)] +#[allow(missing_docs)] +pub struct ConversionError(String); + +impl ConversionError { + #[cfg(feature = "std")] + pub fn new(e: E) -> Self { + Self(e.to_string()) + } + + #[cfg(not(feature = "std"))] + pub fn new(e: E) -> Self { + Self(e.to_string()) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ConversionError {} pub trait ConvertTo: Send + Sized { - fn convert(self) -> Result; + fn convert(self) -> Result; fn convert_unchecked(self) -> T { self.convert().unwrap() @@ -12,7 +32,7 @@ pub trait ConvertTo: Send + Sized { } impl ConvertTo for T { - fn convert(self) -> Result { + fn convert(self) -> Result { Ok(self) } @@ -22,7 +42,7 @@ impl ConvertTo for T { } impl ConvertTo for &T { - fn convert(self) -> Result { + fn convert(self) -> Result { Ok(*self) } diff --git a/sdk/src/utils/merkle_hasher.rs b/sdk/src/utils/merkle_hasher.rs index c63e5ce41e..4e770617e6 100644 --- a/sdk/src/utils/merkle_hasher.rs +++ b/sdk/src/utils/merkle_hasher.rs @@ -4,9 +4,9 @@ use crypto::hashes::{Digest, Output}; /// Leaf domain separation prefix. -const LEAF_HASH_PREFIX: u8 = 0x00; +pub(crate) const LEAF_HASH_PREFIX: u8 = 0x00; /// Node domain separation prefix. -const NODE_HASH_PREFIX: u8 = 0x01; +pub(crate) const NODE_HASH_PREFIX: u8 = 0x01; /// A Merkle hasher based on a digest function. pub(crate) struct MerkleHasher; @@ -48,7 +48,7 @@ impl MerkleHasher { } /// Computes the largest power of two less than or equal to `n`. -fn largest_power_of_two(n: u32) -> usize { +pub(crate) fn largest_power_of_two(n: u32) -> usize { debug_assert!(n > 1, "invalid input to `largest_power_of_two`"); 1 << (32 - (n - 1).leading_zeros() - 1) } diff --git a/sdk/src/utils/mod.rs b/sdk/src/utils/mod.rs index 1341ced35a..b503cf4226 100644 --- a/sdk/src/utils/mod.rs +++ b/sdk/src/utils/mod.rs @@ -7,4 +7,4 @@ pub mod merkle_hasher; #[cfg(feature = "serde")] pub mod serde; -pub use convert::ConvertTo; +pub use convert::{ConversionError, ConvertTo}; diff --git a/sdk/src/utils/serde.rs b/sdk/src/utils/serde.rs index 3d2fb379df..6235b76869 100644 --- a/sdk/src/utils/serde.rs +++ b/sdk/src/utils/serde.rs @@ -73,9 +73,7 @@ pub mod prefix_hex_bytes { D: Deserializer<'de>, T: FromHexPrefixed, { - prefix_hex::decode(String::deserialize(deserializer)?) - .map_err(crate::types::block::Error::Hex) - .map_err(de::Error::custom) + prefix_hex::decode(String::deserialize(deserializer)?).map_err(de::Error::custom) } } @@ -277,3 +275,32 @@ pub mod mana_rewards { .collect::, _>>() } } + +#[cfg(feature = "client")] +pub mod option_mana_rewards { + use alloc::collections::BTreeMap; + + use serde::{Deserialize, Deserializer}; + + use crate::types::block::output::OutputId; + + pub fn serialize( + mana_rewards: &Option>, + s: S, + ) -> Result { + match mana_rewards { + Some(map) => super::mana_rewards::serialize(map, s), + None => s.serialize_none(), + } + } + + pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result>, D::Error> { + Option::>::deserialize(d)? + .map(|map| { + map.into_iter() + .map(|(k, v)| Ok((k, v.parse().map_err(serde::de::Error::custom)?))) + .collect::, _>>() + }) + .transpose() + } +} diff --git a/sdk/src/wallet/core/builder.rs b/sdk/src/wallet/core/builder.rs index ae8df31326..7848733294 100644 --- a/sdk/src/wallet/core/builder.rs +++ b/sdk/src/wallet/core/builder.rs @@ -16,12 +16,14 @@ use crate::wallet::storage::adapter::memory::Memory; #[cfg(feature = "storage")] use crate::wallet::storage::{StorageManager, StorageOptions}; use crate::{ - client::secret::{GenerateAddressOptions, SecretManage, SecretManager}, - types::block::address::{Address, Bech32Address}, + client::{ + secret::{GenerateAddressOptions, SecretManage, SecretManager}, + ClientError, + }, + types::block::address::{Bech32Address, Ed25519Address}, wallet::{ - core::{operations::background_syncing::BackgroundSyncStatus, Bip44, WalletData, WalletInner}, - operations::syncing::SyncOptions, - ClientOptions, Wallet, + core::{operations::background_syncing::BackgroundSyncStatus, Bip44, WalletInner, WalletLedger}, + ClientOptions, Wallet, WalletError, }, }; @@ -29,8 +31,8 @@ use crate::{ #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct WalletBuilder { - pub(crate) bip_path: Option, pub(crate) address: Option, + pub(crate) bip_path: Option, pub(crate) alias: Option, pub(crate) client_options: Option, #[cfg(feature = "storage")] @@ -42,8 +44,8 @@ pub struct WalletBuilder { impl Default for WalletBuilder { fn default() -> Self { Self { - bip_path: Default::default(), address: Default::default(), + bip_path: Default::default(), alias: Default::default(), client_options: Default::default(), #[cfg(feature = "storage")] @@ -55,25 +57,22 @@ impl Default for WalletBuilder { impl WalletBuilder where - crate::wallet::Error: From, + WalletError: From, { /// Initialises a new instance of the wallet builder with the default storage adapter. pub fn new() -> Self { - Self { - secret_manager: None, - ..Default::default() - } + Self::default() } - /// Set the BIP44 path of the wallet. - pub fn with_bip_path(mut self, bip_path: impl Into>) -> Self { - self.bip_path = bip_path.into(); + /// Set the address of the wallet. + pub fn with_address(mut self, address: impl Into>) -> Self { + self.address = address.into(); self } - /// Set the wallet address. - pub fn with_address(mut self, address: impl Into>) -> Self { - self.address = address.into(); + /// Set the BIP44 path of the wallet. + pub fn with_bip_path(mut self, bip_path: impl Into>) -> Self { + self.bip_path = bip_path.into(); self } @@ -124,12 +123,12 @@ where impl WalletBuilder where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, Self: SaveLoadWallet, { /// Builds the wallet. - pub async fn finish(mut self) -> crate::wallet::Result> { + pub async fn finish(mut self) -> Result, WalletError> { log::debug!("[WalletBuilder]"); #[cfg(feature = "storage")] @@ -138,7 +137,7 @@ where // would be created with an empty parameter which just leads to errors later #[cfg(feature = "storage")] if !storage_options.path.is_dir() && self.client_options.is_none() { - return Err(crate::wallet::Error::MissingParameter("client_options")); + return Err(WalletError::MissingParameter("client_options")); } #[cfg(all(feature = "rocksdb", feature = "storage"))] @@ -160,7 +159,7 @@ where let loaded_client_options = loaded_wallet_builder .as_ref() .and_then(|data| data.client_options.clone()) - .ok_or(crate::wallet::Error::MissingParameter("client_options"))?; + .ok_or(WalletError::MissingParameter("client_options"))?; // Update self so it gets used and stored again self.client_options = Some(loaded_client_options); @@ -171,58 +170,89 @@ where // May use a previously stored secret manager if it wasn't provided if self.secret_manager.is_none() { - let secret_manager = loaded_wallet_builder - .as_ref() - .and_then(|builder| builder.secret_manager.clone()); - - self.secret_manager = secret_manager; - } - - // May use a previously stored BIP path if it wasn't provided - if self.bip_path.is_none() { - self.bip_path = loaded_wallet_builder.as_ref().and_then(|builder| builder.bip_path); + self.secret_manager.replace( + loaded_wallet_builder + .as_ref() + .and_then(|builder| builder.secret_manager.clone()) + .ok_or(WalletError::MissingParameter("secret_manager"))?, + ); } - // May use a previously stored wallet alias if it wasn't provided - if self.alias.is_none() { - self.alias = loaded_wallet_builder.as_ref().and_then(|builder| builder.alias.clone()); + let mut verify_address = false; + let loaded_address = loaded_wallet_builder + .as_ref() + .and_then(|builder| builder.address.clone()); + + // May use a previously stored address if it wasn't provided + if let Some(address) = &self.address { + if let Some(loaded_address) = &loaded_address { + if address != loaded_address { + return Err(WalletError::WalletAddressMismatch(address.clone())); + } + } else { + verify_address = true; + } + } else { + self.address = loaded_address; } - // May use a previously stored wallet address if it wasn't provided - if self.address.is_none() { - self.address = loaded_wallet_builder - .as_ref() - .and_then(|builder| builder.address.clone()); - } + let loaded_bip_path = loaded_wallet_builder.as_ref().and_then(|builder| builder.bip_path); - // May create a default Ed25519 wallet address if there's a secret manager. - if self.address.is_none() { - if self.secret_manager.is_some() { - let address = self.create_default_wallet_address().await?; - self.address = Some(address); + // May use a previously stored BIP path if it wasn't provided + if let Some(bip_path) = self.bip_path { + if let Some(loaded_bip_path) = loaded_bip_path { + if bip_path != loaded_bip_path { + return Err(WalletError::BipPathMismatch { + new_bip_path: Some(bip_path), + old_bip_path: Some(loaded_bip_path), + }); + } } else { - return Err(crate::wallet::Error::MissingParameter("address")); + verify_address = true; } + } else { + self.bip_path = loaded_bip_path; } - // Panic: can be safely unwrapped now - let address = self.address.as_ref().unwrap().clone(); - #[cfg(feature = "storage")] - let mut wallet_data = storage_manager.load_wallet_data().await?; + // Create the node client. + let client = self + .client_options + .clone() + .ok_or(WalletError::MissingParameter("client_options"))? + .finish() + .await?; - // The bip path must not change. - #[cfg(feature = "storage")] - if let Some(wallet_data) = &wallet_data { - let new_bip_path = self.bip_path; - let old_bip_path = wallet_data.bip_path; - if new_bip_path != old_bip_path { - return Err(crate::wallet::Error::BipPathMismatch { - new_bip_path, - old_bip_path, - }); + match (self.address.as_ref(), self.bip_path.as_ref()) { + (Some(address), Some(bip_path)) => { + if verify_address { + // verify that the address is derived from the provided bip path. + if let Some(backing_ed25519_address) = address.inner.backing_ed25519() { + self.verify_ed25519_address(backing_ed25519_address, bip_path).await?; + } else { + return Err(WalletError::InvalidParameter("address/bip_path mismatch")); + } + } + } + (Some(_address), None) => {} + (None, Some(bip_path)) => { + self.address.replace(Bech32Address::new( + client.get_bech32_hrp().await?, + self.generate_ed25519_address(bip_path).await?, + )); + } + (None, None) => { + return Err(WalletError::MissingParameter("address or bip_path")); } + }; + + // May use a previously stored wallet alias if it wasn't provided + if self.alias.is_none() { + self.alias = loaded_wallet_builder.as_ref().and_then(|builder| builder.alias.clone()); } + #[cfg(feature = "storage")] + let mut wallet_ledger = storage_manager.load_wallet_ledger().await?; + // Store the wallet builder (for convenience reasons) #[cfg(feature = "storage")] self.save(&storage_manager).await?; @@ -230,27 +260,25 @@ where // It happened that inputs got locked, the transaction failed, but they weren't unlocked again, so we do this // here #[cfg(feature = "storage")] - if let Some(wallet_data) = &mut wallet_data { - unlock_unused_inputs(wallet_data)?; + if let Some(wallet_ledger) = &mut wallet_ledger { + unlock_unused_inputs(wallet_ledger)?; } - // Create the node client. - let client = self - .client_options - .clone() - .ok_or(crate::wallet::Error::MissingParameter("client_options"))? - .finish() - .await?; + #[cfg(feature = "storage")] + let default_sync_options = storage_manager.get_default_sync_options().await?.unwrap_or_default(); + #[cfg(not(feature = "storage"))] + let default_sync_options = crate::wallet::SyncOptions::default(); let background_syncing_status = tokio::sync::watch::channel(BackgroundSyncStatus::Stopped); let background_syncing_status = (Arc::new(background_syncing_status.0), background_syncing_status.1); // Build the wallet. let wallet_inner = WalletInner { - default_sync_options: Mutex::new(SyncOptions::default()), + default_sync_options: Mutex::new(default_sync_options), last_synced: Mutex::new(0), background_syncing_status, client, + // TODO: make secret manager optional secret_manager: self.secret_manager.expect("make WalletInner::secret_manager optional?"), #[cfg(feature = "events")] event_emitter: tokio::sync::RwLock::new(EventEmitter::new()), @@ -260,83 +288,86 @@ where storage_manager, }; #[cfg(feature = "storage")] - let wallet_data = wallet_data.unwrap_or_else(|| WalletData::new(self.bip_path, address, self.alias.clone())); + let wallet_ledger = wallet_ledger.unwrap_or_default(); #[cfg(not(feature = "storage"))] - let wallet_data = WalletData::new(self.bip_path, address, self.alias.clone()); + let wallet_ledger = WalletLedger::default(); + let wallet = Wallet { + // Unwrap: The address is always set above (or we already returned) + address: Arc::new(RwLock::new(self.address.unwrap())), + bip_path: Arc::new(RwLock::new(self.bip_path)), + alias: Arc::new(RwLock::new(self.alias)), inner: Arc::new(wallet_inner), - data: Arc::new(RwLock::new(wallet_data)), + ledger: Arc::new(RwLock::new(wallet_ledger)), }; // If the wallet builder is not set, it means the user provided it and we need to update the addresses. // In the other case it was loaded from the database and addresses are up to date. if provided_client_options { - wallet.update_bech32_hrp().await?; + wallet.update_address_hrp().await?; } Ok(wallet) } - /// Generate the wallet address. - pub(crate) async fn create_default_wallet_address(&self) -> crate::wallet::Result { - let bech32_hrp = self - .client_options - .as_ref() - .unwrap() - .network_info - .protocol_parameters - .bech32_hrp; - let bip_path = self.bip_path.as_ref().unwrap(); - - Ok(Bech32Address::new( - bech32_hrp, - Address::Ed25519( - self.secret_manager - .as_ref() - .unwrap() - .read() - .await - .generate_ed25519_addresses( - bip_path.coin_type, - bip_path.account, - bip_path.address_index..bip_path.address_index + 1, - GenerateAddressOptions { - internal: bip_path.change != 0, - ledger_nano_prompt: false, - }, - ) - .await?[0], - ), - )) - } - #[cfg(feature = "storage")] pub(crate) async fn from_wallet(wallet: &Wallet) -> Self { Self { - bip_path: wallet.bip_path().await, address: Some(wallet.address().await), + bip_path: wallet.bip_path().await, alias: wallet.alias().await, client_options: Some(wallet.client_options().await), storage_options: Some(wallet.storage_options.clone()), secret_manager: Some(wallet.secret_manager.clone()), } } + + #[inline(always)] + async fn verify_ed25519_address( + &self, + ed25519_address: &Ed25519Address, + bip_path: &Bip44, + ) -> Result<(), WalletError> { + (ed25519_address == &self.generate_ed25519_address(bip_path).await?) + .then_some(()) + .ok_or(WalletError::InvalidParameter("address/bip_path mismatch")) + } + + async fn generate_ed25519_address(&self, bip_path: &Bip44) -> Result { + if let Some(secret_manager) = &self.secret_manager { + let secret_manager = &*secret_manager.read().await; + Ok(secret_manager + .generate_ed25519_addresses( + bip_path.coin_type, + bip_path.account, + bip_path.address_index..bip_path.address_index + 1, + GenerateAddressOptions { + internal: bip_path.change != 0, + ledger_nano_prompt: false, + }, + ) + // Panic: if it didn't return an Err, then there must be at least one address + .await?[0]) + } else { + Err(WalletError::MissingParameter("secret_manager")) + } + } } // Check if any of the locked inputs is not used in a transaction and unlock them, so they get available for new // transactions #[cfg(feature = "storage")] -fn unlock_unused_inputs(wallet_data: &mut WalletData) -> crate::wallet::Result<()> { +fn unlock_unused_inputs(wallet_ledger: &mut WalletLedger) -> Result<(), WalletError> { log::debug!("[unlock_unused_inputs]"); let mut used_inputs = HashSet::new(); - for transaction_id in &wallet_data.pending_transactions { - if let Some(tx) = wallet_data.transactions.get(transaction_id) { + for transaction_id in &wallet_ledger.pending_transactions { + if let Some(tx) = wallet_ledger.transactions.get(transaction_id) { for input in &tx.inputs { used_inputs.insert(*input.metadata.output_id()); } } } - wallet_data.locked_outputs.retain(|input| { + wallet_ledger.locked_outputs.retain(|input| { let used = used_inputs.contains(input); if !used { log::debug!("unlocking unused input {input}"); @@ -357,11 +388,11 @@ pub(crate) mod dto { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WalletBuilderDto { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub(crate) bip_path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) address: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) bip_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) alias: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) client_options: Option, @@ -373,8 +404,8 @@ pub(crate) mod dto { impl From for WalletBuilder { fn from(value: WalletBuilderDto) -> Self { Self { - bip_path: value.bip_path, address: value.address, + bip_path: value.bip_path, alias: value.alias, client_options: value.client_options, #[cfg(feature = "storage")] diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index 59f5b92e1d..6e7a8e30cd 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -40,21 +40,27 @@ use crate::{ }, TryFromDto, }, - wallet::{operations::syncing::SyncOptions, types::OutputData, Error, FilterOptions, Result}, + wallet::{operations::syncing::SyncOptions, types::OutputData, FilterOptions, WalletError}, }; /// The stateful wallet used to interact with an IOTA network. #[derive(Debug)] pub struct Wallet { + pub(crate) address: Arc>, + pub(crate) bip_path: Arc>>, + pub(crate) alias: Arc>>, pub(crate) inner: Arc>, - pub(crate) data: Arc>, + pub(crate) ledger: Arc>, } impl Clone for Wallet { fn clone(&self) -> Self { Self { + address: self.address.clone(), + bip_path: self.bip_path.clone(), + alias: self.alias.clone(), inner: self.inner.clone(), - data: self.data.clone(), + ledger: self.ledger.clone(), } } } @@ -69,7 +75,7 @@ impl core::ops::Deref for Wallet { impl Wallet where - crate::wallet::Error: From, + WalletError: From, { /// Initialises the wallet builder. pub fn builder() -> WalletBuilder { @@ -100,15 +106,9 @@ pub struct WalletInner { pub(crate) storage_manager: StorageManager, } -/// Wallet data. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct WalletData { - /// The wallet BIP44 path. - pub(crate) bip_path: Option, - /// The wallet address. - pub(crate) address: Bech32Address, - /// The wallet alias. - pub(crate) alias: Option, +/// Wallet ledger. +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct WalletLedger { /// Outputs // stored separated from the wallet for performance? pub(crate) outputs: HashMap, @@ -137,23 +137,7 @@ pub struct WalletData { pub(crate) native_token_foundries: HashMap, } -impl WalletData { - pub(crate) fn new(bip_path: Option, address: Bech32Address, alias: Option) -> Self { - Self { - bip_path, - address, - alias, - outputs: HashMap::new(), - locked_outputs: HashSet::new(), - unspent_outputs: HashMap::new(), - transactions: HashMap::new(), - pending_transactions: HashSet::new(), - incoming_transactions: HashMap::new(), - inaccessible_incoming_transactions: HashSet::new(), - native_token_foundries: HashMap::new(), - } - } - +impl WalletLedger { fn filter_outputs<'a>( outputs: impl Iterator, filter: FilterOptions, @@ -355,42 +339,13 @@ impl WalletData { } } -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ - /// Create a new wallet. - pub(crate) async fn new(inner: Arc>, data: WalletData) -> Result { - #[cfg(feature = "storage")] - let default_sync_options = inner - .storage_manager - .get_default_sync_options() - .await? - .unwrap_or_default(); - #[cfg(not(feature = "storage"))] - let default_sync_options = Default::default(); - - // TODO: maybe move this into a `reset` fn or smth to avoid this kinda-weird block. - { - let mut last_synced = inner.last_synced.lock().await; - *last_synced = Default::default(); - let mut sync_options = inner.default_sync_options.lock().await; - *sync_options = default_sync_options; - } - - Ok(Self { - inner, - data: Arc::new(RwLock::new(data)), - }) - } - +impl Wallet { /// Get the [`Output`] that minted a native token by the token ID. First try to get it /// from the wallet, if it isn't in the wallet try to get it from the node - pub async fn get_foundry_output(&self, native_token_id: TokenId) -> Result { + pub async fn get_foundry_output(&self, native_token_id: TokenId) -> Result { let foundry_id = FoundryId::from(native_token_id); - for output_data in self.data.read().await.outputs.values() { + for output_data in self.ledger.read().await.outputs.values() { if let Output::Foundry(foundry_output) = &output_data.output { if foundry_output.id() == foundry_id { return Ok(output_data.output.clone()); @@ -410,32 +365,55 @@ where self.inner.emit(wallet_event).await } - pub async fn data(&self) -> tokio::sync::RwLockReadGuard<'_, WalletData> { - self.data.read().await + /// Get the wallet address. + pub async fn address(&self) -> Bech32Address { + self.address.read().await.clone() + } + + pub(crate) async fn address_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, Bech32Address> { + self.address.write().await } - pub(crate) async fn data_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, WalletData> { - self.data.write().await + /// Get the wallet's Bech32 HRP. + pub async fn bech32_hrp(&self) -> Hrp { + self.address.read().await.hrp } - #[cfg(feature = "storage")] - pub(crate) fn storage_manager(&self) -> &StorageManager { - &self.storage_manager + /// Get the wallet's bip path. + pub async fn bip_path(&self) -> Option { + *self.bip_path.read().await + } + + pub(crate) async fn bip_path_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, Option> { + self.bip_path.write().await } /// Get the alias of the wallet if one was set. pub async fn alias(&self) -> Option { - self.data().await.alias.clone() + self.alias.read().await.clone() } - /// Get the wallet address. - pub async fn address(&self) -> Bech32Address { - self.data().await.address.clone() + pub(crate) async fn alias_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, Option> { + self.alias.write().await + } + + /// Get the wallet's ledger state. + pub async fn ledger(&self) -> tokio::sync::RwLockReadGuard<'_, WalletLedger> { + self.ledger.read().await + } + + pub(crate) async fn ledger_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, WalletLedger> { + self.ledger.write().await + } + + #[cfg(feature = "storage")] + pub(crate) fn storage_manager(&self) -> &StorageManager { + &self.storage_manager } /// Returns the implicit account creation address of the wallet if it is Ed25519 based. - pub async fn implicit_account_creation_address(&self) -> Result { - let bech32_address = &self.data().await.address; + pub async fn implicit_account_creation_address(&self) -> Result { + let bech32_address = &self.address().await; if let Address::Ed25519(address) = bech32_address.inner() { Ok(Bech32Address::new( @@ -443,28 +421,18 @@ where ImplicitAccountCreationAddress::from(*address), )) } else { - Err(Error::NonEd25519Address) + Err(WalletError::NonEd25519Address) } } - - /// Get the wallet's configured Bech32 HRP. - pub async fn bech32_hrp(&self) -> Hrp { - self.data().await.address.hrp - } - - /// Get the wallet's configured bip path. - pub async fn bip_path(&self) -> Option { - self.data().await.bip_path - } } impl WalletInner { - /// Get the [SecretManager] - pub fn get_secret_manager(&self) -> &Arc> { + /// Get the [`SecretManager`] of the wallet. + pub fn secret_manager(&self) -> &Arc> { &self.secret_manager } - /// Listen to wallet events, empty vec will listen to all events + /// Listen to wallet events, empty vec will listen to all events. #[cfg(feature = "events")] #[cfg_attr(docsrs, doc(cfg(feature = "events")))] pub async fn listen + Send>(&self, events: I, handler: F) @@ -476,7 +444,7 @@ impl WalletInner { emitter.on(events, handler); } - /// Remove wallet event listeners, empty vec will remove all listeners + /// Remove wallet event listeners, empty vec will remove all listeners. #[cfg(feature = "events")] #[cfg_attr(docsrs, doc(cfg(feature = "events")))] pub async fn clear_listeners + Send>(&self, events: I) @@ -488,12 +456,12 @@ impl WalletInner { } /// Generates a new random mnemonic. - pub fn generate_mnemonic(&self) -> crate::wallet::Result { + pub fn generate_mnemonic(&self) -> Result { Ok(Client::generate_mnemonic()?) } /// Verify that a &str is a valid mnemonic. - pub fn verify_mnemonic(&self, mnemonic: &MnemonicRef) -> crate::wallet::Result<()> { + pub fn verify_mnemonic(&self, mnemonic: &MnemonicRef) -> Result<(), WalletError> { verify_mnemonic(mnemonic)?; Ok(()) } @@ -523,13 +491,10 @@ impl Drop for WalletInner { } } -/// Dto for the wallet data. +/// Dto for the wallet ledger. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WalletDataDto { - pub bip_path: Option, - pub address: Bech32Address, - pub alias: Option, +pub struct WalletLedgerDto { pub outputs: HashMap, pub locked_outputs: HashSet, pub unspent_outputs: HashMap, @@ -540,17 +505,14 @@ pub struct WalletDataDto { pub native_token_foundries: HashMap, } -impl TryFromDto for WalletData { - type Error = crate::wallet::Error; +impl TryFromDto for WalletLedger { + type Error = WalletError; fn try_from_dto_with_params_inner( - dto: WalletDataDto, + dto: WalletLedgerDto, params: Option<&ProtocolParameters>, ) -> core::result::Result { Ok(Self { - bip_path: dto.bip_path, - address: dto.address, - alias: dto.alias, outputs: dto.outputs, locked_outputs: dto.locked_outputs, unspent_outputs: dto.unspent_outputs, @@ -558,25 +520,22 @@ impl TryFromDto for WalletData { .transactions .into_iter() .map(|(id, o)| Ok((id, TransactionWithMetadata::try_from_dto_with_params_inner(o, params)?))) - .collect::>()?, + .collect::>()?, pending_transactions: dto.pending_transactions, incoming_transactions: dto .incoming_transactions .into_iter() .map(|(id, o)| Ok((id, TransactionWithMetadata::try_from_dto_with_params_inner(o, params)?))) - .collect::>()?, + .collect::>()?, inaccessible_incoming_transactions: Default::default(), native_token_foundries: dto.native_token_foundries, }) } } -impl From<&WalletData> for WalletDataDto { - fn from(value: &WalletData) -> Self { +impl From<&WalletLedger> for WalletLedgerDto { + fn from(value: &WalletLedger) -> Self { Self { - bip_path: value.bip_path, - address: value.address.clone(), - alias: value.alias.clone(), outputs: value.outputs.clone(), locked_outputs: value.locked_outputs.clone(), unspent_outputs: value.unspent_outputs.clone(), @@ -596,7 +555,7 @@ impl From<&WalletData> for WalletDataDto { } } -#[cfg(test)] +#[cfg(all(test, feature = "protocol_parameters_samples"))] mod test { use core::str::FromStr; @@ -607,9 +566,9 @@ mod test { types::block::{ address::{Address, Ed25519Address}, input::{Input, UtxoInput}, - output::{AddressUnlockCondition, BasicOutput, Output, StorageScoreParameters}, + output::{AddressUnlockCondition, BasicOutput, Output}, payload::signed_transaction::{SignedTransactionPayload, Transaction, TransactionId}, - protocol::ProtocolParameters, + protocol::iota_mainnet_protocol_parameters, rand::mana::rand_mana_allotment, signature::{Ed25519Signature, Signature}, unlock::{ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, @@ -624,17 +583,7 @@ mod test { #[test] fn serialize() { - let protocol_parameters = ProtocolParameters::new( - 2, - "testnet", - "rms", - StorageScoreParameters::new(500, 1, 10, 1, 1, 1), - 1_813_620_509_061_365, - 1582328545, - 10, - 20, - ) - .unwrap(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); @@ -651,8 +600,8 @@ mod test { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); let pub_key_bytes = prefix_hex::decode(ED25519_PUBLIC_KEY).unwrap(); @@ -686,13 +635,7 @@ mod test { incoming_transaction, ); - let wallet_data = WalletData { - bip_path: Some(Bip44::new(4218)), - address: crate::types::block::address::Bech32Address::from_str( - "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy", - ) - .unwrap(), - alias: Some("Alice".to_string()), + let wallet_ledger = WalletLedger { outputs: HashMap::new(), locked_outputs: HashSet::new(), unspent_outputs: HashMap::new(), @@ -703,29 +646,22 @@ mod test { native_token_foundries: HashMap::new(), }; - let deser_wallet_data = WalletData::try_from_dto( - serde_json::from_str::(&serde_json::to_string(&WalletDataDto::from(&wallet_data)).unwrap()) - .unwrap(), + let deser_wallet_ledger = WalletLedger::try_from_dto( + serde_json::from_str::( + &serde_json::to_string(&WalletLedgerDto::from(&wallet_ledger)).unwrap(), + ) + .unwrap(), ) .unwrap(); - assert_eq!(wallet_data, deser_wallet_data); + assert_eq!(wallet_ledger, deser_wallet_ledger); } - impl WalletData { - /// Returns a mock of this type with the following values: - /// index: 0, coin_type: 4218, alias: "Alice", address: - /// rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy, all other fields are set to their Rust - /// defaults. + impl WalletLedger { + // TODO: use something non-empty #[cfg(feature = "storage")] - pub(crate) fn mock() -> Self { + pub(crate) fn test_instance() -> Self { Self { - bip_path: Some(Bip44::new(4218)), - address: crate::types::block::address::Bech32Address::from_str( - "rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy", - ) - .unwrap(), - alias: Some("Alice".to_string()), outputs: HashMap::new(), locked_outputs: HashSet::new(), unspent_outputs: HashMap::new(), diff --git a/sdk/src/wallet/core/operations/address_generation.rs b/sdk/src/wallet/core/operations/address_generation.rs deleted file mode 100644 index 50427dd324..0000000000 --- a/sdk/src/wallet/core/operations/address_generation.rs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - client::secret::{GenerateAddressOptions, SecretManage, SecretManager}, - types::block::address::Ed25519Address, - wallet::{Error, Wallet}, -}; -#[cfg(all(feature = "events", feature = "ledger_nano"))] -use crate::{ - types::block::address::ToBech32Ext, - wallet::events::types::{AddressData, WalletEvent}, -}; - -impl Wallet { - /// Generate an address without storing it - /// ```ignore - /// let public_addresses = wallet - /// .generate_ed25519_address(None) - /// .await?; - /// ``` - pub async fn generate_ed25519_address( - &self, - account_index: u32, - address_index: u32, - options: impl Into> + Send, - ) -> crate::wallet::Result { - // TODO #1279: not sure yet whether we also should allow this method to generate addresses for different bip - // paths. - let coin_type = self.bip_path().await.ok_or(Error::MissingBipPath)?.coin_type; - - let address = match &*self.secret_manager.read().await { - #[cfg(feature = "ledger_nano")] - SecretManager::LedgerNano(ledger_nano) => { - // If we don't sync, then we want to display the prompt on the ledger with the address. But the user - // needs to have it visible on the computer first, so we need to generate it without the - // prompt first - let options = options.into(); - #[cfg(feature = "events")] - if options.as_ref().map_or(false, |o| o.ledger_nano_prompt) { - let changed_options = options.map(|mut options| { - // Change options so ledger will not show the prompt the first time - options.ledger_nano_prompt = false; - options - }); - // Generate without prompt to be able to display it - let address = ledger_nano - .generate_ed25519_addresses( - coin_type, - account_index, - address_index..address_index + 1, - changed_options, - ) - .await?; - - let bech32_hrp = self.bech32_hrp().await; - - self.emit(WalletEvent::LedgerAddressGeneration(AddressData { - address: address[0].to_bech32(bech32_hrp), - })) - .await; - } - // Generate with prompt so the user can verify - ledger_nano - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } - #[cfg(feature = "stronghold")] - SecretManager::Stronghold(stronghold) => { - stronghold - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } - SecretManager::Mnemonic(mnemonic) => { - mnemonic - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } - #[cfg(feature = "private_key_secret_manager")] - SecretManager::PrivateKey(private_key) => { - private_key - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } - SecretManager::Placeholder => return Err(crate::client::Error::PlaceholderSecretManager.into()), - }; - - Ok(*address - .first() - .ok_or(crate::wallet::Error::MissingParameter("address"))?) - } -} diff --git a/sdk/src/wallet/core/operations/background_syncing.rs b/sdk/src/wallet/core/operations/background_syncing.rs index 7328441fef..5d70eb14b7 100644 --- a/sdk/src/wallet/core/operations/background_syncing.rs +++ b/sdk/src/wallet/core/operations/background_syncing.rs @@ -6,8 +6,8 @@ use std::time::Duration; use tokio::time::timeout; use crate::{ - client::secret::SecretManage, - wallet::{operations::syncing::SyncOptions, task, Wallet}, + client::{secret::SecretManage, ClientError}, + wallet::{operations::syncing::SyncOptions, task, Wallet, WalletError}, }; /// The default interval for background syncing @@ -22,15 +22,15 @@ pub(crate) enum BackgroundSyncStatus { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Start the background syncing process for the wallet, default interval is 7 seconds pub async fn start_background_syncing( &self, options: Option, interval: Option, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { log::debug!("[start_background_syncing]"); let (tx_background_sync, mut rx_background_sync) = self.background_syncing_status.clone(); @@ -89,7 +89,7 @@ where } /// Stop the background syncing of the wallet - pub async fn stop_background_syncing(&self) -> crate::wallet::Result<()> { + pub async fn stop_background_syncing(&self) -> Result<(), WalletError> { log::debug!("[stop_background_syncing]"); let mut rx_background_sync = self.background_syncing_status.1.clone(); diff --git a/sdk/src/wallet/core/operations/client.rs b/sdk/src/wallet/core/operations/client.rs index 1955b3e7f5..a46468324b 100644 --- a/sdk/src/wallet/core/operations/client.rs +++ b/sdk/src/wallet/core/operations/client.rs @@ -13,9 +13,9 @@ use crate::{ node::{Node, NodeAuth, NodeDto}, }, secret::SecretManage, - Client, ClientBuilder, + Client, ClientBuilder, ClientError, NetworkInfo, }, - wallet::{Wallet, WalletBuilder}, + wallet::{Wallet, WalletBuilder, WalletError}, }; impl Wallet { @@ -30,16 +30,16 @@ impl Wallet { impl Wallet where - crate::client::Error: From, - crate::wallet::Error: From, + ClientError: From, + WalletError: From, WalletBuilder: SaveLoadWallet, { - pub async fn set_client_options(&self, client_options: ClientBuilder) -> crate::wallet::Result<()> { + pub async fn set_client_options(&self, client_options: ClientBuilder) -> Result<(), WalletError> { let ClientBuilder { node_manager_builder, #[cfg(feature = "mqtt")] broker_options, - mut network_info, + protocol_parameters, api_timeout, #[cfg(not(target_family = "wasm"))] max_parallel_api_requests, @@ -60,14 +60,21 @@ where } if change_in_node_manager { - // Update the protocol of the network_info to not have the default data, which can be wrong - // Ignore errors, because there might be no node at all and then it should still not error - if let Ok(info) = self.client.get_info().await { - network_info.protocol_parameters = info.node_info.latest_protocol_parameters().parameters.clone(); + if let Ok(node_info) = self.client.get_node_info().await { + let params = &node_info.info.latest_protocol_parameters().parameters; + + *self.client.network_info.write().await = NetworkInfo { + protocol_parameters: params.clone(), + tangle_time: node_info.info.status.relative_accepted_tangle_time, + }; + } else if let Some(protocol_parameters) = protocol_parameters { + *self.client.network_info.write().await = NetworkInfo { + protocol_parameters, + tangle_time: None, + }; } - *self.client.network_info.write().await = network_info; - self.update_bech32_hrp().await?; + self.update_address_hrp().await?; } #[cfg(feature = "storage")] @@ -81,7 +88,7 @@ where } /// Update the authentication for a node. - pub async fn update_node_auth(&self, url: Url, auth: Option) -> crate::wallet::Result<()> { + pub async fn update_node_auth(&self, url: Url, auth: Option) -> Result<(), WalletError> { log::debug!("[update_node_auth]"); let mut node_manager_builder = NodeManagerBuilder::from(&*self.client.node_manager.read().await); @@ -139,7 +146,7 @@ where .update_node_manager(node_manager_builder.build(HashSet::new())) .await?; - self.update_bech32_hrp().await?; + self.update_address_hrp().await?; Ok(()) } diff --git a/sdk/src/wallet/core/operations/ledger_nano.rs b/sdk/src/wallet/core/operations/ledger_nano.rs index 8d1beeb028..0b148d1811 100644 --- a/sdk/src/wallet/core/operations/ledger_nano.rs +++ b/sdk/src/wallet/core/operations/ledger_nano.rs @@ -2,24 +2,27 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::secret::{ledger_nano::LedgerSecretManager, LedgerNanoStatus, SecretManager}, - wallet::Wallet, + client::{ + secret::{ledger_nano::LedgerSecretManager, LedgerNanoStatus, SecretManager}, + ClientError, + }, + wallet::{Wallet, WalletError}, }; impl Wallet { /// Get the ledger nano status - pub async fn get_ledger_nano_status(&self) -> crate::wallet::Result { + pub async fn get_ledger_nano_status(&self) -> Result { Ok(self.secret_manager.read().await.get_ledger_nano_status().await) } } impl Wallet { /// Get the ledger nano status - pub async fn get_ledger_nano_status(&self) -> crate::wallet::Result { + pub async fn get_ledger_nano_status(&self) -> Result { if let SecretManager::LedgerNano(ledger) = &*self.secret_manager.read().await { Ok(ledger.get_ledger_nano_status().await) } else { - Err(crate::client::Error::SecretManagerMismatch.into()) + Err(ClientError::SecretManagerMismatch.into()) } } } diff --git a/sdk/src/wallet/core/operations/mod.rs b/sdk/src/wallet/core/operations/mod.rs index e01ca173a6..058c1e84dc 100644 --- a/sdk/src/wallet/core/operations/mod.rs +++ b/sdk/src/wallet/core/operations/mod.rs @@ -1,7 +1,6 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -pub(crate) mod address_generation; pub(crate) mod background_syncing; pub(crate) mod client; #[cfg(feature = "ledger_nano")] diff --git a/sdk/src/wallet/core/operations/storage.rs b/sdk/src/wallet/core/operations/storage.rs index 7ccbc935de..57b2b48406 100644 --- a/sdk/src/wallet/core/operations/storage.rs +++ b/sdk/src/wallet/core/operations/storage.rs @@ -13,17 +13,15 @@ mod storage_stub { wallet::{ core::builder::dto::WalletBuilderDto, storage::constants::{SECRET_MANAGER_KEY, WALLET_BUILDER_KEY}, - WalletBuilder, + WalletBuilder, WalletError, }, }; #[async_trait] pub trait SaveLoadWallet { - async fn save(&self, storage: &impl StorageAdapter) -> crate::wallet::Result<()>; + async fn save(&self, storage: &impl StorageAdapter) -> Result<(), WalletError>; - async fn load( - storage: &impl StorageAdapter, - ) -> crate::wallet::Result> + async fn load(storage: &impl StorageAdapter) -> Result, WalletError> where Self: Sized; } @@ -31,9 +29,9 @@ mod storage_stub { #[async_trait] impl SaveLoadWallet for WalletBuilder where - crate::wallet::Error: From, + WalletError: From, { - async fn save(&self, storage: &impl StorageAdapter) -> crate::wallet::Result<()> { + async fn save(&self, storage: &impl StorageAdapter) -> Result<(), WalletError> { log::debug!("[save] wallet builder"); storage.set(WALLET_BUILDER_KEY, self).await?; @@ -47,9 +45,7 @@ mod storage_stub { Ok(()) } - async fn load( - storage: &impl StorageAdapter, - ) -> crate::wallet::Result> { + async fn load(storage: &impl StorageAdapter) -> Result, WalletError> { log::debug!("[load] wallet builder"); if let Some(wallet_builder_dto) = storage.get::(WALLET_BUILDER_KEY).await? { log::debug!("[load] wallet builder dto: {wallet_builder_dto:?}"); @@ -68,15 +64,13 @@ mod storage_stub { #[async_trait] impl SaveLoadWallet for WalletBuilder { - async fn save(&self, storage: &impl StorageAdapter) -> crate::wallet::Result<()> { + async fn save(&self, storage: &impl StorageAdapter) -> Result<(), WalletError> { log::debug!("[save] wallet builder"); storage.set(WALLET_BUILDER_KEY, self).await?; Ok(()) } - async fn load( - storage: &impl StorageAdapter, - ) -> crate::wallet::Result> { + async fn load(storage: &impl StorageAdapter) -> Result, WalletError> { log::debug!("[load] wallet builder"); let res = storage.get::(WALLET_BUILDER_KEY).await?; log::debug!("[load] wallet builder: {res:?}"); diff --git a/sdk/src/wallet/core/operations/stronghold.rs b/sdk/src/wallet/core/operations/stronghold.rs index d3c72db45c..4e662671e5 100644 --- a/sdk/src/wallet/core/operations/stronghold.rs +++ b/sdk/src/wallet/core/operations/stronghold.rs @@ -6,20 +6,20 @@ use std::time::Duration; use crypto::keys::bip39::Mnemonic; use crate::{ - client::{secret::SecretManager, stronghold::StrongholdAdapter, utils::Password}, - wallet::Wallet, + client::{secret::SecretManager, stronghold::StrongholdAdapter, utils::Password, ClientError}, + wallet::{Wallet, WalletError}, }; impl Wallet { /// Sets the Stronghold password - pub async fn set_stronghold_password(&self, password: impl Into + Send) -> crate::wallet::Result<()> { + pub async fn set_stronghold_password(&self, password: impl Into + Send) -> Result<(), WalletError> { let password = password.into(); if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.set_password(password).await?; Ok(()) } else { - Err(crate::client::Error::SecretManagerMismatch.into()) + Err(ClientError::SecretManagerMismatch.into()) } } @@ -28,7 +28,7 @@ impl Wallet { &self, current_password: impl Into + Send, new_password: impl Into + Send, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { let current_password = current_password.into(); let new_password = new_password.into(); @@ -37,55 +37,55 @@ impl Wallet { stronghold.change_password(new_password).await?; Ok(()) } else { - Err(crate::client::Error::SecretManagerMismatch.into()) + Err(ClientError::SecretManagerMismatch.into()) } } /// Sets the Stronghold password clear interval - pub async fn set_stronghold_password_clear_interval(&self, timeout: Option) -> crate::wallet::Result<()> { + pub async fn set_stronghold_password_clear_interval(&self, timeout: Option) -> Result<(), WalletError> { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.set_timeout(timeout).await; Ok(()) } else { - Err(crate::client::Error::SecretManagerMismatch.into()) + Err(ClientError::SecretManagerMismatch.into()) } } /// Stores a mnemonic into the Stronghold vault - pub async fn store_mnemonic(&self, mnemonic: Mnemonic) -> crate::wallet::Result<()> { + pub async fn store_mnemonic(&self, mnemonic: Mnemonic) -> Result<(), WalletError> { if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.store_mnemonic(mnemonic).await?; Ok(()) } else { - Err(crate::client::Error::SecretManagerMismatch.into()) + Err(ClientError::SecretManagerMismatch.into()) } } /// Clears the Stronghold password from memory. - pub async fn clear_stronghold_password(&self) -> crate::wallet::Result<()> { + pub async fn clear_stronghold_password(&self) -> Result<(), WalletError> { log::debug!("[clear_stronghold_password]"); if let SecretManager::Stronghold(stronghold) = &mut *self.secret_manager.write().await { stronghold.clear_key().await; Ok(()) } else { - Err(crate::client::Error::SecretManagerMismatch.into()) + Err(ClientError::SecretManagerMismatch.into()) } } /// Checks if the Stronghold password is available. - pub async fn is_stronghold_password_available(&self) -> crate::wallet::Result { + pub async fn is_stronghold_password_available(&self) -> Result { log::debug!("[is_stronghold_password_available]"); if let SecretManager::Stronghold(stronghold) = &*self.secret_manager.write().await { Ok(stronghold.is_key_available().await) } else { - Err(crate::client::Error::SecretManagerMismatch.into()) + Err(ClientError::SecretManagerMismatch.into()) } } } impl Wallet { /// Sets the Stronghold password - pub async fn set_stronghold_password(&self, password: impl Into + Send) -> crate::wallet::Result<()> { + pub async fn set_stronghold_password(&self, password: impl Into + Send) -> Result<(), WalletError> { Ok(self.secret_manager.write().await.set_password(password).await?) } @@ -94,7 +94,7 @@ impl Wallet { &self, current_password: impl Into + Send, new_password: impl Into + Send, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { let stronghold = &mut *self.secret_manager.write().await; stronghold.set_password(current_password).await?; stronghold.change_password(new_password).await?; @@ -102,25 +102,25 @@ impl Wallet { } /// Sets the Stronghold password clear interval - pub async fn set_stronghold_password_clear_interval(&self, timeout: Option) -> crate::wallet::Result<()> { + pub async fn set_stronghold_password_clear_interval(&self, timeout: Option) -> Result<(), WalletError> { self.secret_manager.write().await.set_timeout(timeout).await; Ok(()) } /// Stores a mnemonic into the Stronghold vault - pub async fn store_mnemonic(&self, mnemonic: Mnemonic) -> crate::wallet::Result<()> { + pub async fn store_mnemonic(&self, mnemonic: Mnemonic) -> Result<(), WalletError> { Ok(self.secret_manager.write().await.store_mnemonic(mnemonic).await?) } /// Clears the Stronghold password from memory. - pub async fn clear_stronghold_password(&self) -> crate::wallet::Result<()> { + pub async fn clear_stronghold_password(&self) -> Result<(), WalletError> { log::debug!("[clear_stronghold_password]"); self.secret_manager.write().await.clear_key().await; Ok(()) } /// Checks if the Stronghold password is available. - pub async fn is_stronghold_password_available(&self) -> crate::wallet::Result { + pub async fn is_stronghold_password_available(&self) -> Result { log::debug!("[is_stronghold_password_available]"); Ok(self.secret_manager.write().await.is_key_available().await) } diff --git a/sdk/src/wallet/core/operations/stronghold_backup/mod.rs b/sdk/src/wallet/core/operations/stronghold_backup/mod.rs index 3435397b5e..abfedfc1ad 100644 --- a/sdk/src/wallet/core/operations/stronghold_backup/mod.rs +++ b/sdk/src/wallet/core/operations/stronghold_backup/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod stronghold_snapshot; use std::{fs, path::PathBuf}; -use self::stronghold_snapshot::read_wallet_data_from_stronghold_snapshot; +use self::stronghold_snapshot::read_fields_from_stronghold_snapshot; #[cfg(feature = "storage")] use crate::wallet::WalletBuilder; use crate::{ @@ -14,17 +14,18 @@ use crate::{ utils::Password, }, types::block::address::Hrp, - wallet::Wallet, + wallet::{core::WalletLedgerDto, Wallet, WalletError}, }; impl Wallet { - /// Backup the wallet data in a Stronghold file. + /// Backup the wallet in a Stronghold snapshot file. + /// /// `stronghold_password` must be the current one when Stronghold is used as SecretManager. - pub async fn backup( + pub async fn backup_to_stronghold_snapshot( &self, backup_path: PathBuf, stronghold_password: impl Into + Send, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { let stronghold_password = stronghold_password.into(); log::debug!("[backup] creating a stronghold backup"); @@ -34,7 +35,7 @@ impl Wallet { // Backup with existing stronghold SecretManager::Stronghold(stronghold) => { stronghold.set_password(stronghold_password).await?; - self.store_data_to_stronghold(stronghold).await?; + self.write_fields_to_stronghold_snapshot(stronghold).await?; // Write snapshot to backup path stronghold.write_stronghold_snapshot(Some(&backup_path)).await?; } @@ -45,7 +46,7 @@ impl Wallet { .password(stronghold_password) .build(backup_path)?; - self.store_data_to_stronghold(&backup_stronghold).await?; + self.write_fields_to_stronghold_snapshot(&backup_stronghold).await?; // Write snapshot to backup path backup_stronghold.write_stronghold_snapshot(None).await?; @@ -55,7 +56,8 @@ impl Wallet { Ok(()) } - /// Restore a backup from a Stronghold file + /// Restore a backup from a Stronghold snapshot file. + /// /// Replaces client_options, bip_path, secret_manager and wallet. Returns an error if the wallet was already /// created If Stronghold is used as secret_manager, the existing Stronghold file will be overwritten. If a /// mnemonic was stored, it will be gone. @@ -64,49 +66,47 @@ impl Wallet { /// coin type doesn't match /// If a bech32 hrp is provided to ignore_if_bech32_hrp_mismatch, that doesn't match the one of the current address, /// the wallet will not be restored. - pub async fn restore_backup( + pub async fn restore_from_stronghold_snapshot( &self, backup_path: PathBuf, stronghold_password: impl Into + Send, ignore_if_bip_path_mismatch: Option, ignore_if_bech32_hrp_mismatch: Option, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { let stronghold_password = stronghold_password.into(); log::debug!("[restore_backup] loading stronghold backup"); if !backup_path.is_file() { - return Err(crate::wallet::Error::Backup("backup path doesn't exist")); + return Err(WalletError::Backup("backup path doesn't exist")); } - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; // We don't want to overwrite a possible existing wallet - if !wallet_data.outputs.is_empty() { - return Err(crate::wallet::Error::Backup( + if !wallet_ledger.outputs.is_empty() { + return Err(WalletError::Backup( "can't restore backup when there is already a wallet", )); } - let curr_bip_path = wallet_data.bip_path; + let curr_bip_path = self.bip_path().await; // Explicitly drop the data to avoid contention - drop(wallet_data); + drop(wallet_ledger); // We'll create a new stronghold to load the backup let new_stronghold = StrongholdSecretManager::builder() .password(stronghold_password.clone()) .build(backup_path.clone())?; - let (read_client_options, read_secret_manager, read_wallet_data) = - read_wallet_data_from_stronghold_snapshot::(&new_stronghold).await?; - - let read_bip_path = read_wallet_data.as_ref().and_then(|data| data.bip_path); + let (read_address, read_bip_path, read_alias, read_client_options, read_secret_manager, read_wallet_ledger) = + read_fields_from_stronghold_snapshot::(&new_stronghold).await?; // If the bip path is not matching the current one, we may ignore the backup let ignore_backup_values = ignore_if_bip_path_mismatch.map_or(false, |ignore| { if ignore { - // TODO: #1279 okay that if both are none we always load the backup values? + // TODO: is it okay that if both are none we always load the backup values? curr_bip_path != read_bip_path } else { false @@ -114,7 +114,7 @@ impl Wallet { }); if !ignore_backup_values { - self.data_mut().await.bip_path = read_bip_path; + *self.bip_path_mut().await = read_bip_path; } // Get the current snapshot path if set @@ -131,7 +131,7 @@ impl Wallet { } let restored_secret_manager = SecretManager::from_config(&read_secret_manager) - .map_err(|_| crate::wallet::Error::Backup("invalid secret_manager"))?; + .map_err(|_| WalletError::Backup("invalid secret_manager"))?; // Copy Stronghold file so the seed is available in the new location fs::copy(backup_path, new_snapshot_path)?; @@ -154,14 +154,17 @@ impl Wallet { } if !ignore_backup_values { - if let Some(read_wallet_data) = read_wallet_data { + if let Some(read_wallet_ledger) = read_wallet_ledger { let restore_wallet = ignore_if_bech32_hrp_mismatch.map_or(true, |expected_bech32_hrp| { // Only restore if bech32 hrps match - read_wallet_data.address.hrp() == &expected_bech32_hrp + read_address.hrp() == &expected_bech32_hrp }); if restore_wallet { - *self.data_mut().await = read_wallet_data; + *self.address_mut().await = read_address; + *self.bip_path_mut().await = read_bip_path; + *self.alias_mut().await = read_alias; + *self.ledger_mut().await = read_wallet_ledger; } } } @@ -182,12 +185,16 @@ impl Wallet { .expect("can't convert os string"), ) .with_client_options(self.client_options().await) - .with_bip_path(self.data().await.bip_path); + .with_address(self.address().await) + .with_bip_path(self.bip_path().await) + .with_alias(self.alias().await); wallet_builder.save(self.storage_manager()).await?; - // also save wallet data to db - self.storage_manager().save_wallet_data(&*self.data().await).await?; + // also save wallet ledger to db + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*self.ledger().await)) + .await?; } Ok(()) @@ -195,19 +202,20 @@ impl Wallet { } impl Wallet { - /// Backup the wallet data in a Stronghold file - /// stronghold_password must be the current one when Stronghold is used as SecretManager. - pub async fn backup( + /// Backup the wallet in a Stronghold snapshot file. + /// + /// `stronghold_password` must be the current one when Stronghold is used as SecretManager. + pub async fn backup_to_stronghold_snapshot( &self, backup_path: PathBuf, stronghold_password: impl Into + Send, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { log::debug!("[backup] creating a stronghold backup"); let secret_manager = self.secret_manager.read().await; secret_manager.set_password(stronghold_password).await?; - self.store_data_to_stronghold(&secret_manager).await?; + self.write_fields_to_stronghold_snapshot(&secret_manager).await?; // Write snapshot to backup path secret_manager.write_stronghold_snapshot(Some(&backup_path)).await?; @@ -215,7 +223,8 @@ impl Wallet { Ok(()) } - /// Restore a backup from a Stronghold file + /// Restore a backup from a Stronghold file. + /// /// Replaces client_options, bip path, secret_manager and wallet. Returns an error if the wallet was already /// created If Stronghold is used as secret_manager, the existing Stronghold file will be overwritten. If a /// mnemonic was stored, it will be gone. @@ -224,49 +233,47 @@ impl Wallet { /// bip path doesn't match /// If a bech32 hrp is provided to ignore_if_bech32_hrp_mismatch, that doesn't match the one of the current address, /// the wallet will not be restored. - pub async fn restore_backup( + pub async fn restore_from_stronghold_snapshot( &self, backup_path: PathBuf, stronghold_password: impl Into + Send, ignore_if_bip_path_mismatch: Option, ignore_if_bech32_hrp_mismatch: Option, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { let stronghold_password = stronghold_password.into(); log::debug!("[restore_backup] loading stronghold backup"); if !backup_path.is_file() { - return Err(crate::wallet::Error::Backup("backup path doesn't exist")); + return Err(WalletError::Backup("backup path doesn't exist")); } - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; // We don't want to overwrite a possible existing wallet - if !wallet_data.outputs.is_empty() { - return Err(crate::wallet::Error::Backup( + if !wallet_ledger.outputs.is_empty() { + return Err(WalletError::Backup( "can't restore backup when there is already a wallet", )); } - let curr_bip_path = wallet_data.bip_path; + let curr_bip_path = self.bip_path().await; // Explicitly drop the data to avoid contention - drop(wallet_data); + drop(wallet_ledger); // We'll create a new stronghold to load the backup let new_stronghold = StrongholdSecretManager::builder() .password(stronghold_password.clone()) .build(backup_path.clone())?; - let (read_client_options, read_secret_manager, read_wallet_data) = - read_wallet_data_from_stronghold_snapshot::(&new_stronghold).await?; - - let read_bip_path = read_wallet_data.as_ref().and_then(|data| data.bip_path); + let (read_address, read_bip_path, read_alias, read_client_options, read_secret_manager, read_wallet_ledger) = + read_fields_from_stronghold_snapshot::(&new_stronghold).await?; // If the bip path is not matching the current one, we may ignore the backup let ignore_backup_values = ignore_if_bip_path_mismatch.map_or(false, |ignore| { if ignore { - // TODO: #1279 okay that if both are none we always load the backup values? + // TODO: is it okay that if both are none we always load the backup values? curr_bip_path != read_bip_path } else { false @@ -274,7 +281,7 @@ impl Wallet { }); if !ignore_backup_values { - self.data_mut().await.bip_path = read_bip_path; + *self.bip_path_mut().await = read_bip_path; } if let Some(mut read_secret_manager) = read_secret_manager { @@ -284,7 +291,7 @@ impl Wallet { read_secret_manager.snapshot_path = new_snapshot_path.to_string_lossy().into_owned(); let restored_secret_manager = StrongholdSecretManager::from_config(&read_secret_manager) - .map_err(|_| crate::wallet::Error::Backup("invalid secret_manager"))?; + .map_err(|_| WalletError::Backup("invalid secret_manager"))?; // Copy Stronghold file so the seed is available in the new location fs::copy(backup_path, new_snapshot_path)?; @@ -303,14 +310,17 @@ impl Wallet { } if !ignore_backup_values { - if let Some(read_wallet_data) = read_wallet_data { + if let Some(read_wallet_ledger) = read_wallet_ledger { let restore_wallet = ignore_if_bech32_hrp_mismatch.map_or(true, |expected_bech32_hrp| { // Only restore if bech32 hrps match - read_wallet_data.address.hrp() == &expected_bech32_hrp + read_address.hrp() == &expected_bech32_hrp }); if restore_wallet { - *self.data_mut().await = read_wallet_data; + *self.address_mut().await = read_address; + *self.bip_path_mut().await = read_bip_path; + *self.alias_mut().await = read_alias; + *self.ledger_mut().await = read_wallet_ledger; } } } @@ -331,12 +341,16 @@ impl Wallet { .expect("can't convert os string"), ) .with_client_options(self.client_options().await) - .with_bip_path(self.data().await.bip_path); + .with_address(self.address().await) + .with_bip_path(self.bip_path().await) + .with_alias(self.alias().await); wallet_builder.save(self.storage_manager()).await?; - // also save wallet data to db - self.storage_manager().save_wallet_data(&*self.data().await).await?; + // also save wallet ledger to db + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*self.ledger().await)) + .await?; } Ok(()) diff --git a/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs b/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs index 83632aaca1..0be48f2efe 100644 --- a/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs +++ b/sdk/src/wallet/core/operations/stronghold_backup/stronghold_snapshot.rs @@ -1,73 +1,109 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use crypto::keys::bip44::Bip44; + use crate::{ client::{secret::SecretManagerConfig, storage::StorageAdapter, stronghold::StrongholdAdapter}, - types::TryFromDto, + types::{block::address::Bech32Address, TryFromDto}, wallet::{ - core::{WalletData, WalletDataDto}, + self, + core::{WalletLedger, WalletLedgerDto}, migration::{latest_backup_migration_version, migrate, MIGRATION_VERSION_KEY}, - ClientOptions, Wallet, + ClientOptions, Wallet, WalletError, }, }; pub(crate) const CLIENT_OPTIONS_KEY: &str = "client_options"; pub(crate) const SECRET_MANAGER_KEY: &str = "secret_manager"; -pub(crate) const WALLET_DATA_KEY: &str = "wallet_data"; +pub(crate) const WALLET_LEDGER_KEY: &str = "wallet_ledger"; +pub(crate) const WALLET_ADDRESS_KEY: &str = "wallet_address"; +pub(crate) const WALLET_BIP_PATH_KEY: &str = "wallet_bip_path"; +pub(crate) const WALLET_ALIAS_KEY: &str = "wallet_alias"; impl Wallet { - pub(crate) async fn store_data_to_stronghold(&self, stronghold: &StrongholdAdapter) -> crate::wallet::Result<()> { + pub(crate) async fn write_fields_to_stronghold_snapshot( + &self, + stronghold: &StrongholdAdapter, + ) -> Result<(), WalletError> { // Set migration version stronghold .set(MIGRATION_VERSION_KEY, &latest_backup_migration_version()) .await?; + // Store the client options let client_options = self.client_options().await; stronghold.set(CLIENT_OPTIONS_KEY, &client_options).await?; + // Store the secret manager if let Some(secret_manager_dto) = self.secret_manager.read().await.to_config() { stronghold.set(SECRET_MANAGER_KEY, &secret_manager_dto).await?; } - let serialized_wallet_data = serde_json::to_value(&WalletDataDto::from(&*self.data.read().await))?; - stronghold.set(WALLET_DATA_KEY, &serialized_wallet_data).await?; + // Store the wallet address + stronghold + .set(WALLET_ADDRESS_KEY, self.address().await.as_ref()) + .await?; + + // Store the wallet bip path + stronghold.set(WALLET_BIP_PATH_KEY, &self.bip_path().await).await?; + + // Store the wallet alias + stronghold.set(WALLET_ALIAS_KEY, &self.alias().await).await?; + + let serialized_wallet_ledger = serde_json::to_value(&WalletLedgerDto::from(&*self.ledger.read().await))?; + stronghold.set(WALLET_LEDGER_KEY, &serialized_wallet_ledger).await?; Ok(()) } } -pub(crate) async fn read_wallet_data_from_stronghold_snapshot( +pub(crate) async fn read_fields_from_stronghold_snapshot( stronghold: &StrongholdAdapter, -) -> crate::wallet::Result<(Option, Option, Option)> { +) -> Result< + ( + Bech32Address, + Option, + Option, + Option, + Option, + Option, + ), + WalletError, +> { migrate(stronghold).await?; // Get client_options let client_options = stronghold.get(CLIENT_OPTIONS_KEY).await?; - // TODO #1279: remove - // // Get coin_type - // let coin_type_bytes = stronghold.get_bytes(COIN_TYPE_KEY).await?; - // let coin_type = if let Some(coin_type_bytes) = coin_type_bytes { - // let coin_type = u32::from_le_bytes( - // coin_type_bytes - // .try_into() - // .map_err(|_| WalletError::Backup("invalid coin_type"))?, - // ); - // log::debug!("[restore_backup] restored coin_type: {coin_type}"); - // Some(coin_type) - // } else { - // None - // }; - // Get secret_manager - let restored_secret_manager = stronghold.get(SECRET_MANAGER_KEY).await?; + let secret_manager = stronghold.get(SECRET_MANAGER_KEY).await?; + + // Get the wallet address + let wallet_address = stronghold + .get(WALLET_ADDRESS_KEY) + .await? + .ok_or(wallet::WalletError::Backup("missing non-optional wallet address"))?; + + // Get the wallet bip path + let wallet_bip_path = stronghold.get(WALLET_BIP_PATH_KEY).await?; + + // Get the wallet alias + let wallet_alias = stronghold.get(WALLET_ALIAS_KEY).await?; - // Get wallet data - let restored_wallet_data = stronghold - .get::(WALLET_DATA_KEY) + // Get wallet ledger + let wallet_ledger = stronghold + .get::(WALLET_LEDGER_KEY) .await? - .map(WalletData::try_from_dto) + .map(WalletLedger::try_from_dto) .transpose()?; - Ok((client_options, restored_secret_manager, restored_wallet_data)) + Ok(( + wallet_address, + wallet_bip_path, + wallet_alias, + client_options, + secret_manager, + wallet_ledger, + )) } diff --git a/sdk/src/wallet/error.rs b/sdk/src/wallet/error.rs index 2cd452ab7e..50d6f5488f 100644 --- a/sdk/src/wallet/error.rs +++ b/sdk/src/wallet/error.rs @@ -6,25 +6,42 @@ use std::fmt::Debug; use crypto::keys::bip44::Bip44; use serde::{ser::Serializer, Serialize}; -use crate::types::block::{address::Bech32Address, output::DelegationId, payload::signed_transaction::TransactionId}; +use crate::{ + client::ClientError, + types::block::{ + address::Bech32Address, + context_input::ContextInputError, + input::InputError, + mana::ManaError, + output::{ + feature::FeatureError, unlock_condition::UnlockConditionError, DelegationId, NativeTokenError, OutputError, + TokenSchemeError, + }, + payload::{signed_transaction::TransactionId, PayloadError}, + signature::SignatureError, + unlock::UnlockError, + BlockError, + }, + utils::ConversionError, +}; /// The wallet error type. #[derive(Debug, thiserror::Error, strum::AsRefStr)] #[strum(serialize_all = "camelCase")] #[non_exhaustive] -pub enum Error { +pub enum WalletError { /// Errors during backup creation or restoring #[error("backup failed {0}")] Backup(&'static str), /// Error from block crate. #[error("{0}")] - Block(Box), + Block(#[from] BlockError), /// Burning or melting failed #[error("burning or melting failed: {0}")] BurningOrMeltingFailed(String), /// Client error. #[error("`{0}`")] - Client(Box), + Client(#[from] ClientError), /// BIP44 coin type mismatch #[error("BIP44 mismatch: {new_bip_path:?}, existing bip path is: {old_bip_path:?}")] BipPathMismatch { @@ -56,6 +73,9 @@ pub enum Error { /// Invalid output kind. #[error("invalid output kind: {0}")] InvalidOutputKind(String), + /// Invalid parameter. + #[error("invalid parameter: {0}")] + InvalidParameter(&'static str), /// Invalid Voting Power #[cfg(feature = "participation")] #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] @@ -132,16 +152,18 @@ pub enum Error { AccountNotFound, #[error("staking failed: {0}")] StakingFailed(String), + #[error("{0}")] + Convert(#[from] ConversionError), } -impl Error { +impl WalletError { pub fn other(err: E) -> Self { Self::Other(Box::new(err) as _) } } // Serialize type with Display error -impl Serialize for Error { +impl Serialize for WalletError { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -161,49 +183,52 @@ impl Serialize for Error { } } -impl From for Error { - fn from(error: crate::types::block::Error) -> Self { - Self::Block(Box::new(error)) - } -} - -impl From for Error { - fn from(error: crate::client::Error) -> Self { - Self::Client(Box::new(error)) - } -} - -impl From for Error { - fn from(error: crate::client::api::input_selection::Error) -> Self { +impl From for WalletError { + fn from(error: crate::client::api::transaction_builder::TransactionBuilderError) -> Self { // Map "same" error so it's easier to handle match error { - crate::client::api::input_selection::Error::InsufficientAmount { found, required } => { - Self::InsufficientFunds { - available: found, - required, - } - } - _ => Self::Client(Box::new(crate::client::Error::InputSelection(error))), + crate::client::api::transaction_builder::TransactionBuilderError::InsufficientAmount { + found, + required, + } => Self::InsufficientFunds { + available: found, + required, + }, + _ => Self::Client(ClientError::TransactionBuilder(error)), } } } +crate::impl_from_error_via!(WalletError via BlockError: + PayloadError, + OutputError, + InputError, + NativeTokenError, + ManaError, + UnlockConditionError, + FeatureError, + TokenSchemeError, + ContextInputError, + UnlockError, + SignatureError, +); + #[cfg(feature = "stronghold")] -impl From for Error { +impl From for WalletError { fn from(error: crate::client::stronghold::Error) -> Self { - Self::Client(Box::new(crate::client::Error::Stronghold(error))) + Self::Client(ClientError::Stronghold(error)) } } #[cfg(feature = "ledger_nano")] -impl From for Error { +impl From for WalletError { fn from(error: crate::client::secret::ledger_nano::Error) -> Self { - Self::Client(Box::new(crate::client::Error::Ledger(error))) + Self::Client(ClientError::from(error)) } } #[cfg(feature = "rocksdb")] -impl From for Error { +impl From for WalletError { fn from(error: rocksdb::Error) -> Self { Self::Storage(error.to_string()) } diff --git a/sdk/src/wallet/events/mod.rs b/sdk/src/wallet/events/mod.rs index c75cc39acc..1335a6dd9d 100644 --- a/sdk/src/wallet/events/mod.rs +++ b/sdk/src/wallet/events/mod.rs @@ -148,7 +148,7 @@ mod tests { // emit events emitter.emit(WalletEvent::TransactionProgress( - TransactionProgressEvent::SelectingInputs, + TransactionProgressEvent::BuildingTransaction, )); emitter.emit(WalletEvent::TransactionInclusion(TransactionInclusionEvent { transaction_id: TransactionId::from_str( @@ -164,7 +164,7 @@ mod tests { emitter.clear([WalletEventType::TransactionProgress]); // emit event of removed type emitter.emit(WalletEvent::TransactionProgress( - TransactionProgressEvent::SelectingInputs, + TransactionProgressEvent::BuildingTransaction, )); assert_eq!(2, event_counter.load(Ordering::SeqCst)); @@ -173,7 +173,7 @@ mod tests { emitter.clear([]); // emit events emitter.emit(WalletEvent::TransactionProgress( - TransactionProgressEvent::SelectingInputs, + TransactionProgressEvent::BuildingTransaction, )); emitter.emit(WalletEvent::TransactionInclusion(TransactionInclusionEvent { transaction_id: TransactionId::from_str( @@ -193,7 +193,7 @@ mod tests { for _ in 0..1_000_000 { emitter.emit(WalletEvent::TransactionProgress( - TransactionProgressEvent::SelectingInputs, + TransactionProgressEvent::BuildingTransaction, )); } assert_eq!(1_000_002, event_counter.load(Ordering::SeqCst)); diff --git a/sdk/src/wallet/events/types.rs b/sdk/src/wallet/events/types.rs index 475fd5ab2e..279da997f7 100644 --- a/sdk/src/wallet/events/types.rs +++ b/sdk/src/wallet/events/types.rs @@ -6,16 +6,14 @@ use serde::{Deserialize, Serialize, Serializer}; use crate::{ client::api::PreparedTransactionDataDto, - types::{ - api::core::OutputWithMetadataResponse, - block::{ - address::Bech32Address, - payload::signed_transaction::{dto::SignedTransactionPayloadDto, TransactionId}, - }, + types::block::{ + address::Bech32Address, + output::OutputWithMetadata, + payload::signed_transaction::{dto::SignedTransactionPayloadDto, TransactionId}, }, wallet::{ types::{InclusionState, OutputData}, - Error, + WalletError, }, }; @@ -149,7 +147,7 @@ pub enum WalletEventType { } impl TryFrom for WalletEventType { - type Error = Error; + type Error = WalletError; fn try_from(value: u8) -> Result { let event_type = match value { @@ -159,7 +157,7 @@ impl TryFrom for WalletEventType { 2 => Self::SpentOutput, 3 => Self::TransactionInclusion, 4 => Self::TransactionProgress, - _ => return Err(Error::InvalidEventType(value)), + _ => return Err(WalletError::InvalidEventType(value)), }; Ok(event_type) } @@ -175,7 +173,7 @@ pub struct NewOutputEvent { pub transaction: Option, /// The inputs for the transaction that created the output. Might be pruned and not available. #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_inputs: Option>, + pub transaction_inputs: Option>, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -194,16 +192,18 @@ pub struct TransactionInclusionEvent { #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum TransactionProgressEvent { - /// Performing input selection. - SelectingInputs, + /// Building a transaction. + BuildingTransaction, /// Generating remainder value deposit address. GeneratingRemainderDepositAddress(AddressData), /// Prepared transaction. PreparedTransaction(Box), - /// Prepared transaction signing hash hex encoded, required for blindsigning with a ledger nano - PreparedTransactionSigningHash(String), /// Signing the transaction. SigningTransaction, + /// Prepared transaction signing hash hex encoded, required for blindsigning with a ledger nano + PreparedTransactionSigningHash(String), + /// Prepared block signing input, required for blind signing with ledger nano + PreparedBlockSigningInput(String), /// Broadcasting. Broadcasting, } @@ -219,15 +219,22 @@ impl Serialize for TransactionProgressEvent { signing_hash: &'a str, } + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + struct PreparedBlockSigningInput_<'a> { + block_signing_input: &'a str, + } + #[derive(Serialize)] #[serde(untagged)] enum TransactionProgressEvent_<'a> { T0, T1(&'a AddressData), T2(&'a PreparedTransactionDataDto), - T3(PreparedTransactionSigningHash_<'a>), - T4, - T5, + T3, + T4(PreparedTransactionSigningHash_<'a>), + T5(PreparedBlockSigningInput_<'a>), + T6, } #[derive(Serialize)] struct TypedTransactionProgressEvent_<'a> { @@ -237,7 +244,7 @@ impl Serialize for TransactionProgressEvent { event: TransactionProgressEvent_<'a>, } let event = match self { - Self::SelectingInputs => TypedTransactionProgressEvent_ { + Self::BuildingTransaction => TypedTransactionProgressEvent_ { kind: 0, event: TransactionProgressEvent_::T0, }, @@ -249,17 +256,21 @@ impl Serialize for TransactionProgressEvent { kind: 2, event: TransactionProgressEvent_::T2(e), }, - Self::PreparedTransactionSigningHash(e) => TypedTransactionProgressEvent_ { + Self::SigningTransaction => TypedTransactionProgressEvent_ { kind: 3, - event: TransactionProgressEvent_::T3(PreparedTransactionSigningHash_ { signing_hash: e }), + event: TransactionProgressEvent_::T3, }, - Self::SigningTransaction => TypedTransactionProgressEvent_ { + Self::PreparedTransactionSigningHash(e) => TypedTransactionProgressEvent_ { kind: 4, - event: TransactionProgressEvent_::T4, + event: TransactionProgressEvent_::T4(PreparedTransactionSigningHash_ { signing_hash: e }), }, - Self::Broadcasting => TypedTransactionProgressEvent_ { + Self::PreparedBlockSigningInput(e) => TypedTransactionProgressEvent_ { kind: 5, - event: TransactionProgressEvent_::T5, + event: TransactionProgressEvent_::T5(PreparedBlockSigningInput_ { block_signing_input: e }), + }, + Self::Broadcasting => TypedTransactionProgressEvent_ { + kind: 6, + event: TransactionProgressEvent_::T6, }, }; event.serialize(serializer) @@ -274,6 +285,12 @@ impl<'de> Deserialize<'de> for TransactionProgressEvent { signing_hash: String, } + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct PreparedBlockSigningInput_ { + block_signing_input: String, + } + let value = serde_json::Value::deserialize(d)?; Ok( match value @@ -282,22 +299,29 @@ impl<'de> Deserialize<'de> for TransactionProgressEvent { .ok_or_else(|| serde::de::Error::custom("invalid transaction progress event type"))? as u8 { - 0 => Self::SelectingInputs, + 0 => Self::BuildingTransaction, 1 => Self::GeneratingRemainderDepositAddress(AddressData::deserialize(value).map_err(|e| { serde::de::Error::custom(format!("cannot deserialize GeneratingRemainderDepositAddress: {e}")) })?), 2 => Self::PreparedTransaction(Box::new(PreparedTransactionDataDto::deserialize(value).map_err( |e| serde::de::Error::custom(format!("cannot deserialize PreparedTransactionDataDto: {e}")), )?)), - 3 => Self::PreparedTransactionSigningHash( + 3 => Self::SigningTransaction, + 4 => Self::PreparedTransactionSigningHash( PreparedTransactionSigningHash_::deserialize(value) .map_err(|e| { serde::de::Error::custom(format!("cannot deserialize PreparedTransactionSigningHash: {e}")) })? .signing_hash, ), - 4 => Self::SigningTransaction, - 5 => Self::Broadcasting, + 5 => Self::PreparedBlockSigningInput( + PreparedBlockSigningInput_::deserialize(value) + .map_err(|e| { + serde::de::Error::custom(format!("cannot deserialize PreparedBlockSigningInput: {e}")) + })? + .block_signing_input, + ), + 6 => Self::Broadcasting, _ => return Err(serde::de::Error::custom("invalid transaction progress event type")), }, ) diff --git a/sdk/src/wallet/migration/migrate_0.rs b/sdk/src/wallet/migration/migrate_0.rs index 44aedd74ca..695cc56ed5 100644 --- a/sdk/src/wallet/migration/migrate_0.rs +++ b/sdk/src/wallet/migration/migrate_0.rs @@ -15,7 +15,7 @@ impl MigrationData for Migrate { #[async_trait] #[cfg(feature = "storage")] impl Migration for Migrate { - async fn migrate(_storage: &crate::wallet::storage::Storage) -> Result<()> { + async fn migrate(_storage: &crate::wallet::storage::Storage) -> Result<(), WalletError> { Ok(()) } } @@ -23,7 +23,7 @@ impl Migration for Migrate { #[async_trait] #[cfg(feature = "stronghold")] impl Migration for Migrate { - async fn migrate(_storage: &crate::client::stronghold::StrongholdAdapter) -> Result<()> { + async fn migrate(_storage: &crate::client::stronghold::StrongholdAdapter) -> Result<(), WalletError> { Ok(()) } } diff --git a/sdk/src/wallet/migration/mod.rs b/sdk/src/wallet/migration/mod.rs index 2875c27219..fb68956fb3 100644 --- a/sdk/src/wallet/migration/mod.rs +++ b/sdk/src/wallet/migration/mod.rs @@ -10,10 +10,7 @@ use async_trait::async_trait; use once_cell::sync::Lazy; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use crate::{ - client::storage::StorageAdapter, - wallet::{Error, Result}, -}; +use crate::{client::storage::StorageAdapter, wallet::WalletError}; pub(crate) const MIGRATION_VERSION_KEY: &str = "migration-version"; @@ -79,27 +76,27 @@ pub(crate) trait MigrationData { #[async_trait] pub(crate) trait Migration: MigrationData { - async fn migrate(storage: &S) -> Result<()>; + async fn migrate(storage: &S) -> Result<(), WalletError>; } #[async_trait] trait DynMigration: Send + Sync { fn version(&self) -> MigrationVersion; - async fn migrate(&self, storage: &S) -> Result<()>; + async fn migrate(&self, storage: &S) -> Result<(), WalletError>; } #[async_trait] impl + Send + Sync> DynMigration for T where - crate::wallet::Error: From, + WalletError: From, S::Error: From, { fn version(&self) -> MigrationVersion { T::version() } - async fn migrate(&self, storage: &S) -> Result<()> { + async fn migrate(&self, storage: &S) -> Result<(), WalletError> { let version = self.version(); log::info!("Migrating to version {}", version); T::migrate(storage).await?; @@ -108,9 +105,9 @@ where } } -pub async fn migrate(storage: &S) -> Result<()> +pub async fn migrate(storage: &S) -> Result<(), WalletError> where - crate::wallet::Error: From, + WalletError: From, S::Error: From, { let last_migration = storage.get::(MIGRATION_VERSION_KEY).await?; @@ -122,11 +119,11 @@ where fn migrations( mut last_migration: Option, -) -> Result>> { +) -> Result>, WalletError> { let migrations = MIGRATIONS .get::, &'static dyn DynMigration>>() .ok_or_else(|| { - Error::Migration(format!( + WalletError::Migration(format!( "invalid migration storage kind: {}", std::any::type_name::() )) @@ -148,12 +145,12 @@ trait Convert { type New: Serialize + DeserializeOwned; type Old: DeserializeOwned; - fn check(value: &mut serde_json::Value) -> crate::wallet::Result<()> { + fn check(value: &mut serde_json::Value) -> Result<(), WalletError> { if Self::New::deserialize(&*value).is_err() { *value = serde_json::to_value(Self::convert(Self::Old::deserialize(&*value)?)?)?; } Ok(()) } - fn convert(old: Self::Old) -> crate::wallet::Result; + fn convert(old: Self::Old) -> Result; } diff --git a/sdk/src/wallet/mod.rs b/sdk/src/wallet/mod.rs index 65dc73ccb4..590c1f24ee 100644 --- a/sdk/src/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -41,7 +41,7 @@ pub use self::operations::participation::{ParticipationEventWithNodes, Participa use self::types::TransactionWithMetadata; pub use self::{ core::{Wallet, WalletBuilder}, - error::Error, + error::WalletError, operations::{ output_claiming::OutputsToClaim, output_consolidation::ConsolidationParams, @@ -62,12 +62,12 @@ pub use self::{ mint_nfts::MintNftParams, }, send::SendParams, + send_mana::SendManaParams, send_native_tokens::SendNativeTokenParams, send_nft::SendNftParams, staking::begin::BeginStakingParams, }, prepare_output::{Assets, Features, OutputParams, ReturnStrategy, StorageDeposit, Unlocks}, - RemainderValueStrategy, TransactionOptions, }, }, types::OutputData, @@ -76,16 +76,13 @@ use crate::{ types::{ api::core::OutputWithMetadataResponse, block::{ - output::{AccountId, AnchorId, DelegationId, FoundryId, NftId}, + output::{AccountId, AnchorId, DelegationId, FoundryId, NftId, OutputWithMetadata}, payload::signed_transaction::{SignedTransactionPayload, TransactionId}, }, }, wallet::types::InclusionState, }; -/// The wallet Result type. -pub type Result = std::result::Result; - /// Options to filter outputs #[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] @@ -112,7 +109,7 @@ pub(crate) fn build_transaction_from_payload_and_inputs( tx_id: TransactionId, tx_payload: SignedTransactionPayload, inputs: Vec, -) -> crate::wallet::Result { +) -> Result { Ok(TransactionWithMetadata { payload: tx_payload.clone(), block_id: inputs.first().map(|i| *i.metadata.block_id()), @@ -127,6 +124,12 @@ pub(crate) fn build_transaction_from_payload_and_inputs( network_id: tx_payload.transaction().network_id(), incoming: true, note: None, - inputs, + inputs: inputs + .into_iter() + .map(|input| OutputWithMetadata { + output: input.output, + metadata: input.metadata, + }) + .collect(), }) } diff --git a/sdk/src/wallet/operations/announce_candidacy.rs b/sdk/src/wallet/operations/announce_candidacy.rs index b00593c537..f202c19683 100644 --- a/sdk/src/wallet/operations/announce_candidacy.rs +++ b/sdk/src/wallet/operations/announce_candidacy.rs @@ -2,22 +2,22 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::secret::SecretManage, + client::{secret::SecretManage, ClientError}, types::block::{ output::AccountId, payload::{CandidacyAnnouncementPayload, Payload}, BlockId, }, - wallet::Wallet, + wallet::{Wallet, WalletError}, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Announce a staking account's candidacy for the staking period. - pub async fn announce_candidacy(&self, account_id: AccountId) -> crate::wallet::Result { + pub async fn announce_candidacy(&self, account_id: AccountId) -> Result { self.submit_basic_block( Payload::CandidacyAnnouncement(CandidacyAnnouncementPayload), account_id, diff --git a/sdk/src/wallet/operations/balance.rs b/sdk/src/wallet/operations/balance.rs index e9efdbfd80..e137da3c04 100644 --- a/sdk/src/wallet/operations/balance.rs +++ b/sdk/src/wallet/operations/balance.rs @@ -1,6 +1,8 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashSet; + use primitive_types::U256; use crate::{ @@ -11,23 +13,20 @@ use crate::{ wallet::{ operations::{helpers::time::can_output_be_unlocked_forever_from_now_on, output_claiming::OutputsToClaim}, types::{Balance, NativeTokensBalance}, - Result, Wallet, + Wallet, WalletError, }, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { /// Get the balance of the wallet. - pub async fn balance(&self) -> Result { + pub async fn balance(&self) -> Result { log::debug!("[BALANCE] balance"); let protocol_parameters = self.client().get_protocol_parameters().await?; let slot_index = self.client().get_slot_index().await?; - let wallet_data = self.data().await.clone(); + let wallet_address = self.address().await; + let wallet_ledger = self.ledger().await; let network_id = protocol_parameters.network_id(); let storage_score_params = protocol_parameters.storage_score_parameters(); @@ -36,20 +35,23 @@ where let mut total_native_tokens = NativeTokensBuilder::default(); #[cfg(feature = "participation")] - let voting_output = wallet_data.get_voting_output()?; + let voting_output = wallet_ledger.get_voting_output(); - let claimable_outputs = wallet_data.claimable_outputs(OutputsToClaim::All, slot_index, &protocol_parameters)?; + let claimable_outputs = + wallet_ledger.claimable_outputs(&wallet_address, OutputsToClaim::All, slot_index, &protocol_parameters)?; #[cfg(feature = "participation")] { if let Some(voting_output) = &voting_output { - if voting_output.output.as_basic().address() == wallet_data.address.inner() { + if voting_output.output.as_basic().address() == wallet_address.inner() { balance.base_coin.voting_power = voting_output.output.amount(); } } } - for (output_id, output_data) in &wallet_data.unspent_outputs { + let mut reward_outputs = HashSet::new(); + + for (output_id, output_data) in &wallet_ledger.unspent_outputs { // Check if output is from the network we're currently connected to if output_data.network_id != network_id { continue; @@ -70,13 +72,13 @@ where slot_index, )?; // Add mana rewards - if let Ok(response) = self.client().get_output_mana_rewards(output_id, slot_index).await { - balance.mana.rewards += response.rewards; + if account.features().staking().is_some() { + reward_outputs.insert(*output_id); } // Add storage deposit balance.required_storage_deposit.account += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } @@ -88,7 +90,7 @@ where balance.base_coin.total += foundry.amount(); // Add storage deposit balance.required_storage_deposit.foundry += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } @@ -103,12 +105,10 @@ where // Add amount balance.base_coin.total += delegation.amount(); // Add mana rewards - if let Ok(response) = self.client().get_output_mana_rewards(output_id, slot_index).await { - balance.mana.rewards += response.rewards; - } + reward_outputs.insert(*output_id); // Add storage deposit balance.required_storage_deposit.delegation += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } @@ -141,12 +141,12 @@ where // Add storage deposit if output.is_basic() { balance.required_storage_deposit.basic += storage_cost; - if output.native_token().is_some() && !wallet_data.locked_outputs.contains(output_id) { + if output.native_token().is_some() && !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } } else if output.is_nft() { balance.required_storage_deposit.nft += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } } @@ -172,7 +172,7 @@ where // We use the addresses with unspent outputs, because other addresses of // the account without unspent // outputs can't be related to this output - wallet_data.address.inner(), + wallet_address.inner(), output, slot_index, protocol_parameters.committable_age_range(), @@ -188,7 +188,7 @@ where .map_or_else( || output.amount(), |sdr| { - if wallet_data.address.inner() == sdr.return_address() { + if wallet_address.inner() == sdr.return_address() { // sending to ourself, we get the full amount output.amount() } else { @@ -219,13 +219,13 @@ where // Amount for basic outputs isn't added to total storage cost if there aren't native // tokens, since we can spend it without burning. if output.native_token().is_some() - && !wallet_data.locked_outputs.contains(output_id) + && !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } } else if output.is_nft() { balance.required_storage_deposit.nft += storage_cost; - if !wallet_data.locked_outputs.contains(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { total_storage_cost += storage_cost; } } @@ -259,18 +259,18 @@ where } // for `available` get locked_outputs, sum outputs amount and subtract from total_amount - log::debug!("[BALANCE] locked outputs: {:#?}", wallet_data.locked_outputs); + log::debug!("[BALANCE] locked outputs: {:#?}", wallet_ledger.locked_outputs); let mut locked_amount = 0; let mut locked_mana = DecayedMana::default(); let mut locked_native_tokens = NativeTokensBuilder::default(); - for locked_output in &wallet_data.locked_outputs { + for locked_output in &wallet_ledger.locked_outputs { // Skip potentially_locked_outputs, as their amounts aren't added to the balance if balance.potentially_locked_outputs.contains_key(locked_output) { continue; } - if let Some(output_data) = wallet_data.unspent_outputs.get(locked_output) { + if let Some(output_data) = wallet_ledger.unspent_outputs.get(locked_output) { // Only check outputs that are in this network if output_data.network_id == network_id { locked_amount += output_data.output.amount(); @@ -309,7 +309,7 @@ where } }); - let metadata = wallet_data + let metadata = wallet_ledger .native_token_foundries .get(&FoundryId::from(*native_token.token_id())) .and_then(|foundry| foundry.immutable_features().metadata()) @@ -325,6 +325,14 @@ where ); } + drop(wallet_ledger); + + for output_id in reward_outputs { + if let Ok(response) = self.client().get_output_mana_rewards(&output_id, slot_index).await { + balance.mana.rewards += response.rewards; + } + } + #[cfg(not(feature = "participation"))] { balance.base_coin.available = balance.base_coin.total.saturating_sub(locked_amount); diff --git a/sdk/src/wallet/operations/block.rs b/sdk/src/wallet/operations/block.rs index 491bd78ae6..afdfb18240 100644 --- a/sdk/src/wallet/operations/block.rs +++ b/sdk/src/wallet/operations/block.rs @@ -1,28 +1,37 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +#[cfg(feature = "events")] +use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent}; use crate::{ - client::secret::{SecretManage, SignBlock}, + client::{ + secret::{SecretManage, SignBlock}, + ClientError, + }, types::block::{output::AccountId, payload::Payload, BlockId}, - wallet::{Error, Result, Wallet}, + wallet::{Wallet, WalletError}, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { pub(crate) async fn submit_basic_block( &self, payload: impl Into> + Send, issuer_id: impl Into> + Send, allow_negative_bic: bool, - ) -> Result { + ) -> Result { log::debug!("submit_basic_block"); // If an issuer ID is provided, use it; otherwise, use the first available account or implicit account. let issuer_id = match issuer_id.into() { Some(id) => id, - None => self.data().await.first_account_id().ok_or(Error::AccountNotFound)?, + None => self + .ledger() + .await + .first_account_id() + .ok_or(WalletError::AccountNotFound)?, }; let unsigned_block = self.client().build_basic_block(issuer_id, payload).await?; @@ -32,20 +41,30 @@ where let work_score = protocol_parameters.work_score(unsigned_block.body.as_basic()); let congestion = self.client().get_account_congestion(&issuer_id, work_score).await?; if (congestion.reference_mana_cost * work_score as u64) as i128 > congestion.block_issuance_credits { - return Err(crate::wallet::Error::InsufficientBic { + return Err(WalletError::InsufficientBic { available: congestion.block_issuance_credits, required: work_score as u64 * congestion.reference_mana_cost, }); } } + #[cfg(feature = "events")] + self.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::PreparedBlockSigningInput(prefix_hex::encode(unsigned_block.signing_input())), + )) + .await; + let block = unsigned_block .sign_ed25519( - &*self.get_secret_manager().read().await, - self.bip_path().await.ok_or(Error::MissingBipPath)?, + &*self.secret_manager().read().await, + self.bip_path().await.ok_or(WalletError::MissingBipPath)?, ) .await?; + #[cfg(feature = "events")] + self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting)) + .await; + let block_id = self.client().post_block(&block).await?; log::debug!("submitted block {}", block_id); diff --git a/sdk/src/wallet/operations/helpers/time.rs b/sdk/src/wallet/operations/helpers/time.rs index 62a504e592..d44b99cb46 100644 --- a/sdk/src/wallet/operations/helpers/time.rs +++ b/sdk/src/wallet/operations/helpers/time.rs @@ -3,7 +3,7 @@ use crate::{ types::block::{address::Address, output::Output, protocol::CommittableAgeRange, slot::SlotIndex}, - wallet::types::OutputData, + wallet::{types::OutputData, WalletError}, }; // Check if an output can be unlocked by the wallet address at the current time @@ -12,7 +12,7 @@ pub(crate) fn can_output_be_unlocked_now( output_data: &OutputData, commitment_slot_index: impl Into + Copy, committable_age_range: CommittableAgeRange, -) -> crate::wallet::Result { +) -> Result { if let Some(unlock_conditions) = output_data.output.unlock_conditions() { if unlock_conditions.is_timelocked(commitment_slot_index, committable_age_range.min) { return Ok(false); diff --git a/sdk/src/wallet/operations/output_claiming.rs b/sdk/src/wallet/operations/output_claiming.rs index eaec534a7c..6b2dc20dab 100644 --- a/sdk/src/wallet/operations/output_claiming.rs +++ b/sdk/src/wallet/operations/output_claiming.rs @@ -6,9 +6,13 @@ use std::collections::HashSet; use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{ + api::{options::TransactionOptions, PreparedTransactionData}, + secret::SecretManage, + ClientError, + }, types::block::{ - address::{Address, Ed25519Address}, + address::{Address, Bech32Address, Ed25519Address}, output::{ unlock_condition::AddressUnlockCondition, BasicOutput, NftOutputBuilder, Output, OutputId, UnlockCondition, }, @@ -16,10 +20,10 @@ use crate::{ slot::SlotIndex, }, wallet::{ - core::WalletData, - operations::{helpers::time::can_output_be_unlocked_now, transaction::TransactionOptions}, + core::WalletLedger, + operations::helpers::time::can_output_be_unlocked_now, types::{OutputData, TransactionWithMetadata}, - Wallet, + Wallet, WalletError, }, }; @@ -34,7 +38,7 @@ pub enum OutputsToClaim { All, } -impl WalletData { +impl WalletLedger { /// Get basic and nft outputs that have /// [`ExpirationUnlockCondition`](crate::types::block::output::unlock_condition::ExpirationUnlockCondition), /// [`StorageDepositReturnUnlockCondition`](crate::types::block::output::unlock_condition::StorageDepositReturnUnlockCondition) or @@ -43,10 +47,11 @@ impl WalletData { /// additional inputs pub(crate) fn claimable_outputs( &self, + wallet_address: &Bech32Address, outputs_to_claim: OutputsToClaim, slot_index: SlotIndex, protocol_parameters: &ProtocolParameters, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { log::debug!("[OUTPUT_CLAIMING] claimable_outputs"); // Get outputs for the claim @@ -66,7 +71,7 @@ impl WalletData { && can_output_be_unlocked_now( // We use the addresses with unspent outputs, because other addresses of the // account without unspent outputs can't be related to this output - self.address.inner(), + wallet_address.inner(), output_data, slot_index, protocol_parameters.committable_age_range(), @@ -132,8 +137,8 @@ impl WalletData { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Get basic and nft outputs that have /// [`ExpirationUnlockCondition`](crate::types::block::output::unlock_condition::ExpirationUnlockCondition), @@ -141,26 +146,31 @@ where /// [`TimelockUnlockCondition`](crate::types::block::output::unlock_condition::TimelockUnlockCondition) and can be /// unlocked now and also get basic outputs with only an [`AddressUnlockCondition`] unlock condition, for /// additional inputs - pub async fn claimable_outputs(&self, outputs_to_claim: OutputsToClaim) -> crate::wallet::Result> { - let wallet_data = self.data().await; + pub async fn claimable_outputs(&self, outputs_to_claim: OutputsToClaim) -> Result, WalletError> { + let wallet_ledger = self.ledger().await; let slot_index = self.client().get_slot_index().await?; let protocol_parameters = self.client().get_protocol_parameters().await?; - wallet_data.claimable_outputs(outputs_to_claim, slot_index, &protocol_parameters) + wallet_ledger.claimable_outputs( + &self.address().await, + outputs_to_claim, + slot_index, + &protocol_parameters, + ) } /// Get basic outputs that have only one unlock condition which is [AddressUnlockCondition], so they can be used as /// additional inputs - pub(crate) async fn get_basic_outputs_for_additional_inputs(&self) -> crate::wallet::Result> { + pub(crate) async fn get_basic_outputs_for_additional_inputs(&self) -> Result, WalletError> { log::debug!("[OUTPUT_CLAIMING] get_basic_outputs_for_additional_inputs"); #[cfg(feature = "participation")] - let voting_output = self.get_voting_output().await?; - let wallet_data = self.data().await; + let voting_output = self.get_voting_output().await; + let wallet_ledger = self.ledger().await; // Get basic outputs only with AddressUnlockCondition and no other unlock condition let mut basic_outputs: Vec = Vec::new(); - for (output_id, output_data) in &wallet_data.unspent_outputs { + for (output_id, output_data) in &wallet_ledger.unspent_outputs { #[cfg(feature = "participation")] if let Some(ref voting_output) = voting_output { // Remove voting output from inputs, because we don't want to spent it to claim something else. @@ -169,8 +179,8 @@ where } } // Don't use outputs that are locked for other transactions - if !wallet_data.locked_outputs.contains(output_id) { - if let Some(output) = wallet_data.outputs.get(output_id) { + if !wallet_ledger.locked_outputs.contains(output_id) { + if let Some(output) = wallet_ledger.outputs.get(output_id) { if let Output::Basic(basic_output) = &output.output { if let [UnlockCondition::Address(a)] = basic_output.unlock_conditions().as_ref() { // Implicit accounts can't be used @@ -193,27 +203,12 @@ where pub async fn claim_outputs + Send>( &self, output_ids_to_claim: I, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { log::debug!("[OUTPUT_CLAIMING] claim_outputs"); - let prepared_transaction = self.prepare_claim_outputs(output_ids_to_claim).await.map_err(|error| { - // Map InsufficientStorageDepositAmount error here because it's the result of InsufficientFunds in this - // case and then easier to handle - match error { - crate::wallet::Error::Block(block_error) => match *block_error { - crate::types::block::Error::InsufficientStorageDepositAmount { amount, required } => { - crate::wallet::Error::InsufficientFunds { - available: amount, - required, - } - } - _ => crate::wallet::Error::Block(block_error), - }, - _ => error, - } - })?; + let prepared_transaction = self.prepare_claim_outputs(output_ids_to_claim).await?; let claim_tx = self.sign_and_submit_transaction(prepared_transaction, None).await?; @@ -229,7 +224,7 @@ where pub async fn prepare_claim_outputs + Send>( &self, output_ids_to_claim: I, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -239,25 +234,25 @@ where let storage_score_params = self.client().get_storage_score_parameters().await?; - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; let mut outputs_to_claim = Vec::new(); for output_id in output_ids_to_claim { - if let Some(output_data) = wallet_data.unspent_outputs.get(&output_id) { - if !wallet_data.locked_outputs.contains(&output_id) { + if let Some(output_data) = wallet_ledger.unspent_outputs.get(&output_id) { + if !wallet_ledger.locked_outputs.contains(&output_id) { outputs_to_claim.push(output_data.clone()); } } } if outputs_to_claim.is_empty() { - return Err(crate::wallet::Error::CustomInput( + return Err(WalletError::CustomInput( "provided outputs can't be claimed".to_string(), )); } - let wallet_address = wallet_data.address.clone(); - drop(wallet_data); + let wallet_address = self.address().await; + drop(wallet_ledger); let mut nft_outputs_to_send = Vec::new(); @@ -279,13 +274,13 @@ where // deposit for the remaining amount and possible native tokens NftOutputBuilder::from(nft_output) .with_nft_id(nft_output.nft_id_non_null(&output_data.output_id)) - .with_unlock_conditions([AddressUnlockCondition::new(wallet_address.clone())]) + .with_unlock_conditions([AddressUnlockCondition::new(&wallet_address)]) .finish_output()? } else { NftOutputBuilder::from(nft_output) .with_minimum_amount(storage_score_params) .with_nft_id(nft_output.nft_id_non_null(&output_data.output_id)) - .with_unlock_conditions([AddressUnlockCondition::new(wallet_address.clone())]) + .with_unlock_conditions([AddressUnlockCondition::new(&wallet_address)]) .finish_output()? }; @@ -293,7 +288,7 @@ where } } - self.prepare_transaction( + self.prepare_send_outputs( // We only need to provide the NFT outputs, ISA automatically creates basic outputs as remainder outputs nft_outputs_to_send, TransactionOptions { diff --git a/sdk/src/wallet/operations/output_consolidation.rs b/sdk/src/wallet/operations/output_consolidation.rs index e4fe56c5ed..e4c9450b5a 100644 --- a/sdk/src/wallet/operations/output_consolidation.rs +++ b/sdk/src/wallet/operations/output_consolidation.rs @@ -8,7 +8,14 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "ledger_nano")] use crate::client::secret::{ledger_nano::LedgerSecretManager, DowncastSecretManager}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{ + api::{ + options::{RemainderValueStrategy, TransactionOptions}, + PreparedTransactionData, + }, + secret::SecretManage, + ClientError, + }, types::block::{ address::{Address, Bech32Address}, input::INPUT_COUNT_MAX, @@ -17,9 +24,9 @@ use crate::{ }, wallet::{ constants::DEFAULT_OUTPUT_CONSOLIDATION_THRESHOLD, - operations::{helpers::time::can_output_be_unlocked_now, transaction::TransactionOptions}, + operations::helpers::time::can_output_be_unlocked_now, types::{OutputData, TransactionWithMetadata}, - RemainderValueStrategy, Result, Wallet, + Wallet, WalletError, }, }; @@ -69,13 +76,16 @@ impl ConsolidationParams { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Consolidates basic outputs from an account by sending them to a provided address or to an own address again if /// the output amount is >= the output_threshold. When `force` is set to `true`, the threshold is ignored. Only /// consolidates the amount of outputs that fit into a single transaction. - pub async fn consolidate_outputs(&self, params: ConsolidationParams) -> Result { + pub async fn consolidate_outputs( + &self, + params: ConsolidationParams, + ) -> Result { let prepared_transaction = self.prepare_consolidate_outputs(params).await?; let consolidation_tx = self.sign_and_submit_transaction(prepared_transaction, None).await?; @@ -89,9 +99,12 @@ where } /// Prepares the transaction for [Wallet::consolidate_outputs()]. - pub async fn prepare_consolidate_outputs(&self, params: ConsolidationParams) -> Result { + pub async fn prepare_consolidate_outputs( + &self, + params: ConsolidationParams, + ) -> Result { log::debug!("[OUTPUT_CONSOLIDATION] prepare consolidating outputs if needed"); - let wallet_address = self.data().await.address.clone(); + let wallet_address = self.address().await; let outputs_to_consolidate = self.get_outputs_to_consolidate(¶ms).await?; @@ -107,7 +120,7 @@ where ..Default::default() }); - self.prepare_transaction([], options).await + self.prepare_send_outputs([], options).await } /// Determines whether an output should be consolidated or not. @@ -116,7 +129,7 @@ where output_data: &OutputData, slot_index: SlotIndex, wallet_address: &Address, - ) -> Result { + ) -> Result { Ok(if let Output::Basic(basic_output) = &output_data.output { let protocol_parameters = self.client().get_protocol_parameters().await?; let unlock_conditions = basic_output.unlock_conditions(); @@ -155,29 +168,29 @@ where } /// Returns all outputs that should be consolidated. - async fn get_outputs_to_consolidate(&self, params: &ConsolidationParams) -> Result> { + async fn get_outputs_to_consolidate(&self, params: &ConsolidationParams) -> Result, WalletError> { // #[cfg(feature = "participation")] // let voting_output = self.get_voting_output().await?; let slot_index = self.client().get_slot_index().await?; let storage_score_parameters = self.client().get_protocol_parameters().await?.storage_score_parameters; - let wallet_data = self.data().await; - let wallet_address = wallet_data.address.clone(); + let wallet_ledger = self.ledger().await; + let wallet_address = self.address().await; let mut outputs_to_consolidate = Vec::new(); let mut native_token_inputs = HashMap::new(); - for (output_id, output_data) in &wallet_data.unspent_outputs { + for (output_id, output_data) in &wallet_ledger.unspent_outputs { // #[cfg(feature = "participation")] // if let Some(ref voting_output) = voting_output { // // Remove voting output from inputs, because we want to keep its features and not consolidate it. // if output_data.output_id == voting_output.output_id { // continue; - // } + // }.await // } - let is_locked_output = wallet_data.locked_outputs.contains(output_id); + let is_locked_output = wallet_ledger.locked_outputs.contains(output_id); let should_consolidate_output = self - .should_consolidate_output(output_data, slot_index, &wallet_address) + .should_consolidate_output(output_data, slot_index, wallet_address.as_ref()) .await?; if !is_locked_output && should_consolidate_output { outputs_to_consolidate.push(output_data.clone()); @@ -210,7 +223,7 @@ where }) }); - drop(wallet_data); + drop(wallet_ledger); let output_threshold = self.get_output_consolidation_threshold(params).await?; @@ -221,7 +234,7 @@ where outputs_to_consolidate.len(), output_threshold ); - return Err(crate::wallet::Error::NoOutputsToConsolidate { + return Err(WalletError::NoOutputsToConsolidate { available_outputs: outputs_to_consolidate.len(), consolidation_threshold: output_threshold, }); @@ -240,7 +253,7 @@ where /// Returns the max amount of inputs that can be used in a consolidation transaction. For Ledger Nano it's more /// limited. - async fn get_max_inputs(&self) -> Result { + async fn get_max_inputs(&self) -> Result { #[cfg(feature = "ledger_nano")] let max_inputs = { use crate::client::secret::SecretManager; @@ -281,7 +294,7 @@ where /// Returns the threshold value above which outputs should be consolidated. Lower for ledger nano secret manager, as /// their memory size is limited. - async fn get_output_consolidation_threshold(&self, params: &ConsolidationParams) -> Result { + async fn get_output_consolidation_threshold(&self, params: &ConsolidationParams) -> Result { #[allow(clippy::option_if_let_else)] let output_threshold = match params.output_threshold { Some(t) => t, diff --git a/sdk/src/wallet/operations/participation/event.rs b/sdk/src/wallet/operations/participation/event.rs index 73e76c5cc8..f531070940 100644 --- a/sdk/src/wallet/operations/participation/event.rs +++ b/sdk/src/wallet/operations/participation/event.rs @@ -25,7 +25,7 @@ where pub async fn register_participation_events( &self, options: &ParticipationEventRegistrationOptions, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { let client = Client::builder() .with_ignore_node_health() .with_node_auth(options.node.url.as_str(), options.node.auth.clone())? @@ -71,7 +71,7 @@ where } /// Removes a previously registered participation event from local storage. - pub async fn deregister_participation_event(&self, id: &ParticipationEventId) -> crate::wallet::Result<()> { + pub async fn deregister_participation_event(&self, id: &ParticipationEventId) -> Result<(), WalletError> { self.storage_manager().remove_participation_event(id).await?; Ok(()) } @@ -80,7 +80,7 @@ where pub async fn get_participation_event( &self, id: ParticipationEventId, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { Ok(self .storage_manager() .get_participation_events() @@ -92,7 +92,7 @@ where /// Retrieves information for all registered participation events. pub async fn get_participation_events( &self, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { self.storage_manager().get_participation_events().await } @@ -101,7 +101,7 @@ where &self, node: &Node, event_type: Option, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { let client = Client::builder() .with_ignore_node_health() .with_node_auth(node.url.as_str(), node.auth.clone())? @@ -114,7 +114,7 @@ where pub async fn get_participation_event_status( &self, id: &ParticipationEventId, - ) -> crate::wallet::Result { + ) -> Result { Ok(self.get_client_for_event(id).await?.event_status(id, None).await?) } } diff --git a/sdk/src/wallet/operations/participation/mod.rs b/sdk/src/wallet/operations/participation/mod.rs index 4b83fa1b69..5ebe35645f 100644 --- a/sdk/src/wallet/operations/participation/mod.rs +++ b/sdk/src/wallet/operations/participation/mod.rs @@ -6,7 +6,7 @@ // They become spendable again when the user reduces the “voting power”. // This is done by creating a special “voting output” that adheres to the following rules, NOT by sending to a different // address. -// If the user has designated funds to vote with, the resulting output MUST NOT be used for input selection. +// If the user has designated funds to vote with, the resulting output MUST NOT be used for building the transaction. // pub(crate) mod event; // pub(crate) mod voting; @@ -25,7 +25,7 @@ use crate::{ }, block::output::{unlock_condition::UnlockCondition, Output, OutputId}, }, - wallet::{core::WalletData, types::OutputData, Result, Wallet}, + wallet::{core::WalletLedger, types::OutputData, Wallet}, }; /// An object containing an account's entire participation overview. @@ -46,17 +46,13 @@ pub struct ParticipationEventWithNodes { pub nodes: Vec, } -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { // /// Calculates the voting overview of a wallet. If event_ids are provided, only return outputs and tracked // /// participations for them. // pub async fn get_participation_overview( // &self, // event_ids: Option>, - // ) -> Result { + // ) -> Result { // log::debug!("[get_participation_overview]"); // // TODO: Could use the address endpoint in the future when https://github.com/iotaledger/inx-participation/issues/50 is done. @@ -66,8 +62,8 @@ where // "[get_participation_overview] restored_spent_cached_outputs_len: {}", // restored_spent_cached_outputs_len // ); - // let wallet_data = self.data().await; - // let participation_outputs = wallet_data.outputs().values().filter(|output_data| { + // let wallet_ledger = self.data().await; + // let participation_outputs = wallet_ledger.outputs().values().filter(|output_data| { // is_valid_participation_output(&output_data.output) // // Check that the metadata exists, because otherwise we aren't participating for anything // && output_data.output.features().and_then(|f| f.metadata()).is_some() @@ -217,13 +213,13 @@ where /// Returns the voting output ("PARTICIPATION" tag). /// /// If multiple outputs with this tag exist, the one with the largest amount will be returned. - pub async fn get_voting_output(&self) -> Result> { - self.data().await.get_voting_output() + pub async fn get_voting_output(&self) -> Option { + self.ledger().await.get_voting_output() } // /// Gets client for an event. // /// If event isn't found, the client from the account will be returned. - // pub(crate) async fn get_client_for_event(&self, id: &ParticipationEventId) -> crate::wallet::Result { + // pub(crate) async fn get_client_for_event(&self, id: &ParticipationEventId) -> Result { // log::debug!("[get_client_for_event]"); // let events = self.storage_manager().get_participation_events().await?; @@ -244,7 +240,7 @@ where // pub(crate) async fn remove_ended_participation_events( // &self, // participations: &mut Participations, - // ) -> crate::wallet::Result<()> { + // ) -> Result<(), WalletError> { // log::debug!("[remove_ended_participation_events]"); // // TODO change to one of the new timestamps, which ones ? // let latest_milestone_index = 0; @@ -271,18 +267,17 @@ where // } } -impl WalletData { +impl WalletLedger { /// Returns the voting output ("PARTICIPATION" tag). /// /// If multiple outputs with this tag exist, the one with the largest amount will be returned. - pub(crate) fn get_voting_output(&self) -> Result> { + pub(crate) fn get_voting_output(&self) -> Option { log::debug!("[get_voting_output]"); - Ok(self - .unspent_outputs + self.unspent_outputs .values() .filter(|output_data| is_valid_participation_output(&output_data.output)) .max_by_key(|output_data| output_data.output.amount()) - .cloned()) + .cloned() } } diff --git a/sdk/src/wallet/operations/participation/voting.rs b/sdk/src/wallet/operations/participation/voting.rs index 0979b21adf..30399596a2 100644 --- a/sdk/src/wallet/operations/participation/voting.rs +++ b/sdk/src/wallet/operations/participation/voting.rs @@ -38,7 +38,7 @@ where &self, event_id: impl Into> + Send, answers: impl Into>> + Send, - ) -> Result { + ) -> Result { let prepared = self.prepare_vote(event_id, answers).await?; self.sign_and_submit_transaction(prepared, None, None).await @@ -49,7 +49,7 @@ where &self, event_id: impl Into> + Send, answers: impl Into>> + Send, - ) -> Result { + ) -> Result { let event_id = event_id.into(); let answers = answers.into(); if let Some(event_id) = event_id { @@ -106,7 +106,7 @@ where ]) .finish_output()?; - self.prepare_transaction( + self.prepare_send_outputs( [new_output], Some(TransactionOptions { // Only use previous voting output as input. @@ -128,14 +128,20 @@ where /// Removes metadata for any event that has expired (use event IDs to get cached event information, checks event /// milestones in there against latest network milestone). /// If NOT already voting for this event, throws an error. - pub async fn stop_participating(&self, event_id: ParticipationEventId) -> Result { + pub async fn stop_participating( + &self, + event_id: ParticipationEventId, + ) -> Result { let prepared = self.prepare_stop_participating(event_id).await?; self.sign_and_submit_transaction(prepared, None, None).await } /// Prepares the transaction for [Wallet::stop_participating()]. - pub async fn prepare_stop_participating(&self, event_id: ParticipationEventId) -> Result { + pub async fn prepare_stop_participating( + &self, + event_id: ParticipationEventId, + ) -> Result { let voting_output = self .get_voting_output() .await? @@ -177,7 +183,7 @@ where ]) .finish_output()?; - self.prepare_transaction( + self.prepare_send_outputs( [new_output], Some(TransactionOptions { // Only use previous voting output as input. diff --git a/sdk/src/wallet/operations/participation/voting_power.rs b/sdk/src/wallet/operations/participation/voting_power.rs index 5514929c87..d48388604e 100644 --- a/sdk/src/wallet/operations/participation/voting_power.rs +++ b/sdk/src/wallet/operations/participation/voting_power.rs @@ -23,7 +23,7 @@ where crate::client::Error: From, { /// Returns an account's total voting power (voting or NOT voting). - pub async fn get_voting_power(&self) -> Result { + pub async fn get_voting_power(&self) -> Result { Ok(self .get_voting_output() .await? @@ -41,14 +41,14 @@ where /// cached event information, checks event milestones in there against latest network milestone). /// Prioritizes consuming outputs that are designated for voting but don't have any metadata (only possible if user /// increases voting power then increases again immediately after). - pub async fn increase_voting_power(&self, amount: u64) -> Result { + pub async fn increase_voting_power(&self, amount: u64) -> Result { let prepared = self.prepare_increase_voting_power(amount).await?; self.sign_and_submit_transaction(prepared, None, None).await } /// Prepares the transaction for [Wallet::increase_voting_power()]. - pub async fn prepare_increase_voting_power(&self, amount: u64) -> Result { + pub async fn prepare_increase_voting_power(&self, amount: u64) -> Result { let (new_output, tx_options) = match self.get_voting_output().await? { Some(current_output_data) => { let output = current_output_data.output.as_basic(); @@ -77,7 +77,7 @@ where ), }; - self.prepare_transaction([new_output], tx_options).await + self.prepare_send_outputs([new_output], tx_options).await } /// Reduces an account's "voting power" by a given amount. @@ -89,14 +89,14 @@ where /// milestones in there against latest network milestone). /// Prioritizes consuming outputs that are designated for voting but don't have any metadata (only possible if user /// increases voting power then decreases immediately after). - pub async fn decrease_voting_power(&self, amount: u64) -> Result { + pub async fn decrease_voting_power(&self, amount: u64) -> Result { let prepared = self.prepare_decrease_voting_power(amount).await?; self.sign_and_submit_transaction(prepared, None, None).await } /// Prepares the transaction for [Wallet::decrease_voting_power()]. - pub async fn prepare_decrease_voting_power(&self, amount: u64) -> Result { + pub async fn prepare_decrease_voting_power(&self, amount: u64) -> Result { let current_output_data = self .get_voting_output() .await? @@ -114,7 +114,7 @@ where (new_output, Some(tagged_data_payload)) }; - self.prepare_transaction( + self.prepare_send_outputs( [new_output], Some(TransactionOptions { // Use the previous voting output and additionally others for possible additional required amount for diff --git a/sdk/src/wallet/operations/syncing/addresses/output_ids/account_foundry.rs b/sdk/src/wallet/operations/syncing/addresses/output_ids/account_foundry.rs index ff4d7995cd..e67ef0339f 100644 --- a/sdk/src/wallet/operations/syncing/addresses/output_ids/account_foundry.rs +++ b/sdk/src/wallet/operations/syncing/addresses/output_ids/account_foundry.rs @@ -7,31 +7,31 @@ use crate::{ client::{ node_api::indexer::query_parameters::{AccountOutputQueryParameters, FoundryOutputQueryParameters}, secret::SecretManage, + ClientError, }, types::{ api::plugins::indexer::OutputIdsResponse, block::{ - address::{AccountAddress, Bech32Address, ToBech32Ext}, + address::{AccountAddress, AddressError, Bech32Address, ToBech32Ext}, output::{Output, OutputId}, }, }, utils::ConvertTo, - wallet::{operations::syncing::SyncOptions, task, Wallet}, + wallet::{operations::syncing::SyncOptions, task, Wallet, WalletError}, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { /// Returns output ids of account outputs pub(crate) async fn get_account_and_foundry_output_ids( &self, bech32_address: impl ConvertTo, sync_options: &SyncOptions, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { log::debug!("[SYNC] get_account_and_foundry_output_ids"); - let bech32_address = bech32_address.convert()?; + let bech32_address = bech32_address + .convert() + .map_err(AddressError::from) + .map_err(ClientError::Address)?; let mut output_ids = self .client() @@ -52,7 +52,7 @@ where pub(crate) async fn get_foundry_output_ids( &self, account_output_ids: &[OutputId], - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { log::debug!("[SYNC] get_foundry_output_ids"); // Get account outputs, so we can then get the foundry outputs with the account addresses let account_outputs_with_meta = self.get_outputs(account_output_ids.to_vec()).await?; @@ -78,7 +78,7 @@ where } let mut output_ids = HashSet::new(); - let results: Vec> = futures::future::try_join_all(tasks).await?; + let results: Vec> = futures::future::try_join_all(tasks).await?; for res in results { let foundry_output_ids = res?; diff --git a/sdk/src/wallet/operations/syncing/addresses/output_ids/basic.rs b/sdk/src/wallet/operations/syncing/addresses/output_ids/basic.rs index 4374d0c859..e86cb6d771 100644 --- a/sdk/src/wallet/operations/syncing/addresses/output_ids/basic.rs +++ b/sdk/src/wallet/operations/syncing/addresses/output_ids/basic.rs @@ -5,18 +5,15 @@ use crate::{ client::{node_api::indexer::query_parameters::BasicOutputQueryParameters, secret::SecretManage}, types::block::{address::Bech32Address, output::OutputId}, utils::ConvertTo, - wallet::Wallet, + wallet::{Wallet, WalletError}, }; -impl Wallet -where - crate::wallet::Error: From, -{ +impl Wallet { /// Returns output ids of basic outputs that have only the address unlock condition pub(crate) async fn get_basic_output_ids_with_address_unlock_condition_only( &self, bech32_address: impl ConvertTo, - ) -> crate::client::Result> { + ) -> Result, WalletError> { let bech32_address = bech32_address.convert()?; Ok(self @@ -33,7 +30,7 @@ where pub(crate) async fn get_basic_output_ids_with_any_unlock_condition( &self, bech32_address: impl ConvertTo, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { let bech32_address = bech32_address.convert()?; Ok(self diff --git a/sdk/src/wallet/operations/syncing/addresses/output_ids/mod.rs b/sdk/src/wallet/operations/syncing/addresses/output_ids/mod.rs index 994b036752..16caf0613c 100644 --- a/sdk/src/wallet/operations/syncing/addresses/output_ids/mod.rs +++ b/sdk/src/wallet/operations/syncing/addresses/output_ids/mod.rs @@ -13,21 +13,19 @@ use instant::Instant; use crate::{ client::{ - node_api::indexer::query_parameters::{FoundryOutputQueryParameters, OutputQueryParameters}, + node_api::indexer::query_parameters::{ + DelegationOutputQueryParameters, FoundryOutputQueryParameters, OutputQueryParameters, + }, secret::SecretManage, }, types::block::{address::Bech32Address, output::OutputId}, wallet::{ constants::PARALLEL_REQUESTS_AMOUNT, operations::syncing::SyncOptions, - types::address::AddressWithUnspentOutputs, Wallet, + types::address::AddressWithUnspentOutputs, Wallet, WalletError, }, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { /// Returns output ids for outputs that are directly (Ed25519 address in AddressUnlockCondition) or indirectly /// (account/nft address in AddressUnlockCondition and the account/nft output is controlled with the Ed25519 /// address) connected to @@ -35,7 +33,7 @@ where &self, address: &Bech32Address, sync_options: &SyncOptions, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { if sync_options.sync_only_most_basic_outputs { let output_ids = self .get_basic_output_ids_with_address_unlock_condition_only(address.clone()) @@ -65,6 +63,7 @@ where if (address.is_ed25519() && sync_options.wallet.basic_outputs) || (address.is_nft() && sync_options.nft.basic_outputs) || (address.is_account() && sync_options.account.basic_outputs) + || (address.is_implicit_account_creation() && sync_options.sync_implicit_accounts) { // basic outputs #[cfg(target_family = "wasm")] @@ -93,14 +92,17 @@ where } } - if (address.is_ed25519() && sync_options.wallet.nft_outputs) - || (address.is_nft() && sync_options.nft.nft_outputs) - || (address.is_account() && sync_options.account.nft_outputs) + if (address.is_ed25519() && sync_options.wallet.account_outputs) + || (address.is_nft() && sync_options.nft.account_outputs) + || (address.is_account() && sync_options.account.account_outputs) { - // nfts + // accounts and foundries #[cfg(target_family = "wasm")] { - results.push(self.get_nft_output_ids_with_any_unlock_condition(address.clone()).await) + results.push( + self.get_account_and_foundry_output_ids(address.clone(), sync_options) + .await, + ) } #[cfg(not(target_family = "wasm"))] @@ -108,10 +110,11 @@ where tasks.push( async { let bech32_address = address.clone(); + let sync_options = sync_options.clone(); let wallet = self.clone(); tokio::spawn(async move { wallet - .get_nft_output_ids_with_any_unlock_condition(bech32_address) + .get_account_and_foundry_output_ids(bech32_address, &sync_options) .await }) .await @@ -119,19 +122,44 @@ where .boxed(), ); } + } else if address.is_account() && sync_options.account.foundry_outputs { + // foundries + #[cfg(target_family = "wasm")] + { + results.push(Ok(self + .client() + .foundry_output_ids(FoundryOutputQueryParameters::new().account(address.clone())) + .await? + .items)) + } + + #[cfg(not(target_family = "wasm"))] + { + tasks.push( + async { + let bech32_address = address.clone(); + let client = self.client().clone(); + tokio::spawn(async move { + Ok(client + .foundry_output_ids(FoundryOutputQueryParameters::new().account(bech32_address)) + .await? + .items) + }) + .await + } + .boxed(), + ); + } } - if (address.is_ed25519() && sync_options.wallet.account_outputs) - || (address.is_nft() && sync_options.nft.account_outputs) - || (address.is_account() && sync_options.account.account_outputs) + if (address.is_ed25519() && sync_options.wallet.nft_outputs) + || (address.is_nft() && sync_options.nft.nft_outputs) + || (address.is_account() && sync_options.account.nft_outputs) { - // accounts and foundries + // nfts #[cfg(target_family = "wasm")] { - results.push( - self.get_account_and_foundry_output_ids(address.clone(), sync_options) - .await, - ) + results.push(self.get_nft_output_ids_with_any_unlock_condition(address.clone()).await) } #[cfg(not(target_family = "wasm"))] @@ -139,11 +167,10 @@ where tasks.push( async { let bech32_address = address.clone(); - let sync_options = sync_options.clone(); let wallet = self.clone(); tokio::spawn(async move { wallet - .get_account_and_foundry_output_ids(bech32_address, &sync_options) + .get_nft_output_ids_with_any_unlock_condition(bech32_address) .await }) .await @@ -151,13 +178,18 @@ where .boxed(), ); } - } else if address.is_account() && sync_options.account.foundry_outputs { - // foundries + } + + if (address.is_ed25519() && sync_options.wallet.delegation_outputs) + || (address.is_nft() && sync_options.nft.delegation_outputs) + || (address.is_account() && sync_options.account.delegation_outputs) + { + // delegations #[cfg(target_family = "wasm")] { results.push(Ok(self .client() - .foundry_output_ids(FoundryOutputQueryParameters::new().account(address.clone())) + .delegation_output_ids(DelegationOutputQueryParameters::new().address(address.clone())) .await? .items)) } @@ -170,7 +202,7 @@ where let client = self.client().clone(); tokio::spawn(async move { Ok(client - .foundry_output_ids(FoundryOutputQueryParameters::new().account(bech32_address)) + .delegation_output_ids(DelegationOutputQueryParameters::new().address(bech32_address)) .await? .items) }) @@ -200,7 +232,7 @@ where &self, addresses_with_unspent_outputs: Vec, options: &SyncOptions, - ) -> crate::wallet::Result<(Vec, Vec)> { + ) -> Result<(Vec, Vec), WalletError> { log::debug!("[SYNC] start get_output_ids_for_addresses"); let address_output_ids_start_time = Instant::now(); @@ -213,13 +245,13 @@ where .chunks(PARALLEL_REQUESTS_AMOUNT) .map(|x: &[AddressWithUnspentOutputs]| x.to_vec()) { - let results; + let results: Vec>; #[cfg(target_family = "wasm")] { let mut tasks = Vec::new(); for address in addresses_chunk { let output_ids = self.get_output_ids_for_address(&address.address, options).await?; - tasks.push(crate::wallet::Result::Ok((address, output_ids))); + tasks.push(Ok((address, output_ids))); } results = tasks; } @@ -235,7 +267,7 @@ where let output_ids = wallet .get_output_ids_for_address(&address.address, &sync_options) .await?; - crate::wallet::Result::Ok((address, output_ids)) + Ok((address, output_ids)) }) .await }); diff --git a/sdk/src/wallet/operations/syncing/addresses/output_ids/nft.rs b/sdk/src/wallet/operations/syncing/addresses/output_ids/nft.rs index 5430bcb009..8892fa1774 100644 --- a/sdk/src/wallet/operations/syncing/addresses/output_ids/nft.rs +++ b/sdk/src/wallet/operations/syncing/addresses/output_ids/nft.rs @@ -5,19 +5,16 @@ use crate::{ client::{node_api::indexer::query_parameters::NftOutputQueryParameters, secret::SecretManage}, types::block::{address::Bech32Address, output::OutputId}, utils::ConvertTo, - wallet::Wallet, + wallet::{Wallet, WalletError}, }; -impl Wallet -where - crate::wallet::Error: From, -{ +impl Wallet { /// Returns output ids of NFT outputs that have the address in the `AddressUnlockCondition` or /// `ExpirationUnlockCondition` pub(crate) async fn get_nft_output_ids_with_any_unlock_condition( &self, bech32_address: impl ConvertTo, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { let bech32_address = bech32_address.convert()?; Ok(self diff --git a/sdk/src/wallet/operations/syncing/addresses/outputs.rs b/sdk/src/wallet/operations/syncing/addresses/outputs.rs index ca45fc9438..90df1a4d45 100644 --- a/sdk/src/wallet/operations/syncing/addresses/outputs.rs +++ b/sdk/src/wallet/operations/syncing/addresses/outputs.rs @@ -9,20 +9,16 @@ use crate::{ constants::PARALLEL_REQUESTS_AMOUNT, task, types::{address::AddressWithUnspentOutputs, OutputData}, - Wallet, + Wallet, WalletError, }, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { /// Get outputs from addresses pub(crate) async fn get_outputs_from_address_output_ids( &self, addresses_with_unspent_outputs: Vec, - ) -> crate::wallet::Result)>> { + ) -> Result)>, WalletError> { log::debug!("[SYNC] start get_outputs_from_address_output_ids"); let address_outputs_start_time = Instant::now(); @@ -44,12 +40,12 @@ where let unspent_outputs_data = wallet .output_response_to_output_data(unspent_outputs_with_metadata) .await?; - crate::wallet::Result::Ok((address_with_unspent_outputs, unspent_outputs_data)) + Ok((address_with_unspent_outputs, unspent_outputs_data)) }) .await }); } - let results = futures::future::try_join_all(tasks).await?; + let results: Vec> = futures::future::try_join_all(tasks).await?; for res in results { addresses_with_outputs.push(res?); } diff --git a/sdk/src/wallet/operations/syncing/foundries.rs b/sdk/src/wallet/operations/syncing/foundries.rs index 278ceca9a9..f1917cbdfc 100644 --- a/sdk/src/wallet/operations/syncing/foundries.rs +++ b/sdk/src/wallet/operations/syncing/foundries.rs @@ -4,23 +4,19 @@ use std::collections::HashSet; use crate::{ - client::secret::SecretManage, + client::{secret::SecretManage, ClientError}, types::block::output::{FoundryId, Output}, - wallet::{task, Wallet}, + wallet::{task, Wallet, WalletError}, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { pub(crate) async fn request_and_store_foundry_outputs( &self, foundry_ids: HashSet, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { log::debug!("[SYNC] request_and_store_foundry_outputs"); - let mut foundries = self.data().await.native_token_foundries.clone(); + let mut foundries = self.ledger().await.native_token_foundries.clone(); let results = futures::future::try_join_all(foundry_ids.into_iter().filter(|f| !foundries.contains_key(f)).map( |foundry_id| { @@ -29,8 +25,8 @@ where task::spawn(async move { match client.foundry_output_id(foundry_id).await { Ok(output_id) => Ok(Some(client.get_output(&output_id).await?)), - Err(crate::client::Error::NoOutput(_)) => Ok(None), - Err(e) => Err(crate::wallet::Error::Client(e.into())), + Err(ClientError::NoOutput(_)) => Ok(None), + Err(e) => Err(WalletError::Client(e)), } }) .await? @@ -46,8 +42,8 @@ where } } - let mut wallet_data = self.data_mut().await; - wallet_data.native_token_foundries = foundries; + let mut wallet_ledger = self.ledger_mut().await; + wallet_ledger.native_token_foundries = foundries; Ok(()) } diff --git a/sdk/src/wallet/operations/syncing/mod.rs b/sdk/src/wallet/operations/syncing/mod.rs index 4dde220f37..405338d7fb 100644 --- a/sdk/src/wallet/operations/syncing/mod.rs +++ b/sdk/src/wallet/operations/syncing/mod.rs @@ -11,26 +11,22 @@ use std::collections::{HashMap, HashSet}; pub use self::options::SyncOptions; use crate::{ - client::secret::SecretManage, + client::{secret::SecretManage, ClientError}, types::block::{ address::{AccountAddress, Address, Bech32Address, NftAddress}, output::{FoundryId, Output, OutputId, OutputMetadata}, }, wallet::{ constants::MIN_SYNC_INTERVAL, - types::{AddressWithUnspentOutputs, Balance, OutputData}, - Wallet, + types::{address::AddressWithUnspentOutputs, Balance, OutputData}, + Wallet, WalletError, }, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { /// Set the fallback SyncOptions for account syncing. /// If storage is enabled, will persist during restarts. - pub async fn set_default_sync_options(&self, options: SyncOptions) -> crate::wallet::Result<()> { + pub async fn set_default_sync_options(&self, options: SyncOptions) -> Result<(), WalletError> { #[cfg(feature = "storage")] { self.storage_manager().set_default_sync_options(&options).await?; @@ -45,120 +41,6 @@ where self.default_sync_options.lock().await.clone() } - /// Sync the wallet by fetching new information from the nodes. Will also reissue pending transactions - /// if necessary. A custom default can be set using set_default_sync_options. - pub async fn sync(&self, options: Option) -> crate::wallet::Result { - let options = match options { - Some(opt) => opt, - None => self.default_sync_options().await, - }; - - log::debug!("[SYNC] start syncing with {:?}", options); - let syc_start_time = instant::Instant::now(); - - // Prevent syncing the account multiple times simultaneously - let time_now = crate::client::unix_timestamp_now().as_millis(); - let mut last_synced = self.last_synced.lock().await; - log::debug!("[SYNC] last time synced before {}ms", time_now - *last_synced); - if !options.force_syncing && time_now - *last_synced < MIN_SYNC_INTERVAL { - log::debug!( - "[SYNC] synced within the latest {} ms, only calculating balance", - MIN_SYNC_INTERVAL - ); - // Calculate the balance because if we created a transaction in the meantime, the amount for the inputs - // is not available anymore - return self.balance().await; - } - - self.sync_internal(&options).await?; - - // Sync transactions after updating account with outputs, so we can use them to check the transaction - // status - if options.sync_pending_transactions { - let confirmed_tx_with_unknown_output = self.sync_pending_transactions().await?; - // Sync again if we don't know the output yet, to prevent having no unspent outputs after syncing - if confirmed_tx_with_unknown_output { - log::debug!("[SYNC] a transaction for which no output is known got confirmed, syncing outputs again"); - self.sync_internal(&options).await?; - } - }; - - let balance = self.balance().await?; - // Update last_synced mutex - let time_now = crate::client::unix_timestamp_now().as_millis(); - *last_synced = time_now; - log::debug!("[SYNC] finished syncing in {:.2?}", syc_start_time.elapsed()); - Ok(balance) - } - - async fn sync_internal(&self, options: &SyncOptions) -> crate::wallet::Result<()> { - log::debug!("[SYNC] sync_internal"); - - let wallet_address_with_unspent_outputs = AddressWithUnspentOutputs { - address: self.address().await, - output_ids: self.data().await.unspent_outputs().keys().copied().collect(), - internal: false, - key_index: 0, - }; - - let address_to_sync = vec![ - wallet_address_with_unspent_outputs, - AddressWithUnspentOutputs { - address: self.implicit_account_creation_address().await?, - output_ids: vec![], - internal: false, - key_index: 0, - }, - ]; - - let (_addresses_with_unspent_outputs, spent_or_not_synced_output_ids, outputs_data) = - self.request_outputs_recursively(address_to_sync, options).await?; - - // Request possible spent outputs - log::debug!("[SYNC] spent_or_not_synced_outputs: {spent_or_not_synced_output_ids:?}"); - let spent_or_unsynced_output_metadata_responses = self - .client() - .get_outputs_metadata_ignore_not_found(&spent_or_not_synced_output_ids) - .await?; - - // Add the output response to the output ids, the output response is optional, because an output could be - // pruned and then we can't get the metadata - let mut spent_or_unsynced_output_metadata: HashMap> = - spent_or_not_synced_output_ids.into_iter().map(|o| (o, None)).collect(); - for output_metadata_response in spent_or_unsynced_output_metadata_responses { - let output_id = output_metadata_response.output_id(); - spent_or_unsynced_output_metadata.insert(*output_id, Some(output_metadata_response)); - } - - if options.sync_incoming_transactions { - let transaction_ids = outputs_data - .iter() - .map(|output| *output.output_id.transaction_id()) - .collect(); - // Request and store transaction payload for newly received unspent outputs - self.request_incoming_transaction_data(transaction_ids).await?; - } - - if options.sync_native_token_foundries { - let native_token_foundry_ids = outputs_data - .iter() - .filter_map(|output| { - output - .output - .native_token() - .map(|native_token| FoundryId::from(*native_token.token_id())) - }) - .collect::>(); - - // Request and store foundry outputs - self.request_and_store_foundry_outputs(native_token_foundry_ids).await?; - } - - // Updates wallet with balances, output ids, outputs - self.update_after_sync(outputs_data, spent_or_unsynced_output_metadata) - .await - } - // First request all outputs directly related to the wallet address, then for each nft and account output we got, // request all outputs that are related to their account/nft addresses in a loop until no new account or nft outputs // are found. @@ -166,7 +48,7 @@ where &self, addresses_to_sync: Vec, options: &SyncOptions, - ) -> crate::wallet::Result<(Vec, Vec, Vec)> { + ) -> Result<(Vec, Vec, Vec), WalletError> { // Cache account and nft addresses with the related Ed25519 address, so we can update the account // address with the new output ids. let mut addresses_to_scan: HashMap = HashMap::new(); @@ -259,3 +141,133 @@ where )) } } + +impl Wallet +where + WalletError: From, + ClientError: From, +{ + /// Sync the wallet by fetching new information from the nodes. Will also reissue pending transactions + /// if necessary. A custom default can be set using set_default_sync_options. + pub async fn sync(&self, options: Option) -> Result { + let options = match options { + Some(opt) => opt, + None => self.default_sync_options().await, + }; + + log::debug!("[SYNC] start syncing with {:?}", options); + let syc_start_time = instant::Instant::now(); + + // Prevent syncing the account multiple times simultaneously + let time_now = crate::client::unix_timestamp_now().as_millis(); + let mut last_synced = self.last_synced.lock().await; + log::debug!("[SYNC] last time synced before {}ms", time_now - *last_synced); + if !options.force_syncing && time_now - *last_synced < MIN_SYNC_INTERVAL { + log::debug!( + "[SYNC] synced within the latest {} ms, only calculating balance", + MIN_SYNC_INTERVAL + ); + // Calculate the balance because if we created a transaction in the meantime, the amount for the inputs + // is not available anymore + return self.balance().await; + } + + self.sync_internal(&options).await?; + + // Sync transactions after updating account with outputs, so we can use them to check the transaction + // status + if options.sync_pending_transactions { + let confirmed_tx_with_unknown_output = self.sync_pending_transactions().await?; + // Sync again if we don't know the output yet, to prevent having no unspent outputs after syncing + if confirmed_tx_with_unknown_output { + log::debug!("[SYNC] a transaction for which no output is known got confirmed, syncing outputs again"); + self.sync_internal(&options).await?; + } + }; + + let balance = self.balance().await?; + // Update last_synced mutex + let time_now = crate::client::unix_timestamp_now().as_millis(); + *last_synced = time_now; + log::debug!("[SYNC] finished syncing in {:.2?}", syc_start_time.elapsed()); + Ok(balance) + } + + async fn sync_internal(&self, options: &SyncOptions) -> Result<(), WalletError> { + log::debug!("[SYNC] sync_internal"); + + let wallet_address_with_unspent_outputs = AddressWithUnspentOutputs { + address: self.address().await, + output_ids: self.ledger().await.unspent_outputs().keys().copied().collect(), + }; + + let mut addresses_to_sync = vec![wallet_address_with_unspent_outputs]; + + if options.sync_implicit_accounts { + if let Ok(implicit_account_creation_address) = self.implicit_account_creation_address().await { + addresses_to_sync.push(AddressWithUnspentOutputs { + output_ids: self + .ledger() + .await + .implicit_accounts() + .filter_map(|output_data| { + if output_data.output.as_basic().address() == implicit_account_creation_address.inner() { + Some(output_data.output_id) + } else { + None + } + }) + .collect(), + address: implicit_account_creation_address, + }); + } + } + + let (_addresses_with_unspent_outputs, spent_or_not_synced_output_ids, outputs_data) = + self.request_outputs_recursively(addresses_to_sync, options).await?; + + // Request possible spent outputs + log::debug!("[SYNC] spent_or_not_synced_outputs: {spent_or_not_synced_output_ids:?}"); + let spent_or_unsynced_output_metadata_responses = self + .client() + .get_outputs_metadata_ignore_not_found(&spent_or_not_synced_output_ids) + .await?; + + // Add the output response to the output ids, the output response is optional, because an output could be + // pruned and then we can't get the metadata + let mut spent_or_unsynced_output_metadata: HashMap> = + spent_or_not_synced_output_ids.into_iter().map(|o| (o, None)).collect(); + for output_metadata_response in spent_or_unsynced_output_metadata_responses { + let output_id = output_metadata_response.output_id(); + spent_or_unsynced_output_metadata.insert(*output_id, Some(output_metadata_response)); + } + + if options.sync_incoming_transactions { + let transaction_ids = outputs_data + .iter() + .map(|output| *output.output_id.transaction_id()) + .collect(); + // Request and store transaction payload for newly received unspent outputs + self.request_incoming_transaction_data(transaction_ids).await?; + } + + if options.sync_native_token_foundries { + let native_token_foundry_ids = outputs_data + .iter() + .filter_map(|output| { + output + .output + .native_token() + .map(|native_token| FoundryId::from(*native_token.token_id())) + }) + .collect::>(); + + // Request and store foundry outputs + self.request_and_store_foundry_outputs(native_token_foundry_ids).await?; + } + + // Updates wallet with balances, output ids, outputs + self.update_after_sync(outputs_data, spent_or_unsynced_output_metadata) + .await + } +} diff --git a/sdk/src/wallet/operations/syncing/options.rs b/sdk/src/wallet/operations/syncing/options.rs index fe6d9c5489..f5a1d09816 100644 --- a/sdk/src/wallet/operations/syncing/options.rs +++ b/sdk/src/wallet/operations/syncing/options.rs @@ -92,23 +92,25 @@ impl Default for SyncOptions { #[serde(default, rename_all = "camelCase")] pub struct WalletSyncOptions { pub basic_outputs: bool, - pub nft_outputs: bool, pub account_outputs: bool, + pub nft_outputs: bool, + pub delegation_outputs: bool, } impl Default for WalletSyncOptions { fn default() -> Self { Self { basic_outputs: true, - nft_outputs: true, account_outputs: true, + nft_outputs: true, + delegation_outputs: true, } } } impl WalletSyncOptions { pub(crate) fn all_outputs(&self) -> bool { - self.basic_outputs && self.nft_outputs && self.account_outputs + self.basic_outputs && self.account_outputs && self.nft_outputs && self.delegation_outputs } } @@ -117,9 +119,10 @@ impl WalletSyncOptions { #[serde(default, rename_all = "camelCase")] pub struct AccountSyncOptions { pub basic_outputs: bool, - pub nft_outputs: bool, pub account_outputs: bool, pub foundry_outputs: bool, + pub nft_outputs: bool, + pub delegation_outputs: bool, } impl Default for AccountSyncOptions { @@ -127,16 +130,21 @@ impl Default for AccountSyncOptions { fn default() -> Self { Self { basic_outputs: false, - nft_outputs: false, account_outputs: false, foundry_outputs: true, + nft_outputs: false, + delegation_outputs: false, } } } impl AccountSyncOptions { pub(crate) fn all_outputs(&self) -> bool { - self.basic_outputs && self.nft_outputs && self.account_outputs && self.foundry_outputs + self.basic_outputs + && self.account_outputs + && self.foundry_outputs + && self.nft_outputs + && self.delegation_outputs } } @@ -145,12 +153,13 @@ impl AccountSyncOptions { #[serde(default, rename_all = "camelCase")] pub struct NftSyncOptions { pub basic_outputs: bool, - pub nft_outputs: bool, pub account_outputs: bool, + pub nft_outputs: bool, + pub delegation_outputs: bool, } impl NftSyncOptions { pub(crate) fn all_outputs(&self) -> bool { - self.basic_outputs && self.nft_outputs && self.account_outputs + self.basic_outputs && self.account_outputs && self.nft_outputs && self.delegation_outputs } } diff --git a/sdk/src/wallet/operations/syncing/outputs.rs b/sdk/src/wallet/operations/syncing/outputs.rs index c317953024..b8e62b6462 100644 --- a/sdk/src/wallet/operations/syncing/outputs.rs +++ b/sdk/src/wallet/operations/syncing/outputs.rs @@ -4,40 +4,36 @@ use instant::Instant; use crate::{ - client::{secret::SecretManage, Client, Error as ClientError}, + client::{secret::SecretManage, Client, ClientError}, types::{ api::core::OutputWithMetadataResponse, block::{ core::{BasicBlockBody, BlockBody}, input::Input, - output::{OutputId, OutputWithMetadata}, + output::OutputId, payload::{signed_transaction::TransactionId, Payload, SignedTransactionPayload}, }, }, - wallet::{build_transaction_from_payload_and_inputs, task, types::OutputData, Wallet}, + wallet::{build_transaction_from_payload_and_inputs, task, types::OutputData, Wallet, WalletError}, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { /// Convert OutputWithMetadataResponse to OutputData with the network_id added pub(crate) async fn output_response_to_output_data( &self, - outputs_with_meta: Vec, - ) -> crate::wallet::Result> { + outputs_with_meta: Vec, + ) -> Result, WalletError> { log::debug!("[SYNC] convert output_responses"); // store outputs with network_id let network_id = self.client().get_network_id().await?; - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; Ok(outputs_with_meta .into_iter() .map(|output_with_meta| { // check if we know the transaction that created this output and if we created it (if we store incoming // transactions separated, then this check wouldn't be required) - let remainder = wallet_data + let remainder = wallet_ledger .transactions .get(output_with_meta.metadata().output_id().transaction_id()) .map_or(false, |tx| !tx.incoming); @@ -59,16 +55,16 @@ where pub(crate) async fn get_outputs( &self, output_ids: Vec, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { log::debug!("[SYNC] start get_outputs"); let get_outputs_start_time = Instant::now(); let mut outputs = Vec::new(); let mut unknown_outputs = Vec::new(); let mut unspent_outputs = Vec::new(); - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; for output_id in output_ids { - match wallet_data.outputs.get_mut(&output_id) { + match wallet_ledger.outputs.get_mut(&output_id) { // set unspent if not already Some(output_data) => { if output_data.is_spent() { @@ -76,7 +72,7 @@ where output_data.metadata.spent = None; } unspent_outputs.push((output_id, output_data.clone())); - outputs.push(OutputWithMetadata::new( + outputs.push(OutputWithMetadataResponse::new( output_data.output.clone(), output_data.output_id_proof.clone(), output_data.metadata, @@ -88,10 +84,10 @@ where // known output is unspent, so insert it to the unspent outputs again, because if it was an // account/nft/foundry output it could have been removed when syncing without them for (output_id, output_data) in unspent_outputs { - wallet_data.unspent_outputs.insert(output_id, output_data); + wallet_ledger.unspent_outputs.insert(output_id, output_data); } - drop(wallet_data); + drop(wallet_ledger); if !unknown_outputs.is_empty() { outputs.extend(self.client().get_outputs_with_metadata(&unknown_outputs).await?); @@ -111,16 +107,18 @@ where pub(crate) async fn request_incoming_transaction_data( &self, mut transaction_ids: Vec, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { log::debug!("[SYNC] request_incoming_transaction_data"); - let wallet_data = self.data().await; + let wallet_ledger = self.ledger().await; transaction_ids.retain(|transaction_id| { - !(wallet_data.transactions.contains_key(transaction_id) - || wallet_data.incoming_transactions.contains_key(transaction_id) - || wallet_data.inaccessible_incoming_transactions.contains(transaction_id)) + !(wallet_ledger.transactions.contains_key(transaction_id) + || wallet_ledger.incoming_transactions.contains_key(transaction_id) + || wallet_ledger + .inaccessible_incoming_transactions + .contains(transaction_id)) }); - drop(wallet_data); + drop(wallet_ledger); // Limit parallel requests to 100, to avoid timeouts let results = @@ -162,10 +160,10 @@ where .into()) } } - Err(crate::client::Error::Node(crate::client::node_api::error::Error::NotFound(_))) => { + Err(ClientError::Node(crate::client::node_api::error::Error::NotFound(_))) => { Ok((transaction_id, None)) } - Err(e) => Err(crate::wallet::Error::Client(e.into())), + Err(e) => Err(WalletError::Client(e)), } })) .await @@ -176,15 +174,15 @@ where .await?; // Update account with new transactions - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; for (transaction_id, txn) in results.into_iter().flatten() { if let Some(transaction) = txn { - wallet_data.incoming_transactions.insert(transaction_id, transaction); + wallet_ledger.incoming_transactions.insert(transaction_id, transaction); } else { log::debug!("[SYNC] adding {transaction_id} to inaccessible_incoming_transactions"); // Save transactions that weren't found by the node to avoid requesting them endlessly. // Will be cleared when new client options are provided. - wallet_data.inaccessible_incoming_transactions.insert(transaction_id); + wallet_ledger.inaccessible_incoming_transactions.insert(transaction_id); } } @@ -196,7 +194,7 @@ where pub(crate) async fn get_inputs_for_transaction_payload( client: &Client, transaction_payload: &SignedTransactionPayload, -) -> crate::wallet::Result> { +) -> Result, WalletError> { let output_ids = transaction_payload .transaction() .inputs() diff --git a/sdk/src/wallet/operations/syncing/transactions.rs b/sdk/src/wallet/operations/syncing/transactions.rs index b5ab3a80ee..064f0bd5d1 100644 --- a/sdk/src/wallet/operations/syncing/transactions.rs +++ b/sdk/src/wallet/operations/syncing/transactions.rs @@ -1,16 +1,18 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use std::collections::HashMap; + use crate::{ - client::secret::SecretManage, + client::{secret::SecretManage, ClientError}, types::{ api::core::TransactionState, block::{input::Input, output::OutputId, BlockId}, }, wallet::{ - core::WalletData, + core::WalletLedger, types::{InclusionState, TransactionWithMetadata}, - Wallet, + Wallet, WalletError, }, }; @@ -21,41 +23,54 @@ use crate::{ impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Sync transactions. Returns the transaction with updated metadata and spent /// output ids that don't need to be locked anymore /// Return true if a transaction got confirmed for which we don't have an output already, based on this outputs will /// be synced again - pub(crate) async fn sync_pending_transactions(&self) -> crate::wallet::Result { + pub(crate) async fn sync_pending_transactions(&self) -> Result { log::debug!("[SYNC] sync pending transactions"); - let wallet_data = self.data().await; + let network_id = self.client().get_network_id().await?; + let wallet_ledger = self.ledger().await; // only set to true if a transaction got confirmed for which we don't have an output // (transaction_output.is_none()) let mut confirmed_unknown_output = false; - if wallet_data.pending_transactions.is_empty() { + if wallet_ledger.pending_transactions.is_empty() { return Ok(confirmed_unknown_output); } - let network_id = self.client().get_network_id().await?; - let mut updated_transactions = Vec::new(); let mut spent_output_ids = Vec::new(); // Inputs from conflicting transactions that are unspent, but should be removed from the locked outputs so they // are available again let mut output_ids_to_unlock = Vec::new(); - for transaction_id in &wallet_data.pending_transactions { + let pending_transactions = wallet_ledger + .pending_transactions + .iter() + .copied() + .map(|id| { + ( + id, + wallet_ledger + .transactions + .get(&id) + // panic during development to easier detect if something is wrong, should be handled different + // later + .expect("transaction id stored, but transaction is missing") + .clone(), + ) + }) + .collect::>(); + + drop(wallet_ledger); + + for (transaction_id, mut transaction) in pending_transactions { log::debug!("[SYNC] sync pending transaction {transaction_id}"); - let mut transaction = wallet_data - .transactions - .get(transaction_id) - // panic during development to easier detect if something is wrong, should be handled different later - .expect("transaction id stored, but transaction is missing") - .clone(); // only check transaction from the network we're connected to if transaction.network_id != network_id { @@ -64,25 +79,31 @@ where // check if we have an output (remainder, if not sending to an own address) that got created by this // transaction, if that's the case, then the transaction got confirmed - let transaction_output = wallet_data + let transaction_output = self + .ledger() + .await .outputs .keys() - .find(|o| o.transaction_id() == transaction_id); + .find(|o| o.transaction_id() == &transaction_id) + .copied(); if let Some(transaction_output) = transaction_output { - // Save to unwrap, we just got the output - let confirmed_output_data = wallet_data.outputs.get(transaction_output).expect("output exists"); - log::debug!( - "[SYNC] confirmed transaction {transaction_id} in block {}", - confirmed_output_data.metadata.block_id() - ); - updated_transaction_and_outputs( - transaction, - Some(*confirmed_output_data.metadata.block_id()), - InclusionState::Confirmed, - &mut updated_transactions, - &mut spent_output_ids, - ); + { + let wallet_ledger = self.ledger().await; + // Safe to unwrap, we just got the output + let confirmed_output_data = wallet_ledger.outputs.get(&transaction_output).expect("output exists"); + log::debug!( + "[SYNC] confirmed transaction {transaction_id} in block {}", + confirmed_output_data.metadata.block_id() + ); + updated_transaction_and_outputs( + transaction, + Some(*confirmed_output_data.metadata.block_id()), + InclusionState::Confirmed, + &mut updated_transactions, + &mut spent_output_ids, + ); + } continue; } @@ -90,7 +111,7 @@ where let mut input_got_spent = false; for input in transaction.payload.transaction().inputs() { let Input::Utxo(input) = input; - if let Some(input) = wallet_data.outputs.get(input.output_id()) { + if let Some(input) = self.ledger().await.outputs.get(input.output_id()) { if input.metadata.is_spent() { input_got_spent = true; } @@ -152,17 +173,17 @@ where } } else if input_got_spent { process_transaction_with_unknown_state( - &wallet_data, + &*self.ledger().await, transaction, &mut updated_transactions, &mut output_ids_to_unlock, )?; } } - Err(crate::client::Error::Node(crate::client::node_api::error::Error::NotFound(_))) => { + Err(ClientError::Node(crate::client::node_api::error::Error::NotFound(_))) => { if input_got_spent { process_transaction_with_unknown_state( - &wallet_data, + &*self.ledger().await, transaction, &mut updated_transactions, &mut output_ids_to_unlock, @@ -173,7 +194,7 @@ where } } else if input_got_spent { process_transaction_with_unknown_state( - &wallet_data, + &*self.ledger().await, transaction, &mut updated_transactions, &mut output_ids_to_unlock, @@ -188,7 +209,6 @@ where updated_transactions.push(transaction); } } - drop(wallet_data); // updates account with balances, output ids, outputs self.update_with_transactions(updated_transactions, spent_output_ids, output_ids_to_unlock) @@ -219,15 +239,15 @@ fn updated_transaction_and_outputs( // When a transaction got pruned, the inputs and outputs are also not available, then this could mean that it was // confirmed and the created outputs got also already spent and pruned or the inputs got spent in another transaction fn process_transaction_with_unknown_state( - wallet_data: &WalletData, + wallet_ledger: &WalletLedger, mut transaction: TransactionWithMetadata, updated_transactions: &mut Vec, output_ids_to_unlock: &mut Vec, -) -> crate::wallet::Result<()> { +) -> Result<(), WalletError> { let mut all_inputs_spent = true; for input in transaction.payload.transaction().inputs() { let Input::Utxo(input) = input; - if let Some(output_data) = wallet_data.outputs.get(input.output_id()) { + if let Some(output_data) = wallet_ledger.outputs.get(input.output_id()) { if !output_data.is_spent() { // unspent output needs to be made available again output_ids_to_unlock.push(*input.output_id()); diff --git a/sdk/src/wallet/operations/transaction/account.rs b/sdk/src/wallet/operations/transaction/account.rs index b7b6c5b048..a6dc6e9454 100644 --- a/sdk/src/wallet/operations/transaction/account.rs +++ b/sdk/src/wallet/operations/transaction/account.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::{ address::Address, output::{ @@ -16,21 +16,21 @@ use crate::{ }, wallet::{ operations::transaction::{TransactionOptions, TransactionWithMetadata}, - Error, Result, Wallet, + Wallet, WalletError, }, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Transitions an implicit account to an account. pub async fn implicit_account_transition( &self, output_id: &OutputId, key_source: impl Into + Send, - ) -> Result { + ) -> Result { let issuer_id = AccountId::from(output_id); self.sign_and_submit_transaction( @@ -48,19 +48,19 @@ where &self, output_id: &OutputId, key_source: impl Into + Send, - ) -> Result + ) -> Result where - crate::wallet::Error: From, + WalletError: From, { - let wallet_data = self.data().await; - let implicit_account_data = wallet_data + let wallet_ledger = self.ledger().await; + let implicit_account_data = wallet_ledger .unspent_outputs .get(output_id) - .ok_or(Error::ImplicitAccountNotFound)?; + .ok_or(WalletError::ImplicitAccountNotFound)?; let implicit_account = if implicit_account_data.output.is_implicit_account() { implicit_account_data.output.as_basic() } else { - return Err(Error::ImplicitAccountNotFound); + return Err(WalletError::ImplicitAccountNotFound); }; let ed25519_address = *implicit_account .address() @@ -95,16 +95,15 @@ where )?]) .finish_output()?; - drop(wallet_data); + drop(wallet_ledger); let transaction_options = TransactionOptions { required_inputs: [*output_id].into(), issuer_id: Some(account_id), - allow_additional_input_selection: false, ..Default::default() }; - self.prepare_transaction(vec![account], transaction_options.clone()) + self.prepare_send_outputs(vec![account], transaction_options.clone()) .await } } diff --git a/sdk/src/wallet/operations/transaction/build_transaction.rs b/sdk/src/wallet/operations/transaction/build_transaction.rs new file mode 100644 index 0000000000..72df49b0e3 --- /dev/null +++ b/sdk/src/wallet/operations/transaction/build_transaction.rs @@ -0,0 +1,139 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use alloc::collections::BTreeSet; + +use crypto::keys::bip44::Bip44; + +#[cfg(feature = "events")] +use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent}; +use crate::{ + client::{ + api::{options::TransactionOptions, PreparedTransactionData}, + secret::{types::InputSigningData, SecretManage}, + }, + types::block::{ + address::Bech32Address, + output::{Output, OutputId}, + protocol::CommittableAgeRange, + slot::SlotIndex, + }, + wallet::{ + operations::helpers::time::can_output_be_unlocked_forever_from_now_on, types::OutputData, Wallet, WalletError, + }, +}; + +impl Wallet { + /// Builds a transaction using the given outputs and options. + pub(crate) async fn build_transaction( + &self, + outputs: Vec, + mut options: TransactionOptions, + ) -> Result { + log::debug!("[TRANSACTION] build_transaction"); + // Voting output needs to be requested before to prevent a deadlock + #[cfg(feature = "participation")] + let voting_output = self.get_voting_output().await; + let protocol_parameters = self.client().get_protocol_parameters().await?; + + let slot_commitment_id = self.client().get_issuance().await?.latest_commitment.id(); + if options.issuer_id.is_none() { + options.issuer_id = self.ledger().await.first_account_id(); + } + + #[cfg(feature = "events")] + self.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::BuildingTransaction, + )) + .await; + + let wallet_ledger = self.ledger().await; + + #[allow(unused_mut)] + let mut forbidden_inputs = wallet_ledger.locked_outputs.clone(); + + // Prevent consuming the voting output if not actually wanted + #[cfg(feature = "participation")] + if let Some(voting_output) = &voting_output { + if !options.required_inputs.contains(&voting_output.output_id) { + forbidden_inputs.insert(voting_output.output_id); + } + } + + // Filter inputs to not include inputs that require additional outputs for storage deposit return or could be + // still locked. + let available_inputs = filter_inputs( + &self.address().await, + self.bip_path().await, + wallet_ledger + .unspent_outputs + .iter() + .filter_map(|(id, data)| (!forbidden_inputs.contains(id)).then_some(data)), + slot_commitment_id.slot_index(), + protocol_parameters.committable_age_range(), + &options.required_inputs, + )?; + + // Check that no input got already locked + for output_id in &options.required_inputs { + if wallet_ledger.locked_outputs.contains(output_id) { + return Err(WalletError::CustomInput(format!( + "provided custom input {output_id} is already used in another transaction", + ))); + } + } + + Ok(self + .client() + .build_transaction_inner( + [self.address().await.into_inner()], + available_inputs, + outputs, + options, + slot_commitment_id, + protocol_parameters, + ) + .await?) + } +} + +/// Filter available outputs to only include outputs that can be unlocked forever from this moment. +/// Note: this is only for the default transaction builder, it's still possible to send these outputs by using +/// `claim_outputs` or providing their OutputId's in the custom_inputs +#[allow(clippy::too_many_arguments)] +fn filter_inputs<'a>( + wallet_address: &Bech32Address, + wallet_bip_path: Option, + available_outputs: impl IntoIterator, + slot_index: impl Into + Copy, + committable_age_range: CommittableAgeRange, + required_inputs: &BTreeSet, +) -> Result, WalletError> { + let mut available_outputs_signing_data = Vec::new(); + + for output_data in available_outputs { + if !required_inputs.contains(&output_data.output_id) { + let output_can_be_unlocked_now_and_in_future = can_output_be_unlocked_forever_from_now_on( + // We use the addresses with unspent outputs, because other addresses of the + // account without unspent outputs can't be related to this output + wallet_address.inner(), + &output_data.output, + slot_index, + committable_age_range, + ); + + // Outputs that could get unlocked in the future will not be included + if !output_can_be_unlocked_now_and_in_future { + continue; + } + } + + if let Some(available_input) = + output_data.input_signing_data(wallet_address, wallet_bip_path, slot_index, committable_age_range)? + { + available_outputs_signing_data.push(available_input); + } + } + + Ok(available_outputs_signing_data) +} diff --git a/sdk/src/wallet/operations/transaction/high_level/allot_mana.rs b/sdk/src/wallet/operations/transaction/high_level/allot_mana.rs index d379a611e2..752320db22 100644 --- a/sdk/src/wallet/operations/transaction/high_level/allot_mana.rs +++ b/sdk/src/wallet/operations/transaction/high_level/allot_mana.rs @@ -2,24 +2,24 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::mana::ManaAllotment, wallet::{ operations::transaction::{TransactionOptions, TransactionWithMetadata}, - Wallet, + Wallet, WalletError, }, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { pub async fn allot_mana( &self, allotments: impl IntoIterator> + Send, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let options = options.into(); let prepared_transaction = self.prepare_allot_mana(allotments, options.clone()).await?; @@ -30,7 +30,7 @@ where &self, allotments: impl IntoIterator> + Send, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] prepare_allot_mana"); let mut options = options.into().unwrap_or_default(); @@ -41,6 +41,6 @@ where *options.mana_allotments.entry(account_id).or_default() += mana; } - self.prepare_transaction([], options).await + self.prepare_send_outputs([], options).await } } diff --git a/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs index c09dbb8d57..6d1322fbdf 100644 --- a/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/burning_melting/melt_native_token.rs @@ -4,22 +4,21 @@ use primitive_types::U256; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::output::{ - AccountId, AccountOutputBuilder, FoundryId, FoundryOutputBuilder, Output, SimpleTokenScheme, TokenId, - TokenScheme, + AccountId, FoundryId, FoundryOutputBuilder, Output, SimpleTokenScheme, TokenId, TokenScheme, }, wallet::{ operations::transaction::TransactionOptions, types::{OutputData, TransactionWithMetadata}, - Error, Wallet, + Wallet, WalletError, }, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Melts native tokens. /// @@ -31,7 +30,7 @@ where token_id: TokenId, melt_amount: impl Into + Send, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let options = options.into(); let prepared_transaction = self .prepare_melt_native_token(token_id, melt_amount, options.clone()) @@ -46,7 +45,7 @@ where token_id: TokenId, melt_amount: impl Into + Send, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] prepare_melt_native_token"); let foundry_id = FoundryId::from(token_id); @@ -59,29 +58,27 @@ where Output::Foundry(foundry_output) => (account_data, foundry_output), _ => unreachable!("We already checked it's a foundry output"), })?; - - if let Output::Account(account_output) = &existing_account_output_data.output { - // Create the new account output with updated amount. - let account_output = AccountOutputBuilder::from(account_output) - .with_account_id(account_id) - .finish_output()?; - - let TokenScheme::Simple(token_scheme) = existing_foundry_output.token_scheme(); - let outputs = [ - account_output, - FoundryOutputBuilder::from(&existing_foundry_output) - .with_token_scheme(TokenScheme::Simple(SimpleTokenScheme::new( - token_scheme.minted_tokens(), - token_scheme.melted_tokens() + melt_amount, - token_scheme.maximum_supply(), - )?)) - .finish_output()?, - ]; - // Input selection will detect that we're melting native tokens and add the required inputs if available - self.prepare_transaction(outputs, options).await + let account_output_id = existing_account_output_data.output_id; + let mut options = options.into(); + if let Some(options) = options.as_mut() { + options.required_inputs.insert(account_output_id); } else { - unreachable!("We checked if it's an account output before") + options.replace(TransactionOptions { + required_inputs: [account_output_id].into(), + ..Default::default() + }); } + + let TokenScheme::Simple(token_scheme) = existing_foundry_output.token_scheme(); + let outputs = [FoundryOutputBuilder::from(&existing_foundry_output) + .with_token_scheme(TokenScheme::Simple(SimpleTokenScheme::new( + token_scheme.minted_tokens(), + token_scheme.melted_tokens() + melt_amount, + token_scheme.maximum_supply(), + )?)) + .finish_output()?]; + // Transaction builder will detect that we're melting native tokens and add the required inputs if available + self.prepare_send_outputs(outputs, options).await } /// Find and return unspent `OutputData` for given `account_id` and `foundry_id` @@ -89,11 +86,11 @@ where &self, account_id: AccountId, foundry_id: FoundryId, - ) -> crate::wallet::Result<(OutputData, OutputData)> { + ) -> Result<(OutputData, OutputData), WalletError> { let mut existing_account_output_data = None; let mut existing_foundry_output = None; - for (output_id, output_data) in self.data().await.unspent_outputs.iter() { + for (output_id, output_data) in self.ledger().await.unspent_outputs.iter() { match &output_data.output { Output::Account(output) => { if output.account_id_non_null(output_id) == account_id { @@ -115,11 +112,11 @@ where } let existing_account_output_data = existing_account_output_data.ok_or_else(|| { - Error::BurningOrMeltingFailed("required account output for foundry not found".to_string()) + WalletError::BurningOrMeltingFailed("required account output for foundry not found".to_string()) })?; let existing_foundry_output_data = existing_foundry_output - .ok_or_else(|| Error::BurningOrMeltingFailed("required foundry output not found".to_string()))?; + .ok_or_else(|| WalletError::BurningOrMeltingFailed("required foundry output not found".to_string()))?; Ok((existing_account_output_data, existing_foundry_output_data)) } diff --git a/sdk/src/wallet/operations/transaction/high_level/burning_melting/mod.rs b/sdk/src/wallet/operations/transaction/high_level/burning_melting/mod.rs index 8d615eb482..718722eee2 100644 --- a/sdk/src/wallet/operations/transaction/high_level/burning_melting/mod.rs +++ b/sdk/src/wallet/operations/transaction/high_level/burning_melting/mod.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::api::{input_selection::Burn, PreparedTransactionData}, - wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet}, + client::api::{transaction_builder::Burn, PreparedTransactionData}, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet, WalletError}, }; pub(crate) mod melt_native_token; @@ -18,7 +18,7 @@ impl Wallet { &self, burn: impl Into + Send, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let options = options.into(); let prepared = self.prepare_burn(burn, options.clone()).await?; @@ -35,12 +35,12 @@ impl Wallet { &self, burn: impl Into + Send, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let mut options = options.into().unwrap_or_default(); options.burn = Some(burn.into()); // The empty list of outputs is used. Outputs will be generated by - // the input selection algorithm based on the content of the [`Burn`] object. - self.prepare_transaction([], options).await + // the transaction builder algorithm based on the content of the [`Burn`] object. + self.prepare_send_outputs([], options).await } } diff --git a/sdk/src/wallet/operations/transaction/high_level/create_account.rs b/sdk/src/wallet/operations/transaction/high_level/create_account.rs index b57d5db157..fd9d167e72 100644 --- a/sdk/src/wallet/operations/transaction/high_level/create_account.rs +++ b/sdk/src/wallet/operations/transaction/high_level/create_account.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::{ address::Bech32Address, output::{ @@ -14,7 +14,7 @@ use crate::{ wallet::{ operations::transaction::TransactionOptions, types::{OutputData, TransactionWithMetadata}, - Wallet, + Wallet, WalletError, }, }; @@ -33,8 +33,8 @@ pub struct CreateAccountParams { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Creates an account output. /// ```ignore @@ -55,7 +55,7 @@ where &self, params: Option, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let options = options.into(); let prepared_transaction = self.prepare_create_account_output(params, options.clone()).await?; @@ -67,7 +67,7 @@ where &self, params: Option, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] prepare_create_account_output"); let storage_score_params = self.client().get_storage_score_parameters().await?; @@ -100,7 +100,7 @@ where let outputs = [account_output_builder.finish_output()?]; - self.prepare_transaction(outputs, options).await + self.prepare_send_outputs(outputs, options).await } /// Gets an existing account output. @@ -110,7 +110,7 @@ where ) -> Option<(AccountId, OutputData)> { log::debug!("[get_account_output]"); let account_id = account_id.into(); - self.data() + self.ledger() .await .unspent_outputs .values() diff --git a/sdk/src/wallet/operations/transaction/high_level/delegation/create.rs b/sdk/src/wallet/operations/transaction/high_level/delegation/create.rs index 625336b6bf..acc6ba55b0 100644 --- a/sdk/src/wallet/operations/transaction/high_level/delegation/create.rs +++ b/sdk/src/wallet/operations/transaction/high_level/delegation/create.rs @@ -4,12 +4,13 @@ use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::{ address::{AccountAddress, Bech32Address}, output::{unlock_condition::AddressUnlockCondition, DelegationId, DelegationOutputBuilder}, }, - wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet}, + utils::serde::string, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet, WalletError}, }; /// Params for `create_delegation_output()` @@ -21,6 +22,7 @@ pub struct CreateDelegationParams { // TODO: https://github.com/iotaledger/iota-sdk/issues/1888 pub address: Option, /// The amount to delegate. + #[serde(with = "string")] pub delegated_amount: u64, /// The Account Address of the validator to which this output will delegate. pub validator_address: AccountAddress, @@ -44,8 +46,8 @@ pub struct PreparedCreateDelegationTransaction { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Creates a delegation output. /// ```ignore @@ -66,7 +68,7 @@ where &self, params: CreateDelegationParams, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let options = options.into(); let prepared = self.prepare_create_delegation_output(params, options.clone()).await?; @@ -83,7 +85,7 @@ where &self, params: CreateDelegationParams, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] prepare_create_delegation_output"); let address = match params.address.as_ref() { @@ -91,7 +93,7 @@ where self.client().bech32_hrp_matches(bech32_address.hrp()).await?; bech32_address.inner().clone() } - None => self.address().await.inner().clone(), + None => self.address().await.into_inner(), }; let output = DelegationOutputBuilder::new_with_amount( @@ -102,7 +104,7 @@ where .add_unlock_condition(AddressUnlockCondition::new(address)) .finish_output()?; - let transaction = self.prepare_transaction([output], options).await?; + let transaction = self.prepare_send_outputs([output], options).await?; Ok(PreparedCreateDelegationTransaction { delegation_id: DelegationId::from(&transaction.transaction.id().into_output_id(0)), diff --git a/sdk/src/wallet/operations/transaction/high_level/delegation/delay.rs b/sdk/src/wallet/operations/transaction/high_level/delegation/delay.rs index 83ba6f38b7..98242dc057 100644 --- a/sdk/src/wallet/operations/transaction/high_level/delegation/delay.rs +++ b/sdk/src/wallet/operations/transaction/high_level/delegation/delay.rs @@ -2,15 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::output::{AddressUnlockCondition, DelegationId, DelegationOutputBuilder, MinimumOutputAmount}, - wallet::{types::TransactionWithMetadata, Wallet}, + wallet::{types::TransactionWithMetadata, Wallet, WalletError}, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Delay a delegation's claiming. The `reclaim_excess` flag indicates whether excess value over the minimum storage /// requirements will be moved to a basic output that is unlockable by the same address which controls the @@ -20,7 +20,7 @@ where &self, delegation_id: DelegationId, reclaim_excess: bool, - ) -> crate::wallet::Result { + ) -> Result { let prepared_transaction = self .prepare_delay_delegation_claiming(delegation_id, reclaim_excess) .await?; @@ -37,12 +37,12 @@ where &self, delegation_id: DelegationId, reclaim_excess: bool, - ) -> crate::wallet::Result { + ) -> Result { let delegation_output = self - .data() + .ledger() .await .unspent_delegation_output(&delegation_id) - .ok_or(crate::wallet::Error::MissingDelegation(delegation_id))? + .ok_or(WalletError::MissingDelegation(delegation_id))? .output .clone(); let protocol_parameters = self.client().get_protocol_parameters().await?; @@ -87,6 +87,6 @@ where } }; - self.prepare_transaction(outputs, None).await + self.prepare_send_outputs(outputs, None).await } } diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs index 3839f68a62..a00e862141 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/create_native_token.rs @@ -5,15 +5,15 @@ use primitive_types::U256; use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::{ address::AccountAddress, output::{ - feature::MetadataFeature, unlock_condition::ImmutableAccountAddressUnlockCondition, AccountId, - AccountOutputBuilder, FoundryId, FoundryOutputBuilder, Output, SimpleTokenScheme, TokenId, TokenScheme, + feature::MetadataFeature, unlock_condition::ImmutableAccountAddressUnlockCondition, AccountId, FoundryId, + FoundryOutputBuilder, Output, SimpleTokenScheme, TokenId, TokenScheme, }, }, - wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet}, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet, WalletError}, }; /// Address and foundry data for `create_native_token()` @@ -49,12 +49,12 @@ pub struct PreparedCreateNativeTokenTransaction { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Creates a new foundry output with minted native tokens. /// - /// Calls [Wallet::prepare_transaction()](crate::wallet::Wallet::prepare_transaction) internally, the options may + /// Calls [Wallet::prepare_send_outputs()](crate::wallet::Wallet::prepare_send_outputs) internally, the options may /// define the remainder value strategy or custom inputs. /// ```ignore /// let params = CreateNativeTokenParams { @@ -74,7 +74,7 @@ where &self, params: CreateNativeTokenParams, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let options = options.into(); let prepared = self.prepare_create_native_token(params, options.clone()).await?; @@ -91,7 +91,7 @@ where &self, params: CreateNativeTokenParams, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] create_native_token"); let protocol_parameters = self.client().get_protocol_parameters().await?; let storage_score_params = protocol_parameters.storage_score_parameters(); @@ -99,14 +99,19 @@ where let (account_id, account_output_data) = self .get_account_output(params.account_id) .await - .ok_or_else(|| crate::wallet::Error::MintingFailed("Missing account output".to_string()))?; + .ok_or_else(|| WalletError::MintingFailed("Missing account output".to_string()))?; - if let Output::Account(account_output) = &account_output_data.output { - // Create the new account output with the same features, just updated mana and foundry_counter. - let new_account_output_builder = AccountOutputBuilder::from(account_output) - .with_account_id(account_id) - .with_foundry_counter(account_output.foundry_counter() + 1); + let mut options = options.into(); + if let Some(options) = options.as_mut() { + options.required_inputs.insert(account_output_data.output_id); + } else { + options.replace(TransactionOptions { + required_inputs: [account_output_data.output_id].into(), + ..Default::default() + }); + } + if let Output::Account(account_output) = &account_output_data.output { // create foundry output with minted native tokens let foundry_id = FoundryId::build( &AccountAddress::new(account_id), @@ -115,31 +120,28 @@ where ); let token_id = TokenId::from(foundry_id); - let outputs = [ - new_account_output_builder.finish_output()?, - { - let mut foundry_builder = FoundryOutputBuilder::new_with_minimum_amount( - storage_score_params, - account_output.foundry_counter() + 1, - TokenScheme::Simple(SimpleTokenScheme::new( - params.circulating_supply, - 0, - params.maximum_supply, - )?), - ) - .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(AccountAddress::from( - account_id, - ))); + let outputs = [{ + let mut foundry_builder = FoundryOutputBuilder::new_with_minimum_amount( + storage_score_params, + account_output.foundry_counter() + 1, + TokenScheme::Simple(SimpleTokenScheme::new( + params.circulating_supply, + 0, + params.maximum_supply, + )?), + ) + .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(AccountAddress::from( + account_id, + ))); - if let Some(foundry_metadata) = params.foundry_metadata { - foundry_builder = foundry_builder.add_immutable_feature(foundry_metadata); - } + if let Some(foundry_metadata) = params.foundry_metadata { + foundry_builder = foundry_builder.add_immutable_feature(foundry_metadata); + } - foundry_builder.finish_output()? - }, // Native Tokens will be added automatically in the remainder output in try_select_inputs() - ]; + foundry_builder.finish_output()? + }]; - self.prepare_transaction(outputs, options) + self.prepare_send_outputs(outputs, options) .await .map(|transaction| PreparedCreateNativeTokenTransaction { token_id, transaction }) } else { diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs b/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs index 6f94e1e941..3b465419d1 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/mint_native_token.rs @@ -4,17 +4,15 @@ use primitive_types::U256; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, - types::block::output::{ - AccountOutputBuilder, FoundryOutputBuilder, Output, SimpleTokenScheme, TokenId, TokenScheme, - }, - wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Error, Wallet}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, + types::block::output::{FoundryOutputBuilder, Output, SimpleTokenScheme, TokenId, TokenScheme}, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet, WalletError}, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Mints additional native tokens. /// @@ -36,7 +34,7 @@ where token_id: TokenId, mint_amount: impl Into + Send, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let options = options.into(); let prepared = self .prepare_mint_native_token(token_id, mint_amount, options.clone()) @@ -52,12 +50,12 @@ where token_id: TokenId, mint_amount: impl Into + Send, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] mint_native_token"); let mint_amount = mint_amount.into(); - let wallet_data = self.data().await; - let existing_foundry_output = wallet_data.unspent_outputs.values().find(|output_data| { + let wallet_ledger = self.ledger().await; + let existing_foundry_output = wallet_ledger.unspent_outputs.values().find(|output_data| { if let Output::Foundry(output) = &output_data.output { TokenId::new(*output.id()) == token_id } else { @@ -66,21 +64,21 @@ where }); let existing_foundry_output = existing_foundry_output - .ok_or_else(|| Error::MintingFailed(format!("foundry output {token_id} is not available")))? + .ok_or_else(|| WalletError::MintingFailed(format!("foundry output {token_id} is not available")))? .clone(); let existing_account_output = if let Output::Foundry(foundry_output) = &existing_foundry_output.output { let TokenScheme::Simple(token_scheme) = foundry_output.token_scheme(); // Check if we can mint the provided amount without exceeding the maximum_supply if token_scheme.maximum_supply() - token_scheme.circulating_supply() < mint_amount { - return Err(Error::MintingFailed(format!( + return Err(WalletError::MintingFailed(format!( "minting additional {mint_amount} tokens would exceed the maximum supply: {}", token_scheme.maximum_supply() ))); } // Get the account output that controls the foundry output - let existing_account_output = wallet_data.unspent_outputs.values().find(|output_data| { + let existing_account_output = wallet_ledger.unspent_outputs.values().find(|output_data| { if let Output::Account(output) = &output_data.output { output.account_id_non_null(&output_data.output_id) == **foundry_output.account_address() } else { @@ -88,27 +86,27 @@ where } }); existing_account_output - .ok_or_else(|| Error::MintingFailed("account output is not available".to_string()))? + .ok_or_else(|| WalletError::MintingFailed("account output is not available".to_string()))? .clone() } else { - return Err(Error::MintingFailed("account output is not available".to_string())); + return Err(WalletError::MintingFailed( + "account output is not available".to_string(), + )); }; - drop(wallet_data); + drop(wallet_ledger); - let account_output = if let Output::Account(account_output) = existing_account_output.output { - account_output - } else { - unreachable!("We checked if it's an account output before") - }; - let foundry_output = if let Output::Foundry(foundry_output) = existing_foundry_output.output { - foundry_output - } else { - unreachable!("We checked if it's an foundry output before") - }; + let foundry_output = existing_foundry_output.output.as_foundry(); - // Create the next account output with the same data. - let new_account_output_builder = AccountOutputBuilder::from(&account_output); + let mut options = options.into(); + if let Some(options) = options.as_mut() { + options.required_inputs.insert(existing_account_output.output_id); + } else { + options.replace(TransactionOptions { + required_inputs: [existing_account_output.output_id].into(), + ..Default::default() + }); + } // Create next foundry output with minted native tokens @@ -121,14 +119,10 @@ where )?); let new_foundry_output_builder = - FoundryOutputBuilder::from(&foundry_output).with_token_scheme(updated_token_scheme); + FoundryOutputBuilder::from(foundry_output).with_token_scheme(updated_token_scheme); - let outputs = [ - new_account_output_builder.finish_output()?, - new_foundry_output_builder.finish_output()?, - // Native Tokens will be added automatically in the remainder output in try_select_inputs() - ]; + let outputs = [new_foundry_output_builder.finish_output()?]; - self.prepare_transaction(outputs, options).await + self.prepare_send_outputs(outputs, options).await } } diff --git a/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs b/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs index 3c91e692df..1a7eeb01da 100644 --- a/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs +++ b/sdk/src/wallet/operations/transaction/high_level/minting/mint_nfts.rs @@ -5,7 +5,7 @@ use getset::Getters; use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::{ address::Bech32Address, output::{ @@ -17,7 +17,7 @@ use crate::{ utils::ConvertTo, wallet::{ operations::transaction::{TransactionOptions, TransactionWithMetadata}, - Wallet, + Wallet, WalletError, }, }; @@ -53,7 +53,7 @@ impl MintNftParams { } /// Set the address and try convert to [`Bech32Address`] - pub fn try_with_address(mut self, address: impl ConvertTo) -> crate::wallet::Result { + pub fn try_with_address(mut self, address: impl ConvertTo) -> Result { self.address = Some(address.convert()?); Ok(self) } @@ -65,7 +65,7 @@ impl MintNftParams { } /// Set the sender address and try convert to [`Bech32Address`] - pub fn try_with_sender(mut self, sender: impl ConvertTo) -> crate::wallet::Result { + pub fn try_with_sender(mut self, sender: impl ConvertTo) -> Result { self.sender = Some(sender.convert()?); Ok(self) } @@ -89,7 +89,7 @@ impl MintNftParams { } /// Set the issuer address and try convert to [`Bech32Address`] - pub fn try_with_issuer(mut self, issuer: impl ConvertTo) -> crate::wallet::Result { + pub fn try_with_issuer(mut self, issuer: impl ConvertTo) -> Result { self.issuer = Some(issuer.convert()?); Ok(self) } @@ -109,12 +109,12 @@ impl MintNftParams { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Mints NFTs. /// - /// Calls [Wallet::prepare_transaction()](crate::wallet::Wallet::prepare_transaction) internally. The options may + /// Calls [Wallet::prepare_send_outputs()](crate::wallet::Wallet::prepare_send_outputs) internally. The options may /// define the remainder value strategy or custom inputs. Note that addresses need to be bech32-encoded. /// ```ignore /// let nft_id: [u8; 38] = @@ -138,7 +138,7 @@ where &self, params: I, options: impl Into> + Send, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -153,7 +153,7 @@ where &self, params: I, options: impl Into> + Send, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -207,6 +207,6 @@ where outputs.push(nft_builder.finish_output()?); } - self.prepare_transaction(outputs, options).await + self.prepare_send_outputs(outputs, options).await } } diff --git a/sdk/src/wallet/operations/transaction/high_level/mod.rs b/sdk/src/wallet/operations/transaction/high_level/mod.rs index 4834aa9328..0312a91efe 100644 --- a/sdk/src/wallet/operations/transaction/high_level/mod.rs +++ b/sdk/src/wallet/operations/transaction/high_level/mod.rs @@ -7,6 +7,7 @@ pub(crate) mod create_account; pub(crate) mod delegation; pub(crate) mod minting; pub(crate) mod send; +pub(crate) mod send_mana; pub(crate) mod send_native_tokens; pub(crate) mod send_nft; pub(crate) mod staking; diff --git a/sdk/src/wallet/operations/transaction/high_level/send.rs b/sdk/src/wallet/operations/transaction/high_level/send.rs index a177927306..29ae57de2f 100644 --- a/sdk/src/wallet/operations/transaction/high_level/send.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send.rs @@ -5,7 +5,7 @@ use getset::Getters; use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::{ address::{Bech32Address, ToBech32Ext}, output::{ @@ -18,7 +18,7 @@ use crate::{ wallet::{ constants::DEFAULT_EXPIRATION_SLOTS, operations::transaction::{TransactionOptions, TransactionWithMetadata}, - Error, Wallet, + Wallet, WalletError, }, }; @@ -45,7 +45,7 @@ pub struct SendParams { } impl SendParams { - pub fn new(amount: u64, address: impl ConvertTo) -> Result { + pub fn new(amount: u64, address: impl ConvertTo) -> Result { Ok(Self { amount, address: address.convert()?, @@ -54,10 +54,7 @@ impl SendParams { }) } - pub fn try_with_return_address( - mut self, - address: impl ConvertTo, - ) -> Result { + pub fn try_with_return_address(mut self, address: impl ConvertTo) -> Result { self.return_address = Some(address.convert()?); Ok(self) } @@ -75,8 +72,8 @@ impl SendParams { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Sends a certain amount of base coins to a single address. /// @@ -88,7 +85,7 @@ where amount: u64, address: impl ConvertTo, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let params = [SendParams::new(amount, address)?]; self.send_with_params(params, options).await } @@ -114,7 +111,7 @@ where &self, params: I, options: impl Into> + Send, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -129,7 +126,7 @@ where &self, params: I, options: impl Into> + Send, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -155,12 +152,12 @@ where let return_address = return_address .map(|return_address| { if return_address.hrp() != address.hrp() { - Err(crate::client::Error::Bech32HrpMismatch { + Err(ClientError::Bech32HrpMismatch { provided: return_address.hrp().to_string(), expected: address.hrp().to_string(), })?; } - Ok::<_, Error>(return_address) + Ok::<_, WalletError>(return_address) }) .transpose()? .unwrap_or_else(|| default_return_address.clone()); @@ -188,7 +185,7 @@ where .finish_output()?; if !options.as_ref().map(|o| o.allow_micro_amount).unwrap_or_default() { - return Err(Error::InsufficientFunds { + return Err(WalletError::InsufficientFunds { available: amount, required: output.amount(), }); @@ -198,6 +195,6 @@ where } } - self.prepare_transaction(outputs, options).await + self.prepare_send_outputs(outputs, options).await } } diff --git a/sdk/src/wallet/operations/transaction/high_level/send_mana.rs b/sdk/src/wallet/operations/transaction/high_level/send_mana.rs new file mode 100644 index 0000000000..7c71453414 --- /dev/null +++ b/sdk/src/wallet/operations/transaction/high_level/send_mana.rs @@ -0,0 +1,95 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; + +use crate::{ + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, + types::block::{ + address::Bech32Address, + output::{ + unlock_condition::{AddressUnlockCondition, StorageDepositReturnUnlockCondition}, + BasicOutputBuilder, + }, + }, + utils::serde::string, + wallet::{ + operations::transaction::{prepare_output::ReturnStrategy, TransactionOptions, TransactionWithMetadata}, + Wallet, WalletError, + }, +}; + +/// Params for `send_mana()`. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SendManaParams { + #[serde(with = "string")] + mana: u64, + address: Bech32Address, + return_strategy: Option, +} + +impl SendManaParams { + pub fn new(mana: u64, address: Bech32Address) -> Self { + Self { + mana, + address, + return_strategy: None, + } + } + + pub fn with_return_strategy(mut self, return_strategy: ReturnStrategy) -> Self { + self.return_strategy.replace(return_strategy); + self + } +} + +impl Wallet +where + WalletError: From, + ClientError: From, +{ + pub async fn send_mana( + &self, + params: SendManaParams, + options: impl Into> + Send, + ) -> Result { + let options = options.into(); + let prepared_transaction = self.prepare_send_mana(params, options.clone()).await?; + + self.sign_and_submit_transaction(prepared_transaction, options).await + } + + pub async fn prepare_send_mana( + &self, + params: SendManaParams, + options: impl Into> + Send, + ) -> Result { + log::debug!("[TRANSACTION] prepare_send_mana"); + let return_strategy = params.return_strategy.unwrap_or_default(); + let storage_score_params = self.client().get_storage_score_parameters().await?; + + let mut output_builder = BasicOutputBuilder::new_with_minimum_amount(storage_score_params) + .with_mana(params.mana) + .add_unlock_condition(AddressUnlockCondition::new(params.address)); + + match return_strategy { + ReturnStrategy::Return => { + output_builder = output_builder.add_unlock_condition(StorageDepositReturnUnlockCondition::new( + self.address().await.inner().clone(), + 1, + )?); + let return_amount = output_builder.clone().finish()?.amount(); + output_builder = output_builder.replace_unlock_condition(StorageDepositReturnUnlockCondition::new( + self.address().await.inner().clone(), + return_amount, + )?); + } + ReturnStrategy::Gift => {} + } + + let output = output_builder.finish_output()?; + + self.prepare_send_outputs(vec![output], options).await + } +} diff --git a/sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs b/sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs index c1c153447c..18574337f3 100644 --- a/sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send_native_tokens.rs @@ -6,7 +6,7 @@ use primitive_types::U256; use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::{ address::{Bech32Address, ToBech32Ext}, output::{ @@ -19,7 +19,7 @@ use crate::{ wallet::{ constants::DEFAULT_EXPIRATION_SLOTS, operations::transaction::{TransactionOptions, TransactionWithMetadata}, - Error, Result, Wallet, + Wallet, WalletError, }, }; @@ -45,7 +45,7 @@ pub struct SendNativeTokenParams { impl SendNativeTokenParams { /// Creates a new instance of [`SendNativeTokenParams`] - pub fn new(address: impl ConvertTo, native_token: (TokenId, U256)) -> Result { + pub fn new(address: impl ConvertTo, native_token: (TokenId, U256)) -> Result { Ok(Self { address: address.convert()?, native_token, @@ -55,7 +55,10 @@ impl SendNativeTokenParams { } /// Set the return address and try convert to [`Bech32Address`] - pub fn try_with_return_address(mut self, return_address: impl ConvertTo) -> Result { + pub fn try_with_return_address( + mut self, + return_address: impl ConvertTo, + ) -> Result { self.return_address = Some(return_address.convert()?); Ok(self) } @@ -75,8 +78,8 @@ impl SendNativeTokenParams { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Sends native tokens in basic outputs with a /// [`StorageDepositReturnUnlockCondition`](crate::types::block::output::unlock_condition::StorageDepositReturnUnlockCondition) @@ -104,7 +107,7 @@ where &self, params: I, options: impl Into> + Send, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -119,7 +122,7 @@ where &self, params: I, options: impl Into> + Send, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -143,12 +146,12 @@ where let return_address = return_address .map(|addr| { if address.hrp() != addr.hrp() { - Err(crate::client::Error::Bech32HrpMismatch { + Err(ClientError::Bech32HrpMismatch { provided: addr.hrp().to_string(), expected: address.hrp().to_string(), })?; } - Ok::<_, Error>(addr) + Ok::<_, WalletError>(addr) }) .transpose()? .unwrap_or_else(|| default_return_address.clone()); @@ -173,6 +176,6 @@ where ) } - self.prepare_transaction(outputs, options).await + self.prepare_send_outputs(outputs, options).await } } diff --git a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs index 8675a8659b..8ac76efa4b 100644 --- a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs @@ -5,7 +5,7 @@ use getset::Getters; use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, types::block::{ address::Bech32Address, output::{unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, Output}, @@ -13,7 +13,7 @@ use crate::{ utils::ConvertTo, wallet::{ operations::transaction::{TransactionOptions, TransactionWithMetadata}, - Wallet, + Wallet, WalletError, }, }; @@ -31,10 +31,7 @@ pub struct SendNftParams { impl SendNftParams { /// Creates a new instance of [`SendNftParams`] - pub fn new( - address: impl ConvertTo, - nft_id: impl ConvertTo, - ) -> Result { + pub fn new(address: impl ConvertTo, nft_id: impl ConvertTo) -> Result { Ok(Self { address: address.convert()?, nft_id: nft_id.convert()?, @@ -44,11 +41,11 @@ impl SendNftParams { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Sends an NFT to the provided address. - /// Calls [Wallet::prepare_transaction()](crate::wallet::Wallet::prepare_transaction) internally. The + /// Calls [Wallet::prepare_send_outputs()](crate::wallet::Wallet::prepare_send_outputs) internally. The /// options may define the remainder value strategy. Note that custom inputs will be replaced with the required /// nft inputs and addresses need to be bech32-encoded. /// ```ignore @@ -69,7 +66,7 @@ where &self, params: I, options: impl Into> + Send, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -84,7 +81,7 @@ where &self, params: I, options: impl Into> + Send, - ) -> crate::wallet::Result + ) -> Result where I::IntoIter: Send, { @@ -96,7 +93,7 @@ where self.client().bech32_hrp_matches(address.hrp()).await?; // Find nft output from the inputs - if let Some(nft_output_data) = self.data().await.unspent_nft_output(&nft_id) { + if let Some(nft_output_data) = self.ledger().await.unspent_nft_output(&nft_id) { if let Output::Nft(nft_output) = &nft_output_data.output { // Set the nft id and new address unlock condition let nft_builder = NftOutputBuilder::from(nft_output) @@ -105,10 +102,10 @@ where outputs.push(nft_builder.finish_output()?); } } else { - return Err(crate::wallet::Error::NftNotFoundInUnspentOutputs); + return Err(WalletError::NftNotFoundInUnspentOutputs); }; } - self.prepare_transaction(outputs, options).await + self.prepare_send_outputs(outputs, options).await } } diff --git a/sdk/src/wallet/operations/transaction/high_level/staking/begin.rs b/sdk/src/wallet/operations/transaction/high_level/staking/begin.rs index 1b7b844dc0..387cf32cae 100644 --- a/sdk/src/wallet/operations/transaction/high_level/staking/begin.rs +++ b/sdk/src/wallet/operations/transaction/high_level/staking/begin.rs @@ -4,12 +4,17 @@ use serde::{Deserialize, Serialize}; use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{ + api::{options::TransactionOptions, PreparedTransactionData}, + secret::SecretManage, + ClientError, + }, types::block::{ output::{feature::StakingFeature, AccountId, AccountOutputBuilder}, slot::EpochIndex, }, - wallet::{types::TransactionWithMetadata, TransactionOptions, Wallet}, + utils::serde::string, + wallet::{types::TransactionWithMetadata, Wallet, WalletError}, }; /// Parameters for beginning a staking period. @@ -19,8 +24,10 @@ pub struct BeginStakingParams { /// The account id which will begin staking. pub account_id: AccountId, /// The amount of tokens to stake. + #[serde(with = "string")] pub staked_amount: u64, /// The fixed cost of the validator, which it receives as part of its Mana rewards. + #[serde(with = "string")] pub fixed_cost: u64, /// The staking period (in epochs). Will default to the staking unbonding period. pub staking_period: Option, @@ -28,14 +35,14 @@ pub struct BeginStakingParams { impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { pub async fn begin_staking( &self, params: BeginStakingParams, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { let options = options.into(); let prepared = self.prepare_begin_staking(params, options.clone()).await?; @@ -47,23 +54,23 @@ where &self, params: BeginStakingParams, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] prepare_begin_staking"); let account_id = params.account_id; let account_output_data = self - .data() + .ledger() .await .unspent_account_output(&account_id) .cloned() - .ok_or_else(|| crate::wallet::Error::AccountNotFound)?; + .ok_or_else(|| WalletError::AccountNotFound)?; if account_output_data .output .features() .map_or(false, |f| f.staking().is_some()) { - return Err(crate::wallet::Error::StakingFailed(format!( + return Err(WalletError::StakingFailed(format!( "account id {account_id} already has a staking feature" ))); } @@ -72,7 +79,7 @@ where if let Some(staking_period) = params.staking_period { if staking_period < protocol_parameters.staking_unbonding_period() { - return Err(crate::wallet::Error::StakingFailed(format!( + return Err(WalletError::StakingFailed(format!( "staking period {staking_period} is less than the minimum {}", protocol_parameters.staking_unbonding_period() ))); @@ -95,7 +102,7 @@ where )) .finish_output()?; - let transaction = self.prepare_transaction([output], options).await?; + let transaction = self.prepare_send_outputs([output], options).await?; Ok(transaction) } diff --git a/sdk/src/wallet/operations/transaction/high_level/staking/end.rs b/sdk/src/wallet/operations/transaction/high_level/staking/end.rs index fd991ae8fa..4dd5ca0abf 100644 --- a/sdk/src/wallet/operations/transaction/high_level/staking/end.rs +++ b/sdk/src/wallet/operations/transaction/high_level/staking/end.rs @@ -2,49 +2,61 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{ + api::{options::TransactionOptions, PreparedTransactionData}, + secret::SecretManage, + ClientError, + }, types::block::output::{AccountId, AccountOutputBuilder}, - wallet::{types::TransactionWithMetadata, TransactionOptions, Wallet}, + wallet::{types::TransactionWithMetadata, Wallet, WalletError}, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { - pub async fn end_staking(&self, account_id: AccountId) -> crate::wallet::Result { - let prepared = self.prepare_end_staking(account_id).await?; + pub async fn end_staking( + &self, + account_id: AccountId, + options: impl Into> + Send, + ) -> Result { + let options = options.into(); + let prepared = self.prepare_end_staking(account_id, options.clone()).await?; - self.sign_and_submit_transaction(prepared, None).await + self.sign_and_submit_transaction(prepared, options).await } /// Prepares the transaction for [Wallet::end_staking()]. - pub async fn prepare_end_staking(&self, account_id: AccountId) -> crate::wallet::Result { + pub async fn prepare_end_staking( + &self, + account_id: AccountId, + options: impl Into> + Send, + ) -> Result { log::debug!("[TRANSACTION] prepare_end_staking"); let account_output_data = self - .data() + .ledger() .await .unspent_account_output(&account_id) .cloned() - .ok_or_else(|| crate::wallet::Error::AccountNotFound)?; + .ok_or_else(|| WalletError::AccountNotFound)?; let staking_feature = account_output_data .output .features() .and_then(|f| f.staking()) - .ok_or_else(|| crate::wallet::Error::StakingFailed(format!("account id {account_id} is not staking")))?; + .ok_or_else(|| WalletError::StakingFailed(format!("account id {account_id} is not staking")))?; let protocol_parameters = self.client().get_protocol_parameters().await?; let slot_commitment_id = self.client().get_issuance().await?.latest_commitment.id(); - let future_bounded_epoch = - protocol_parameters.epoch_index_of(protocol_parameters.future_bounded_slot(slot_commitment_id)); + let future_bounded_epoch = protocol_parameters.future_bounded_epoch(slot_commitment_id); if future_bounded_epoch <= staking_feature.end_epoch() { let end_epoch = protocol_parameters.epoch_index_of(slot_commitment_id.slot_index()) + (staking_feature.end_epoch() - future_bounded_epoch); - return Err(crate::wallet::Error::StakingFailed(format!( + return Err(WalletError::StakingFailed(format!( "account id {account_id} cannot end staking until {end_epoch}" ))); } @@ -62,12 +74,7 @@ where .with_features(features) .finish_output()?; - let options = TransactionOptions { - required_inputs: [account_output_data.output_id].into(), - ..Default::default() - }; - - let transaction = self.prepare_transaction([output], options).await?; + let transaction = self.prepare_send_outputs([output], options).await?; Ok(transaction) } diff --git a/sdk/src/wallet/operations/transaction/high_level/staking/extend.rs b/sdk/src/wallet/operations/transaction/high_level/staking/extend.rs index 535a3778c5..a4c8b9414f 100644 --- a/sdk/src/wallet/operations/transaction/high_level/staking/extend.rs +++ b/sdk/src/wallet/operations/transaction/high_level/staking/extend.rs @@ -2,24 +2,32 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, + client::{ + api::{options::TransactionOptions, PreparedTransactionData}, + secret::SecretManage, + ClientError, + }, types::block::output::{feature::StakingFeature, AccountId, AccountOutputBuilder}, - wallet::{types::TransactionWithMetadata, TransactionOptions, Wallet}, + wallet::{types::TransactionWithMetadata, Wallet, WalletError}, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { pub async fn extend_staking( &self, account_id: AccountId, additional_epochs: u32, - ) -> crate::wallet::Result { - let prepared = self.prepare_extend_staking(account_id, additional_epochs).await?; + options: impl Into> + Send, + ) -> Result { + let options = options.into(); + let prepared = self + .prepare_extend_staking(account_id, additional_epochs, options.clone()) + .await?; - self.sign_and_submit_transaction(prepared, None).await + self.sign_and_submit_transaction(prepared, options).await } /// Prepares the transaction for [Wallet::extend_staking()]. @@ -27,34 +35,32 @@ where &self, account_id: AccountId, additional_epochs: u32, - ) -> crate::wallet::Result { + options: impl Into> + Send, + ) -> Result { log::debug!("[TRANSACTION] prepare_extend_staking"); let account_output_data = self - .data() + .ledger() .await .unspent_account_output(&account_id) .cloned() - .ok_or_else(|| crate::wallet::Error::AccountNotFound)?; + .ok_or_else(|| WalletError::AccountNotFound)?; let protocol_parameters = self.client().get_protocol_parameters().await?; let slot_commitment_id = self.client().get_issuance().await?.latest_commitment.id(); - let future_bounded_epoch = - protocol_parameters.epoch_index_of(protocol_parameters.future_bounded_slot(slot_commitment_id)); + let future_bounded_epoch = protocol_parameters.future_bounded_epoch(slot_commitment_id); let staking_feature = account_output_data .output .features() .and_then(|f| f.staking()) - .ok_or_else(|| crate::wallet::Error::StakingFailed(format!("account id {account_id} is not staking")))?; + .ok_or_else(|| WalletError::StakingFailed(format!("account id {account_id} is not staking")))?; let mut output_builder = AccountOutputBuilder::from(account_output_data.output.as_account()).with_account_id(account_id); - let mut options = TransactionOptions::default(); - // Just extend the end epoch if it's still possible if future_bounded_epoch <= staking_feature.end_epoch() { output_builder = output_builder.replace_feature(StakingFeature::new( @@ -66,13 +72,12 @@ where // Otherwise, we'll have to claim the rewards } else { if additional_epochs < protocol_parameters.staking_unbonding_period() { - return Err(crate::wallet::Error::StakingFailed(format!( + return Err(WalletError::StakingFailed(format!( "new staking period {additional_epochs} is less than the minimum {}", protocol_parameters.staking_unbonding_period() ))); } - let past_bounded_epoch = - protocol_parameters.epoch_index_of(protocol_parameters.past_bounded_slot(slot_commitment_id)); + let past_bounded_epoch = protocol_parameters.past_bounded_epoch(slot_commitment_id); let end_epoch = past_bounded_epoch.saturating_add(additional_epochs); output_builder = output_builder.replace_feature(StakingFeature::new( staking_feature.staked_amount(), @@ -80,12 +85,11 @@ where past_bounded_epoch, end_epoch, )); - options.required_inputs = [account_output_data.output_id].into(); } let output = output_builder.finish_output()?; - let transaction = self.prepare_transaction([output], options).await?; + let transaction = self.prepare_send_outputs([output], options).await?; Ok(transaction) } diff --git a/sdk/src/wallet/operations/transaction/input_selection.rs b/sdk/src/wallet/operations/transaction/input_selection.rs deleted file mode 100644 index b7cdd895b2..0000000000 --- a/sdk/src/wallet/operations/transaction/input_selection.rs +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use alloc::collections::BTreeSet; -use std::collections::HashMap; - -#[cfg(feature = "events")] -use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent}; -use crate::{ - client::{ - api::{input_selection::InputSelection, transaction::validate_transaction_length, PreparedTransactionData}, - secret::{types::InputSigningData, SecretManage}, - }, - types::block::{ - output::{Output, OutputId}, - protocol::CommittableAgeRange, - slot::SlotIndex, - }, - wallet::{ - core::WalletData, operations::helpers::time::can_output_be_unlocked_forever_from_now_on, types::OutputData, - RemainderValueStrategy, TransactionOptions, Wallet, - }, -}; - -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ - /// Selects inputs for a transaction and locks them in the wallet, so they don't get used again - pub(crate) async fn select_inputs( - &self, - outputs: Vec, - mut options: TransactionOptions, - ) -> crate::wallet::Result { - log::debug!("[TRANSACTION] select_inputs"); - // Voting output needs to be requested before to prevent a deadlock - #[cfg(feature = "participation")] - let voting_output = self.get_voting_output().await?; - let protocol_parameters = self.client().get_protocol_parameters().await?; - let creation_slot = self.client().get_slot_index().await?; - let slot_commitment_id = self.client().get_issuance().await?.latest_commitment.id(); - if options.issuer_id.is_none() { - options.issuer_id = self.data().await.first_account_id(); - } - let reference_mana_cost = if let Some(issuer_id) = options.issuer_id { - Some( - self.client() - .get_account_congestion(&issuer_id, None) - .await? - .reference_mana_cost, - ) - } else { - None - }; - let remainder_address = match options.remainder_value_strategy { - RemainderValueStrategy::ReuseAddress => None, - RemainderValueStrategy::CustomAddress(address) => Some(address), - }; - // lock so the same inputs can't be selected in multiple transactions - let mut wallet_data = self.data_mut().await; - - #[cfg(feature = "events")] - self.emit(WalletEvent::TransactionProgress( - TransactionProgressEvent::SelectingInputs, - )) - .await; - - #[allow(unused_mut)] - let mut forbidden_inputs = wallet_data.locked_outputs.clone(); - - // Prevent consuming the voting output if not actually wanted - #[cfg(feature = "participation")] - if let Some(voting_output) = &voting_output { - if !options.required_inputs.contains(&voting_output.output_id) { - forbidden_inputs.insert(voting_output.output_id); - } - } - - // Filter inputs to not include inputs that require additional outputs for storage deposit return or could be - // still locked. - let available_outputs_signing_data = filter_inputs( - &wallet_data, - wallet_data.unspent_outputs.values(), - creation_slot, - protocol_parameters.committable_age_range(), - &options.required_inputs, - )?; - - let mut mana_rewards = HashMap::new(); - - if let Some(burn) = &options.burn { - for delegation_id in burn.delegations() { - if let Some(output) = wallet_data.unspent_delegation_output(delegation_id) { - mana_rewards.insert( - output.output_id, - self.client() - .get_output_mana_rewards(&output.output_id, slot_commitment_id.slot_index()) - .await? - .rewards, - ); - } - } - } - - // Check that no input got already locked - for output_id in &options.required_inputs { - if wallet_data.locked_outputs.contains(output_id) { - return Err(crate::wallet::Error::CustomInput(format!( - "provided custom input {output_id} is already used in another transaction", - ))); - } - if let Some(input) = wallet_data.outputs.get(output_id) { - if input.output.can_claim_rewards(outputs.iter().find(|o| { - input - .output - .chain_id() - .map(|chain_id| chain_id.or_from_output_id(output_id)) - == o.chain_id() - })) { - mana_rewards.insert( - *output_id, - self.client() - .get_output_mana_rewards(output_id, creation_slot) - .await? - .rewards, - ); - } - } - } - - let mut input_selection = InputSelection::new( - available_outputs_signing_data, - outputs, - Some(wallet_data.address.clone().into_inner()), - creation_slot, - slot_commitment_id, - protocol_parameters.clone(), - ) - .with_required_inputs(options.required_inputs) - .with_forbidden_inputs(forbidden_inputs) - .with_context_inputs(options.context_inputs) - .with_mana_rewards(mana_rewards) - .with_payload(options.tagged_data_payload) - .with_mana_allotments(options.mana_allotments) - .with_remainder_address(remainder_address) - .with_burn(options.burn); - - if let (Some(account_id), Some(reference_mana_cost)) = (options.issuer_id, reference_mana_cost) { - input_selection = input_selection.with_min_mana_allotment(account_id, reference_mana_cost); - } - - if !options.allow_additional_input_selection { - input_selection = input_selection.disable_additional_input_selection(); - } - - if let Some(capabilities) = options.capabilities { - input_selection = input_selection.with_transaction_capabilities(capabilities) - } - - let prepared_transaction_data = input_selection.select()?; - - validate_transaction_length(&prepared_transaction_data.transaction)?; - - // lock outputs so they don't get used by another transaction - for output in &prepared_transaction_data.inputs_data { - log::debug!("[TRANSACTION] locking: {}", output.output_id()); - wallet_data.locked_outputs.insert(*output.output_id()); - } - - Ok(prepared_transaction_data) - } -} - -/// Filter available outputs to only include outputs that can be unlocked forever from this moment. -/// Note: this is only for the default input selection, it's still possible to send these outputs by using -/// `claim_outputs` or providing their OutputId's in the custom_inputs -#[allow(clippy::too_many_arguments)] -fn filter_inputs<'a>( - wallet_data: &WalletData, - available_outputs: impl IntoIterator, - slot_index: impl Into + Copy, - committable_age_range: CommittableAgeRange, - required_inputs: &BTreeSet, -) -> crate::wallet::Result> { - let mut available_outputs_signing_data = Vec::new(); - - for output_data in available_outputs { - if !required_inputs.contains(&output_data.output_id) { - let output_can_be_unlocked_now_and_in_future = can_output_be_unlocked_forever_from_now_on( - // We use the addresses with unspent outputs, because other addresses of the - // account without unspent outputs can't be related to this output - &wallet_data.address.inner, - &output_data.output, - slot_index, - committable_age_range, - ); - - // Outputs that could get unlocked in the future will not be included - if !output_can_be_unlocked_now_and_in_future { - continue; - } - } - - if let Some(available_input) = output_data.input_signing_data(wallet_data, slot_index, committable_age_range)? { - available_outputs_signing_data.push(available_input); - } - } - - Ok(available_outputs_signing_data) -} diff --git a/sdk/src/wallet/operations/transaction/mod.rs b/sdk/src/wallet/operations/transaction/mod.rs index f75de0f89b..063180d48d 100644 --- a/sdk/src/wallet/operations/transaction/mod.rs +++ b/sdk/src/wallet/operations/transaction/mod.rs @@ -2,100 +2,54 @@ // SPDX-License-Identifier: Apache-2.0 pub(crate) mod account; +mod build_transaction; pub(crate) mod high_level; -mod input_selection; -mod options; pub(crate) mod prepare_output; -mod prepare_transaction; +mod send_outputs; mod sign_transaction; pub(crate) mod submit_transaction; -pub use self::options::{RemainderValueStrategy, TransactionOptions}; +#[cfg(feature = "storage")] +use crate::wallet::core::WalletLedgerDto; use crate::{ client::{ - api::{verify_semantic, PreparedTransactionData, SignedTransactionData}, - secret::{types::InputSigningData, SecretManage}, - Error, - }, - types::{ - api::core::OutputWithMetadataResponse, - block::{output::Output, payload::signed_transaction::SignedTransactionPayload}, + api::{options::TransactionOptions, PreparedTransactionData, SignedTransactionData}, + secret::SecretManage, + ClientError, }, + types::block::{output::OutputWithMetadata, payload::signed_transaction::SignedTransactionPayload}, wallet::{ types::{InclusionState, TransactionWithMetadata}, - Wallet, + Wallet, WalletError, }, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { - /// Sends a transaction by specifying its outputs. - /// - /// Note that, if sending a block fails, the method will return `None` for the block id, but the wallet - /// will reissue the transaction during syncing. - /// ```ignore - /// let outputs = [ - /// BasicOutputBuilder::new_with_amount(1_000_000)? - /// .add_unlock_condition(AddressUnlockCondition::new( - /// Address::try_from_bech32("rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu")?, - /// )) - /// .finish_output(account.client.get_token_supply().await?;)?, - /// ]; - /// let tx = account - /// .send_outputs( - /// outputs, - /// Some(TransactionOptions { - /// remainder_value_strategy: RemainderValueStrategy::ReuseAddress, - /// ..Default::default() - /// }), - /// ) - /// .await?; - /// println!("Transaction created: {}", tx.transaction_id); - /// if let Some(block_id) = tx.block_id { - /// println!("Block sent: {}", block_id); - /// } - /// ``` - pub async fn send_outputs( - &self, - outputs: impl Into> + Send, - options: impl Into> + Send, - ) -> crate::wallet::Result { - let outputs = outputs.into(); - let options = options.into(); - // here to check before syncing, how to prevent duplicated verification (also in prepare_transaction())? - // Checking it also here is good to return earlier if something is invalid - let protocol_parameters = self.client().get_protocol_parameters().await?; - - // Check if the outputs have enough amount to cover the storage deposit - for output in &outputs { - output.verify_storage_deposit(protocol_parameters.storage_score_parameters())?; - } - - let prepared_transaction_data = self.prepare_transaction(outputs, options.clone()).await?; - - self.sign_and_submit_transaction(prepared_transaction_data, options) - .await - } - /// Signs a transaction, submit it to a node and store it in the wallet pub async fn sign_and_submit_transaction( &self, prepared_transaction_data: PreparedTransactionData, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] sign_and_submit_transaction"); - let signed_transaction_data = match self.sign_transaction(&prepared_transaction_data).await { - Ok(res) => res, - Err(err) => { - // unlock outputs so they are available for a new transaction - self.unlock_inputs(&prepared_transaction_data.inputs_data).await?; - return Err(err); - } - }; + let wallet_ledger = self.ledger().await; + // check if inputs got already used by another transaction + for output in &prepared_transaction_data.inputs_data { + if wallet_ledger.locked_outputs.contains(output.output_id()) { + return Err(WalletError::CustomInput(format!( + "provided input {} is already used in another transaction", + output.output_id() + ))); + }; + } + drop(wallet_ledger); + + let signed_transaction_data = self.sign_transaction(&prepared_transaction_data).await?; self.submit_and_store_transaction(signed_transaction_data, options) .await @@ -106,7 +60,7 @@ where &self, signed_transaction_data: SignedTransactionData, options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!( "[TRANSACTION] submit_and_store_transaction {}", signed_transaction_data.payload.transaction().id() @@ -114,22 +68,22 @@ where let options = options.into(); // Validate transaction before sending and storing it - let conflict = verify_semantic( - &signed_transaction_data.inputs_data, - &signed_transaction_data.payload, - signed_transaction_data.mana_rewards, - self.client().get_protocol_parameters().await?, - )?; - - if let Some(conflict) = conflict { + if let Err(conflict) = signed_transaction_data.verify_semantic(&self.client().get_protocol_parameters().await?) + { log::debug!( "[TRANSACTION] conflict: {conflict:?} for {:?}", signed_transaction_data.payload ); - // unlock outputs so they are available for a new transaction - self.unlock_inputs(&signed_transaction_data.inputs_data).await?; - return Err(Error::TransactionSemantic(conflict).into()); + return Err(ClientError::TransactionSemantic(conflict).into()); + } + + let mut wallet_ledger = self.ledger_mut().await; + // lock outputs so they don't get used by another transaction + for output in &signed_transaction_data.inputs_data { + log::debug!("[TRANSACTION] locking: {}", output.output_id()); + wallet_ledger.locked_outputs.insert(*output.output_id()); } + drop(wallet_ledger); // Ignore errors from sending, we will try to send it again during [`sync_pending_transactions`] let block_id = match self @@ -154,7 +108,7 @@ where let inputs = signed_transaction_data .inputs_data .into_iter() - .map(|input| OutputWithMetadataResponse { + .map(|input| OutputWithMetadata { metadata: input.output_metadata, output: input.output, }) @@ -172,32 +126,20 @@ where inputs, }; - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; - wallet_data.transactions.insert(transaction_id, transaction.clone()); - wallet_data.pending_transactions.insert(transaction_id); + wallet_ledger.transactions.insert(transaction_id, transaction.clone()); + wallet_ledger.pending_transactions.insert(transaction_id); #[cfg(feature = "storage")] { // TODO: maybe better to use the wallet address as identifier now? - log::debug!("[TRANSACTION] storing wallet"); - self.storage_manager().save_wallet_data(&wallet_data).await?; + log::debug!("[TRANSACTION] storing wallet ledger"); + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*wallet_ledger)) + .await?; } Ok(transaction) } - - // unlock outputs - async fn unlock_inputs(&self, inputs: &[InputSigningData]) -> crate::wallet::Result<()> { - let mut wallet_data = self.data_mut().await; - for input_signing_data in inputs { - let output_id = input_signing_data.output_id(); - wallet_data.locked_outputs.remove(output_id); - log::debug!( - "[TRANSACTION] Unlocked output {} because of transaction error", - output_id - ); - } - Ok(()) - } } diff --git a/sdk/src/wallet/operations/transaction/prepare_output.rs b/sdk/src/wallet/operations/transaction/prepare_output.rs index 75da29a3a7..21b7694461 100644 --- a/sdk/src/wallet/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/operations/transaction/prepare_output.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - client::secret::SecretManage, + client::{api::options::RemainderValueStrategy, secret::SecretManage, ClientError}, types::block::{ address::{Address, Bech32Address, Ed25519Address}, output::{ @@ -17,21 +17,13 @@ use crate::{ StorageScoreParameters, UnlockCondition, }, slot::SlotIndex, - Error, + BlockError, }, utils::serde::string, - wallet::{ - operations::transaction::{RemainderValueStrategy, TransactionOptions}, - types::OutputData, - Wallet, - }, + wallet::{operations::transaction::TransactionOptions, types::OutputData, Wallet, WalletError}, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ +impl Wallet { /// Prepare a basic or NFT output for sending /// If the amount is below the minimum required storage deposit, by default the remaining amount will automatically /// be added with a StorageDepositReturn UnlockCondition, when setting the ReturnStrategy to `gift`, the full @@ -42,7 +34,7 @@ where &self, params: OutputParams, transaction_options: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[OUTPUT] prepare_output {params:?}"); let transaction_options = transaction_options.into(); @@ -59,7 +51,7 @@ where if let Some(features) = params.features { if let Some(tag) = features.tag { first_output_builder = first_output_builder.add_feature(TagFeature::new( - prefix_hex::decode::>(tag).map_err(|_| Error::InvalidField("tag"))?, + prefix_hex::decode::>(tag).map_err(ClientError::PrefixHex)?, )?); } @@ -73,7 +65,7 @@ where if let Some(issuer) = features.issuer { if let OutputBuilder::Basic(_) = first_output_builder { - return Err(crate::wallet::Error::MissingParameter("nft_id")); + return Err(WalletError::MissingParameter("nft_id")); } first_output_builder = first_output_builder.add_immutable_feature(IssuerFeature::new(issuer)); } @@ -113,7 +105,7 @@ where let min_amount_basic_output = BasicOutput::minimum_amount(&Address::from(Ed25519Address::null()), storage_score_params); - let min_required_storage_deposit = first_output.minimum_amount(storage_score_params); + let min_required_storage_deposit = first_output.amount(); if params.amount > min_required_storage_deposit { second_output_builder = second_output_builder.with_amount(params.amount); @@ -177,7 +169,7 @@ where } if final_amount > available_base_coin { - return Err(crate::wallet::Error::InsufficientFunds { + return Err(WalletError::InsufficientFunds { available: available_base_coin, required: final_amount, }); @@ -216,7 +208,7 @@ where } } else { // Would leave dust behind, so return what's required for a remainder - return Err(crate::wallet::Error::InsufficientFunds { + return Err(WalletError::InsufficientFunds { available: available_base_coin, required: available_base_coin + min_amount_basic_output - remaining_balance, }); @@ -233,7 +225,7 @@ where recipient_address: Bech32Address, nft_id: Option, params: StorageScoreParameters, - ) -> crate::wallet::Result<(OutputBuilder, Option)> { + ) -> Result<(OutputBuilder, Option), WalletError> { let (mut first_output_builder, existing_nft_output_data) = if let Some(nft_id) = &nft_id { if nft_id.is_null() { // Mint a new NFT output @@ -243,14 +235,14 @@ where ) } else { // Transition an existing NFT output - let unspent_nft_output = self.data().await.unspent_nft_output(nft_id).cloned(); + let unspent_nft_output = self.ledger().await.unspent_nft_output(nft_id).cloned(); // Find nft output from the inputs let mut first_output_builder = if let Some(nft_output_data) = &unspent_nft_output { let nft_output = nft_output_data.output.as_nft(); NftOutputBuilder::from(nft_output).with_nft_id(*nft_id) } else { - return Err(crate::wallet::Error::NftNotFoundInUnspentOutputs); + return Err(WalletError::NftNotFoundInUnspentOutputs); }; // Remove potentially existing features and unlock conditions. first_output_builder = first_output_builder.clear_features(); @@ -274,7 +266,7 @@ where async fn get_remainder_address( &self, transaction_options: impl Into> + Send, - ) -> crate::wallet::Result
{ + ) -> Result { let transaction_options = transaction_options.into(); Ok(if let Some(options) = &transaction_options { @@ -421,10 +413,10 @@ impl OutputBuilder { self } - fn finish_output(self) -> Result { - match self { - Self::Basic(b) => b.finish_output(), - Self::Nft(b) => b.finish_output(), - } + fn finish_output(self) -> Result { + Ok(match self { + Self::Basic(b) => b.finish_output()?, + Self::Nft(b) => b.finish_output()?, + }) } } diff --git a/sdk/src/wallet/operations/transaction/prepare_transaction.rs b/sdk/src/wallet/operations/transaction/prepare_transaction.rs deleted file mode 100644 index 54113ac0e7..0000000000 --- a/sdk/src/wallet/operations/transaction/prepare_transaction.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2024 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use instant::Instant; -use packable::bounded::TryIntoBoundedU16Error; - -use crate::{ - client::{api::PreparedTransactionData, secret::SecretManage}, - types::block::{input::INPUT_COUNT_MAX, output::Output}, - wallet::{operations::transaction::TransactionOptions, Wallet}, -}; - -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ - /// Get inputs and build the transaction - pub async fn prepare_transaction( - &self, - outputs: impl Into> + Send, - options: impl Into> + Send, - ) -> crate::wallet::Result { - log::debug!("[TRANSACTION] prepare_transaction"); - let options = options.into().unwrap_or_default(); - let outputs = outputs.into(); - let prepare_transaction_start_time = Instant::now(); - let storage_score_params = self.client().get_storage_score_parameters().await?; - - // Check if the outputs have enough amount to cover the storage deposit - for output in &outputs { - output.verify_storage_deposit(storage_score_params)?; - } - - if options.required_inputs.len() as u16 > INPUT_COUNT_MAX { - return Err(crate::types::block::Error::InvalidInputCount( - TryIntoBoundedU16Error::Truncated(options.required_inputs.len()), - ))?; - } - - let prepared_transaction_data = self.select_inputs(outputs, options).await?; - - log::debug!( - "[TRANSACTION] finished prepare_transaction in {:.2?}", - prepare_transaction_start_time.elapsed() - ); - Ok(prepared_transaction_data) - } -} diff --git a/sdk/src/wallet/operations/transaction/send_outputs.rs b/sdk/src/wallet/operations/transaction/send_outputs.rs new file mode 100644 index 0000000000..1cfb01f5ac --- /dev/null +++ b/sdk/src/wallet/operations/transaction/send_outputs.rs @@ -0,0 +1,97 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use instant::Instant; +use packable::bounded::TryIntoBoundedU16Error; + +use crate::{ + client::{api::PreparedTransactionData, secret::SecretManage, ClientError}, + types::block::{input::INPUT_COUNT_MAX, output::Output, payload::PayloadError}, + wallet::{operations::transaction::TransactionOptions, types::TransactionWithMetadata, Wallet, WalletError}, +}; + +impl Wallet +where + WalletError: From, + ClientError: From, +{ + /// Sends a transaction by specifying its outputs. + /// + /// Note that, if sending a block fails, the method will return `None` for the block id, but the wallet + /// will reissue the transaction during syncing. + /// ```ignore + /// let outputs = [ + /// BasicOutputBuilder::new_with_amount(1_000_000)? + /// .add_unlock_condition(AddressUnlockCondition::new( + /// Address::try_from_bech32("rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu")?, + /// )) + /// .finish_output(account.client.get_token_supply().await?;)?, + /// ]; + /// let tx = account + /// .send_outputs( + /// outputs, + /// Some(TransactionOptions { + /// remainder_value_strategy: RemainderValueStrategy::ReuseAddress, + /// ..Default::default() + /// }), + /// ) + /// .await?; + /// println!("Transaction created: {}", tx.transaction_id); + /// if let Some(block_id) = tx.block_id { + /// println!("Block sent: {}", block_id); + /// } + /// ``` + pub async fn send_outputs( + &self, + outputs: impl Into> + Send, + options: impl Into> + Send, + ) -> Result { + let outputs = outputs.into(); + let options = options.into(); + // here to check before syncing, how to prevent duplicated verification (also in prepare_send_outputs())? + // Checking it also here is good to return earlier if something is invalid + let protocol_parameters = self.client().get_protocol_parameters().await?; + + // Check if the outputs have enough amount to cover the storage deposit + for output in &outputs { + output.verify_storage_deposit(protocol_parameters.storage_score_parameters())?; + } + + let prepared_transaction_data = self.prepare_send_outputs(outputs, options.clone()).await?; + + self.sign_and_submit_transaction(prepared_transaction_data, options) + .await + } + + /// Get inputs and build the transaction + pub async fn prepare_send_outputs( + &self, + outputs: impl Into> + Send, + options: impl Into> + Send, + ) -> Result { + log::debug!("[TRANSACTION] prepare_send_outputs"); + let options = options.into().unwrap_or_default(); + let outputs = outputs.into(); + let prepare_send_outputs_start_time = Instant::now(); + let storage_score_params = self.client().get_storage_score_parameters().await?; + + // Check if the outputs have enough amount to cover the storage deposit + for output in &outputs { + output.verify_storage_deposit(storage_score_params)?; + } + + if options.required_inputs.len() as u16 > INPUT_COUNT_MAX { + return Err(PayloadError::InputCount(TryIntoBoundedU16Error::Truncated( + options.required_inputs.len(), + )))?; + } + + let prepared_transaction_data = self.build_transaction(outputs, options).await?; + + log::debug!( + "[TRANSACTION] finished prepare_send_outputs in {:.2?}", + prepare_send_outputs_start_time.elapsed() + ); + Ok(prepared_transaction_data) + } +} diff --git a/sdk/src/wallet/operations/transaction/sign_transaction.rs b/sdk/src/wallet/operations/transaction/sign_transaction.rs index dfdee16c5b..86f9bddee1 100644 --- a/sdk/src/wallet/operations/transaction/sign_transaction.rs +++ b/sdk/src/wallet/operations/transaction/sign_transaction.rs @@ -14,24 +14,23 @@ use { use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent}; use crate::{ client::{ - api::{ - transaction::validate_signed_transaction_payload_length, PreparedTransactionData, SignedTransactionData, - }, + api::{PreparedTransactionData, SignedTransactionData}, secret::SecretManage, + ClientError, }, - wallet::{operations::transaction::SignedTransactionPayload, Wallet}, + wallet::{operations::transaction::SignedTransactionPayload, Wallet, WalletError}, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Signs a transaction. pub async fn sign_transaction( &self, prepared_transaction_data: &PreparedTransactionData, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] sign_transaction"); log::debug!("[TRANSACTION] prepared_transaction_data {prepared_transaction_data:?}"); #[cfg(feature = "events")] @@ -75,25 +74,17 @@ where } let protocol_parameters = self.client().get_protocol_parameters().await?; - let unlocks = match self + let unlocks = self .secret_manager .read() .await .transaction_unlocks(prepared_transaction_data, &protocol_parameters) - .await - { - Ok(res) => res, - Err(err) => { - // unlock outputs so they are available for a new transaction - self.unlock_inputs(&prepared_transaction_data.inputs_data).await?; - return Err(err.into()); - } - }; + .await?; let payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; log::debug!("[TRANSACTION] signed transaction: {:?}", payload); - validate_signed_transaction_payload_length(&payload)?; + payload.validate_length()?; Ok(SignedTransactionData { payload, diff --git a/sdk/src/wallet/operations/transaction/submit_transaction.rs b/sdk/src/wallet/operations/transaction/submit_transaction.rs index 0da286d2ee..994299045a 100644 --- a/sdk/src/wallet/operations/transaction/submit_transaction.rs +++ b/sdk/src/wallet/operations/transaction/submit_transaction.rs @@ -1,31 +1,25 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "events")] -use crate::wallet::events::types::{TransactionProgressEvent, WalletEvent}; use crate::{ - client::secret::SecretManage, + client::{secret::SecretManage, ClientError}, types::block::{output::AccountId, payload::Payload, BlockId}, - wallet::{operations::transaction::SignedTransactionPayload, Wallet}, + wallet::{operations::transaction::SignedTransactionPayload, Wallet, WalletError}, }; impl Wallet where - crate::wallet::Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Submits a signed transaction in a block. pub(crate) async fn submit_signed_transaction( &self, payload: SignedTransactionPayload, issuer_id: impl Into> + Send, - ) -> crate::wallet::Result { + ) -> Result { log::debug!("[TRANSACTION] submit_signed_transaction"); - #[cfg(feature = "events")] - self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting)) - .await; - self.submit_basic_block(Some(Payload::from(payload)), issuer_id, true) .await } diff --git a/sdk/src/wallet/operations/wait_for_tx_acceptance.rs b/sdk/src/wallet/operations/wait_for_tx_acceptance.rs index 8cd17ac7f8..5ee404a8a1 100644 --- a/sdk/src/wallet/operations/wait_for_tx_acceptance.rs +++ b/sdk/src/wallet/operations/wait_for_tx_acceptance.rs @@ -1,51 +1,39 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::time::Duration; - use crate::{ - client::{secret::SecretManage, Error as ClientError}, - types::{ - api::core::TransactionState, - block::{payload::signed_transaction::TransactionId, BlockId}, - }, - wallet::{types::InclusionState, Error, Wallet}, + client::{secret::SecretManage, ClientError}, + types::block::payload::signed_transaction::TransactionId, + wallet::{types::InclusionState, Wallet, WalletError}, }; -const DEFAULT_WAIT_FOR_TX_ACCEPTANCE_INTERVAL: Duration = Duration::from_millis(500); -const DEFAULT_WAIT_FOR_TX_ACCEPTANCE_MAX_ATTEMPTS: u64 = 80; - impl Wallet where - Error: From, - crate::client::Error: From, + WalletError: From, + ClientError: From, { /// Checks the transaction state for a provided transaction id until it's accepted. Interval in milliseconds. - /// Returns the block id that contains this transaction. pub async fn wait_for_transaction_acceptance( &self, transaction_id: &TransactionId, interval: Option, max_attempts: Option, - ) -> crate::wallet::Result { + ) -> Result<(), WalletError> { log::debug!("[wait_for_transaction_acceptance]"); let transaction = self - .data() + .ledger() .await .transactions .get(transaction_id) .cloned() - .ok_or_else(|| Error::TransactionNotFound(*transaction_id))?; + .ok_or_else(|| WalletError::TransactionNotFound(*transaction_id))?; if matches!( transaction.inclusion_state, InclusionState::Accepted | InclusionState::Confirmed | InclusionState::Finalized ) { - return Ok(transaction - .block_id - // Safe to unwrap, we always set the block id together with any of these inclusion states. - .expect("missing block id in TransactionWithMetadata")); + return Ok(()); } if matches!( @@ -59,45 +47,10 @@ where .into()); } - let block_id = transaction - .block_id - .ok_or_else(|| ClientError::TransactionAcceptance(transaction_id.to_string()))?; + self.client() + .wait_for_transaction_acceptance(transaction_id, interval, max_attempts) + .await?; - let duration = interval - .map(std::time::Duration::from_millis) - .unwrap_or(DEFAULT_WAIT_FOR_TX_ACCEPTANCE_INTERVAL); - for _ in 0..max_attempts.unwrap_or(DEFAULT_WAIT_FOR_TX_ACCEPTANCE_MAX_ATTEMPTS) { - let block_metadata = self.client().get_block_metadata(&block_id).await?; - if let Some(transaction_state) = block_metadata.transaction_metadata.map(|m| m.transaction_state) { - match transaction_state { - TransactionState::Accepted | TransactionState::Confirmed | TransactionState::Finalized => { - return Ok(block_id); - } - TransactionState::Failed => { - // Check if the transaction got reissued in another block and confirmed there - let included_block_metadata = self - .client() - .get_included_block_metadata(transaction_id) - .await - .map_err(|e| { - if matches!(e, ClientError::Node(crate::client::node_api::error::Error::NotFound(_))) { - // If no block was found with this transaction id, then it couldn't get accepted - ClientError::TransactionAcceptance(transaction_id.to_string()) - } else { - e - } - })?; - return Ok(included_block_metadata.block_id); - } - TransactionState::Pending => {} // Just need to wait longer - }; - } - - #[cfg(target_family = "wasm")] - gloo_timers::future::TimeoutFuture::new(duration.as_millis() as u32).await; - #[cfg(not(target_family = "wasm"))] - tokio::time::sleep(duration).await; - } - Err(ClientError::TransactionAcceptance(transaction_id.to_string()).into()) + Ok(()) } } diff --git a/sdk/src/wallet/storage/adapter/memory.rs b/sdk/src/wallet/storage/adapter/memory.rs index c1d04fabc8..e4b89a930d 100644 --- a/sdk/src/wallet/storage/adapter/memory.rs +++ b/sdk/src/wallet/storage/adapter/memory.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use tokio::sync::RwLock; -use crate::client::storage::StorageAdapter; +use crate::{client::storage::StorageAdapter, wallet::WalletError}; /// A storage adapter that stores data in memory. #[derive(Debug, Default)] @@ -14,18 +14,18 @@ pub struct Memory(Arc>>>); #[async_trait::async_trait] impl StorageAdapter for Memory { - type Error = crate::wallet::Error; + type Error = WalletError; - async fn get_bytes(&self, key: &str) -> crate::wallet::Result>> { + async fn get_bytes(&self, key: &str) -> Result>, WalletError> { Ok(self.0.read().await.get(key).cloned()) } - async fn set_bytes(&self, key: &str, record: &[u8]) -> crate::wallet::Result<()> { + async fn set_bytes(&self, key: &str, record: &[u8]) -> Result<(), WalletError> { self.0.write().await.insert(key.to_string(), record.to_owned()); Ok(()) } - async fn delete(&self, key: &str) -> crate::wallet::Result<()> { + async fn delete(&self, key: &str) -> Result<(), WalletError> { self.0.write().await.remove(key); Ok(()) } diff --git a/sdk/src/wallet/storage/adapter/mod.rs b/sdk/src/wallet/storage/adapter/mod.rs index 06f7547e9a..8bbf7951f3 100644 --- a/sdk/src/wallet/storage/adapter/mod.rs +++ b/sdk/src/wallet/storage/adapter/mod.rs @@ -9,39 +9,39 @@ pub mod rocksdb; use async_trait::async_trait; -use crate::client::storage::StorageAdapter; +use crate::{client::storage::StorageAdapter, wallet::WalletError}; #[async_trait] pub(crate) trait DynStorageAdapter: std::fmt::Debug + Send + Sync { - async fn dyn_get_bytes(&self, key: &str) -> crate::wallet::Result>>; + async fn dyn_get_bytes(&self, key: &str) -> Result>, WalletError>; - async fn dyn_set_bytes(&self, key: &str, record: &[u8]) -> crate::wallet::Result<()>; + async fn dyn_set_bytes(&self, key: &str, record: &[u8]) -> Result<(), WalletError>; /// Removes a record from the storage. - async fn dyn_delete(&self, key: &str) -> crate::wallet::Result<()>; + async fn dyn_delete(&self, key: &str) -> Result<(), WalletError>; } #[async_trait] impl DynStorageAdapter for T where - crate::wallet::Error: From, + WalletError: From, { - async fn dyn_get_bytes(&self, key: &str) -> crate::wallet::Result>> { + async fn dyn_get_bytes(&self, key: &str) -> Result>, WalletError> { Ok(self.get_bytes(key).await?) } - async fn dyn_set_bytes(&self, key: &str, record: &[u8]) -> crate::wallet::Result<()> { + async fn dyn_set_bytes(&self, key: &str, record: &[u8]) -> Result<(), WalletError> { Ok(self.set_bytes(key, record).await?) } - async fn dyn_delete(&self, key: &str) -> crate::wallet::Result<()> { + async fn dyn_delete(&self, key: &str) -> Result<(), WalletError> { Ok(self.delete(key).await?) } } #[async_trait] impl StorageAdapter for dyn DynStorageAdapter { - type Error = crate::wallet::Error; + type Error = WalletError; async fn get_bytes(&self, key: &str) -> Result>, Self::Error> { self.dyn_get_bytes(key).await diff --git a/sdk/src/wallet/storage/adapter/rocksdb.rs b/sdk/src/wallet/storage/adapter/rocksdb.rs index 641fa12c74..4cd9d13a2d 100644 --- a/sdk/src/wallet/storage/adapter/rocksdb.rs +++ b/sdk/src/wallet/storage/adapter/rocksdb.rs @@ -6,7 +6,7 @@ use std::{path::Path, sync::Arc}; use rocksdb::{DBCompressionType, Options, DB}; use tokio::sync::Mutex; -use crate::client::storage::StorageAdapter; +use crate::{client::storage::StorageAdapter, wallet::WalletError}; /// Key value storage adapter. #[derive(Clone, Debug)] @@ -16,7 +16,7 @@ pub struct RocksdbStorageAdapter { impl RocksdbStorageAdapter { /// Initialises the storage adapter. - pub fn new(path: impl AsRef) -> crate::wallet::Result { + pub fn new(path: impl AsRef) -> Result { let mut opts = Options::default(); opts.set_compression_type(DBCompressionType::Lz4); opts.create_if_missing(true); @@ -30,18 +30,18 @@ impl RocksdbStorageAdapter { #[async_trait::async_trait] impl StorageAdapter for RocksdbStorageAdapter { - type Error = crate::wallet::Error; + type Error = WalletError; - async fn get_bytes(&self, key: &str) -> crate::wallet::Result>> { + async fn get_bytes(&self, key: &str) -> Result>, WalletError> { Ok(self.db.lock().await.get(key)?) } - async fn set_bytes(&self, key: &str, record: &[u8]) -> crate::wallet::Result<()> { + async fn set_bytes(&self, key: &str, record: &[u8]) -> Result<(), WalletError> { self.db.lock().await.put(key, record)?; Ok(()) } - async fn delete(&self, key: &str) -> crate::wallet::Result<()> { + async fn delete(&self, key: &str) -> Result<(), WalletError> { self.db.lock().await.delete(key)?; Ok(()) } diff --git a/sdk/src/wallet/storage/adapter/wasm.rs b/sdk/src/wallet/storage/adapter/wasm.rs index 9716dad466..0db66bc812 100644 --- a/sdk/src/wallet/storage/adapter/wasm.rs +++ b/sdk/src/wallet/storage/adapter/wasm.rs @@ -14,7 +14,7 @@ pub struct WasmAdapter(LocalStorage); impl WasmAdapter { /// Initialises the storage adapter. - pub fn new() -> crate::wallet::Result { + pub fn new() -> Result { Ok(Self(LocalStorage::new())) } } @@ -22,23 +22,23 @@ impl WasmAdapter { #[async_trait::async_trait] impl StorageAdapter for WasmAdapter { /// Gets the record associated with the given key from the storage. - async fn get(&self, key: &str) -> crate::wallet::Result { + async fn get(&self, key: &str) -> Result { self.0.get(key) } /// Saves or updates a record on the storage. - async fn set(&mut self, key: &str, record: String) -> crate::wallet::Result<()> { + async fn set(&mut self, key: &str, record: String) -> Result<(), WalletError> { self.0.set(key, record) } /// Batch writes records to the storage. - async fn batch_set(&mut self, records: HashMap) -> crate::wallet::Result<()> { + async fn batch_set(&mut self, records: HashMap) -> Result<(), WalletError> { records.into_iter().map(|s| self.set(s.0, s.1)); Ok(()) } /// Removes a record from the storage. - async fn remove(&mut self, key: &str) -> crate::wallet::Result<()> { + async fn remove(&mut self, key: &str) -> Result<(), WalletError> { self.0.delete(key) } } diff --git a/sdk/src/wallet/storage/constants.rs b/sdk/src/wallet/storage/constants.rs index 3891fecf14..c30d664764 100644 --- a/sdk/src/wallet/storage/constants.rs +++ b/sdk/src/wallet/storage/constants.rs @@ -15,15 +15,17 @@ pub const fn default_storage_path() -> &'static str { DEFAULT_STORAGE_PATH } +// wallet db schema pub(crate) const DATABASE_SCHEMA_VERSION: u8 = 1; pub(crate) const DATABASE_SCHEMA_VERSION_KEY: &str = "database-schema-version"; -pub(crate) const WALLET_DATA_KEY: &str = "wallet-data"; +// wallet db keys +pub(crate) const WALLET_LEDGER_KEY: &str = "wallet-ledger"; pub(crate) const WALLET_BUILDER_KEY: &str = "wallet-builder"; -pub(crate) const WALLET_SYNC_OPTIONS: &str = "wallet-sync-options"; - pub(crate) const SECRET_MANAGER_KEY: &str = "secret-manager"; +pub(crate) const WALLET_SYNC_OPTIONS: &str = "wallet-sync-options"; + // #[cfg(feature = "participation")] // pub(crate) const PARTICIPATION_EVENTS: &str = "participation-events"; // #[cfg(feature = "participation")] diff --git a/sdk/src/wallet/storage/manager.rs b/sdk/src/wallet/storage/manager.rs index ec217e5365..65f1a32f26 100644 --- a/sdk/src/wallet/storage/manager.rs +++ b/sdk/src/wallet/storage/manager.rs @@ -7,10 +7,11 @@ use crate::{ client::storage::StorageAdapter, types::TryFromDto, wallet::{ - core::{WalletData, WalletDataDto}, + core::{WalletLedger, WalletLedgerDto}, migration::migrate, operations::syncing::SyncOptions, storage::{constants::*, DynStorageAdapter, Storage}, + WalletError, }, }; @@ -24,7 +25,7 @@ impl StorageManager { pub(crate) async fn new( storage: impl DynStorageAdapter + 'static, encryption_key: impl Into>> + Send, - ) -> crate::wallet::Result { + ) -> Result { let storage = Storage { inner: Box::new(storage) as _, encryption_key: encryption_key.into(), @@ -34,7 +35,7 @@ impl StorageManager { // Get the db version or set it if let Some(db_schema_version) = storage.get::(DATABASE_SCHEMA_VERSION_KEY).await? { if db_schema_version != DATABASE_SCHEMA_VERSION { - return Err(crate::wallet::Error::Storage(format!( + return Err(WalletError::Storage(format!( "unsupported database schema version {db_schema_version}" ))); } @@ -49,32 +50,33 @@ impl StorageManager { Ok(storage_manager) } - pub(crate) async fn load_wallet_data(&self) -> crate::wallet::Result> { - if let Some(dto) = self.get::(WALLET_DATA_KEY).await? { - Ok(Some(WalletData::try_from_dto(dto)?)) + pub(crate) async fn save_wallet_ledger(&self, wallet_ledger: &WalletLedgerDto) -> Result<(), WalletError> { + self.set(WALLET_LEDGER_KEY, wallet_ledger).await?; + Ok(()) + } + + pub(crate) async fn load_wallet_ledger(&self) -> Result, WalletError> { + if let Some(dto) = self.get::(WALLET_LEDGER_KEY).await? { + Ok(Some(WalletLedger::try_from_dto(dto)?)) } else { Ok(None) } } - pub(crate) async fn save_wallet_data(&self, wallet_data: &WalletData) -> crate::wallet::Result<()> { - self.set(WALLET_DATA_KEY, &WalletDataDto::from(wallet_data)).await - } - - pub(crate) async fn set_default_sync_options(&self, sync_options: &SyncOptions) -> crate::wallet::Result<()> { - let key = format!("{WALLET_DATA_KEY}-{WALLET_SYNC_OPTIONS}"); + pub(crate) async fn set_default_sync_options(&self, sync_options: &SyncOptions) -> Result<(), WalletError> { + let key = format!("{WALLET_LEDGER_KEY}-{WALLET_SYNC_OPTIONS}"); self.set(&key, &sync_options).await } - pub(crate) async fn get_default_sync_options(&self) -> crate::wallet::Result> { - let key = format!("{WALLET_DATA_KEY}-{WALLET_SYNC_OPTIONS}"); + pub(crate) async fn get_default_sync_options(&self) -> Result, WalletError> { + let key = format!("{WALLET_LEDGER_KEY}-{WALLET_SYNC_OPTIONS}"); self.get(&key).await } } #[async_trait::async_trait] impl StorageAdapter for StorageManager { - type Error = crate::wallet::Error; + type Error = WalletError; async fn get_bytes(&self, key: &str) -> Result>, Self::Error> { self.storage.get_bytes(key).await @@ -91,12 +93,14 @@ impl StorageAdapter for StorageManager { #[cfg(test)] mod tests { + use crypto::keys::bip44::Bip44; use pretty_assertions::assert_eq; use serde::{Deserialize, Serialize}; use super::*; use crate::{ - client::secret::SecretManager, + client::{constants::SHIMMER_COIN_TYPE, secret::SecretManager}, + types::block::address::{Bech32Address, Ed25519Address}, wallet::{core::operations::storage::SaveLoadWallet, storage::adapter::memory::Memory, WalletBuilder}, }; @@ -122,15 +126,18 @@ mod tests { } #[tokio::test] - async fn save_load_wallet_data() { + async fn save_load_wallet_ledger() { let storage_manager = StorageManager::new(Memory::default(), None).await.unwrap(); - assert!(storage_manager.load_wallet_data().await.unwrap().is_none()); + assert!(storage_manager.load_wallet_ledger().await.unwrap().is_none()); - let wallet_data = WalletData::mock(); + let wallet_ledger = WalletLedger::test_instance(); - storage_manager.save_wallet_data(&wallet_data).await.unwrap(); - let wallet = storage_manager.load_wallet_data().await.unwrap(); - assert!(matches!(wallet, Some(data) if data.alias == Some("Alice".to_string()))); + storage_manager + .save_wallet_ledger(&WalletLedgerDto::from(&wallet_ledger)) + .await + .unwrap(); + + assert_eq!(storage_manager.load_wallet_ledger().await.unwrap(), Some(wallet_ledger)); } #[tokio::test] @@ -143,14 +150,24 @@ mod tests { .is_none() ); - let wallet_builder = WalletBuilder::::new(); + let wallet_address = Bech32Address::new("rms".parse().unwrap(), Ed25519Address::null()); + let wallet_bip_path = Bip44::new(SHIMMER_COIN_TYPE); + let wallet_alias = "savings".to_string(); + + let wallet_builder = WalletBuilder::::new() + .with_address(wallet_address.clone()) + .with_bip_path(wallet_bip_path) + .with_alias(wallet_alias.clone()); + wallet_builder.save(&storage_manager).await.unwrap(); - assert!( - WalletBuilder::::load(&storage_manager) - .await - .unwrap() - .is_some() - ); + let restored_wallet_builder = WalletBuilder::::load(&storage_manager) + .await + .unwrap() + .unwrap(); + + assert_eq!(restored_wallet_builder.address, Some(wallet_address)); + assert_eq!(restored_wallet_builder.bip_path, Some(wallet_bip_path)); + assert_eq!(restored_wallet_builder.alias, Some(wallet_alias)); } } diff --git a/sdk/src/wallet/storage/mod.rs b/sdk/src/wallet/storage/mod.rs index d467f8bb2e..d5af5840e0 100644 --- a/sdk/src/wallet/storage/mod.rs +++ b/sdk/src/wallet/storage/mod.rs @@ -23,7 +23,7 @@ use zeroize::Zeroizing; use self::adapter::DynStorageAdapter; pub(crate) use self::manager::StorageManager; pub use self::{kind::StorageKind, options::StorageOptions}; -use crate::client::storage::StorageAdapter; +use crate::{client::storage::StorageAdapter, wallet::WalletError}; #[derive(Debug)] pub struct Storage { @@ -33,7 +33,7 @@ pub struct Storage { #[async_trait] impl StorageAdapter for Storage { - type Error = crate::wallet::Error; + type Error = WalletError; async fn get_bytes(&self, key: &str) -> Result>, Self::Error> { match self.inner.as_ref().get_bytes(key).await? { diff --git a/sdk/src/wallet/storage/participation.rs b/sdk/src/wallet/storage/participation.rs index e529cca4e5..65063eb37f 100644 --- a/sdk/src/wallet/storage/participation.rs +++ b/sdk/src/wallet/storage/participation.rs @@ -20,7 +20,7 @@ impl StorageManager { pub(crate) async fn insert_participation_event( &self, event_with_nodes: ParticipationEventWithNodes, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { log::debug!("insert_participation_event {}", event_with_nodes.id); let mut events = self @@ -36,7 +36,7 @@ impl StorageManager { Ok(()) } - pub(crate) async fn remove_participation_event(&self, id: &ParticipationEventId) -> crate::wallet::Result<()> { + pub(crate) async fn remove_participation_event(&self, id: &ParticipationEventId) -> Result<(), WalletError> { log::debug!("remove_participation_event {id}"); let mut events = match self @@ -57,7 +57,7 @@ impl StorageManager { pub(crate) async fn get_participation_events( &self, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { log::debug!("get_participation_events"); Ok(self.storage.get(PARTICIPATION_EVENTS).await?.unwrap_or_default()) @@ -66,7 +66,7 @@ impl StorageManager { pub(crate) async fn set_cached_participation_output_status( &self, outputs_participation: &HashMap, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { log::debug!("set_cached_participation"); self.storage @@ -78,7 +78,7 @@ impl StorageManager { pub(crate) async fn get_cached_participation_output_status( &self, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { log::debug!("get_cached_participation"); Ok(self diff --git a/sdk/src/wallet/task.rs b/sdk/src/wallet/task.rs index f286871636..76fad921d2 100644 --- a/sdk/src/wallet/task.rs +++ b/sdk/src/wallet/task.rs @@ -11,7 +11,7 @@ where } #[cfg(target_family = "wasm")] -pub(crate) async fn spawn(future: F) -> crate::wallet::Result +pub(crate) async fn spawn(future: F) -> Result where F: futures::Future + 'static, F::Output: 'static, diff --git a/sdk/src/wallet/types/address.rs b/sdk/src/wallet/types/address.rs index 2eced539d4..1fa0ae745f 100644 --- a/sdk/src/wallet/types/address.rs +++ b/sdk/src/wallet/types/address.rs @@ -1,69 +1,18 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::hash::Hash; - use getset::{Getters, Setters}; use serde::{Deserialize, Serialize}; -use crate::{ - types::{ - self, - block::{address::Bech32Address, output::OutputId}, - }, - utils::ConvertTo, -}; - -/// A BIP44 address. -#[derive(Debug, Getters, Setters, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)] -#[serde(rename_all = "camelCase")] -#[getset(get = "pub")] -pub struct Bip44Address { - /// The address. - pub(crate) address: Bech32Address, - /// The address key index. - #[getset(set = "pub(crate)")] - pub(crate) key_index: u32, - /// Determines if an address is a public or an internal (change) address. - #[getset(set = "pub(crate)")] - pub(crate) internal: bool, -} - -impl Bip44Address { - pub fn into_bech32(self) -> Bech32Address { - self.address - } -} - -impl ConvertTo for Bip44Address { - fn convert(self) -> Result { - Ok(self.address) - } - - fn convert_unchecked(self) -> Bech32Address { - self.address - } -} +use crate::types::block::{address::Bech32Address, output::OutputId}; /// An account address with unspent output_ids for unspent outputs. #[derive(Debug, Getters, Setters, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] #[getset(get = "pub")] -pub struct AddressWithUnspentOutputs { +pub(crate) struct AddressWithUnspentOutputs { /// The address. pub(crate) address: Bech32Address, - /// The address key index. - #[getset(set = "pub(crate)")] - pub(crate) key_index: u32, - /// Determines if an address is a public or an internal (change) address. - #[getset(set = "pub(crate)")] - pub(crate) internal: bool, /// Output ids pub(crate) output_ids: Vec, } - -impl AddressWithUnspentOutputs { - pub fn into_bech32(self) -> Bech32Address { - self.address - } -} diff --git a/sdk/src/wallet/types/balance.rs b/sdk/src/wallet/types/balance.rs index facdc4e150..3a7ffe686f 100644 --- a/sdk/src/wallet/types/balance.rs +++ b/sdk/src/wallet/types/balance.rs @@ -139,14 +139,14 @@ impl std::ops::AddAssign for NativeTokensBalance { } } -#[cfg(feature = "rand")] +#[cfg(all(feature = "rand", feature = "protocol_parameters_samples"))] impl Balance { pub fn rand() -> Self { use rand::Rng; use crate::types::block::rand::bytes::rand_bytes_array; - let token_supply = crate::types::block::protocol::protocol_parameters().token_supply(); + let token_supply = crate::types::block::protocol::iota_mainnet_protocol_parameters().token_supply(); let total = rand::thread_rng().gen_range(128..token_supply / 1000000); let mut generator = 0u8; diff --git a/sdk/src/wallet/types/mod.rs b/sdk/src/wallet/types/mod.rs index 55a1343151..543b49055c 100644 --- a/sdk/src/wallet/types/mod.rs +++ b/sdk/src/wallet/types/mod.rs @@ -9,26 +9,27 @@ pub mod participation; use std::str::FromStr; +use crypto::keys::bip44::Bip44; use serde::{Deserialize, Serialize}; -pub use self::{ - address::{AddressWithUnspentOutputs, Bip44Address}, - balance::{Balance, BaseCoinBalance, NativeTokensBalance, RequiredStorageDeposit}, -}; +pub use self::balance::{Balance, BaseCoinBalance, NativeTokensBalance, RequiredStorageDeposit}; use crate::{ - client::secret::types::InputSigningData, + client::{secret::types::InputSigningData, ClientError}, types::{ - api::core::OutputWithMetadataResponse, block::{ - output::{Output, OutputId, OutputIdProof, OutputMetadata}, - payload::signed_transaction::{dto::SignedTransactionPayloadDto, SignedTransactionPayload, TransactionId}, + address::Bech32Address, + output::{Output, OutputId, OutputIdProof, OutputMetadata, OutputWithMetadata}, + payload::{ + signed_transaction::{dto::SignedTransactionPayloadDto, SignedTransactionPayload, TransactionId}, + PayloadError, + }, protocol::{CommittableAgeRange, ProtocolParameters}, slot::SlotIndex, - BlockId, Error as BlockError, + BlockId, }, TryFromDto, }, - wallet::core::WalletData, + wallet::WalletError, }; /// An output with metadata @@ -55,19 +56,20 @@ impl OutputData { pub fn input_signing_data( &self, - wallet_data: &WalletData, + wallet_address: &Bech32Address, + wallet_bip_path: Option, commitment_slot_index: impl Into, committable_age_range: CommittableAgeRange, - ) -> crate::wallet::Result> { + ) -> Result, WalletError> { let required_address = self .output .required_address(commitment_slot_index.into(), committable_age_range)? - .ok_or(crate::client::Error::ExpirationDeadzone)?; + .ok_or(ClientError::ExpirationDeadzone)?; let chain = if let Some(required_ed25519) = required_address.backing_ed25519() { - if let Some(backing_ed25519) = wallet_data.address.inner().backing_ed25519() { + if let Some(backing_ed25519) = wallet_address.inner().backing_ed25519() { if required_ed25519 == backing_ed25519 { - wallet_data.bip_path + wallet_bip_path } else { // Different ed25519 chain than the wallet one. None @@ -106,7 +108,7 @@ pub struct TransactionWithMetadata { /// Outputs that are used as input in the transaction. May not be all, because some may have already been deleted /// from the node. // serde(default) is needed so it doesn't break with old dbs - pub inputs: Vec, + pub inputs: Vec, } /// Dto for a transaction with metadata @@ -129,7 +131,7 @@ pub struct TransactionWithMetadataDto { pub incoming: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub note: Option, - pub inputs: Vec, + pub inputs: Vec, } impl From<&TransactionWithMetadata> for TransactionWithMetadataDto { @@ -149,7 +151,7 @@ impl From<&TransactionWithMetadata> for TransactionWithMetadataDto { } impl TryFromDto for TransactionWithMetadata { - type Error = BlockError; + type Error = PayloadError; fn try_from_dto_with_params_inner( dto: TransactionWithMetadataDto, @@ -161,13 +163,13 @@ impl TryFromDto for TransactionWithMetadata { inclusion_state: dto.inclusion_state, timestamp: dto .timestamp - .parse() - .map_err(|_| BlockError::InvalidField("timestamp"))?, + .parse::() + .map_err(|e| PayloadError::Timestamp(e.to_string()))?, transaction_id: dto.transaction_id, network_id: dto .network_id - .parse() - .map_err(|_| BlockError::InvalidField("network id"))?, + .parse::() + .map_err(|e| PayloadError::NetworkId(e.to_string()))?, incoming: dto.incoming, note: dto.note, inputs: dto.inputs, @@ -210,7 +212,7 @@ pub enum OutputKind { } impl FromStr for OutputKind { - type Err = crate::wallet::Error; + type Err = WalletError; fn from_str(s: &str) -> Result { let kind = match s { @@ -218,7 +220,7 @@ impl FromStr for OutputKind { "Basic" => Self::Basic, "Foundry" => Self::Foundry, "Nft" => Self::Nft, - _ => return Err(crate::wallet::Error::InvalidOutputKind(s.to_string())), + _ => return Err(WalletError::InvalidOutputKind(s.to_string())), }; Ok(kind) } diff --git a/sdk/src/wallet/update.rs b/sdk/src/wallet/update.rs index 54a2fe8df1..bb7572c6d3 100644 --- a/sdk/src/wallet/update.rs +++ b/sdk/src/wallet/update.rs @@ -3,6 +3,8 @@ use std::collections::HashMap; +#[cfg(feature = "storage")] +use crate::wallet::core::WalletLedgerDto; use crate::{ client::secret::SecretManage, types::block::{ @@ -11,7 +13,7 @@ use crate::{ }, wallet::{ types::{InclusionState, OutputData, TransactionWithMetadata}, - Wallet, + Wallet, WalletError, }, }; #[cfg(feature = "events")] @@ -20,17 +22,36 @@ use crate::{ wallet::events::types::{NewOutputEvent, SpentOutputEvent, TransactionInclusionEvent, WalletEvent}, }; -impl Wallet -where - crate::wallet::Error: From, - crate::client::Error: From, -{ - /// Set the alias for the wallet. - pub async fn set_alias(&self, alias: &str) -> crate::wallet::Result<()> { - let mut wallet_data = self.data_mut().await; - wallet_data.alias = Some(alias.to_string()); +impl Wallet { + /// Update the wallet address with a possible new Bech32 HRP and clear the inaccessible incoming transactions. + pub(crate) async fn update_address_hrp(&self) -> Result<(), WalletError> { + let bech32_hrp = self.client().get_bech32_hrp().await?; + log::debug!("updating wallet with new bech32 hrp: {}", bech32_hrp); + + self.address_mut().await.hrp = bech32_hrp; + + let mut wallet_ledger = self.ledger_mut().await; + wallet_ledger.inaccessible_incoming_transactions.clear(); + #[cfg(feature = "storage")] - self.storage_manager().save_wallet_data(&wallet_data).await?; + { + log::debug!("[save] wallet ledger with updated bech32 hrp",); + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*wallet_ledger)) + .await?; + } + + drop(wallet_ledger); + + Ok(()) + } + + /// Set the wallet alias. + pub async fn set_alias(&self, alias: &str) -> Result<(), WalletError> { + log::debug!("setting wallet alias to: {}", alias); + + *self.alias_mut().await = Some(alias.to_string()); + Ok(()) } @@ -39,19 +60,19 @@ where &self, unspent_outputs: Vec, spent_or_unsynced_output_metadata_map: HashMap>, - ) -> crate::wallet::Result<()> { - log::debug!("[SYNC] Update wallet with new synced transactions"); + ) -> Result<(), WalletError> { + log::debug!("[SYNC] Update wallet ledger with new synced transactions"); let network_id = self.client().get_network_id().await?; - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; // Update spent outputs for (output_id, output_metadata_response_opt) in spent_or_unsynced_output_metadata_map { // If we got the output response and it's still unspent, skip it if let Some(output_metadata_response) = output_metadata_response_opt { if output_metadata_response.is_spent() { - wallet_data.unspent_outputs.remove(&output_id); - if let Some(output_data) = wallet_data.outputs.get_mut(&output_id) { + wallet_ledger.unspent_outputs.remove(&output_id); + if let Some(output_data) = wallet_ledger.outputs.get_mut(&output_id) { output_data.metadata = output_metadata_response; } } else { @@ -60,14 +81,14 @@ where } } - if let Some(output) = wallet_data.outputs.get(&output_id) { + if let Some(output) = wallet_ledger.outputs.get(&output_id) { // Could also be outputs from other networks after we switched the node, so we check that first if output.network_id == network_id { log::debug!("[SYNC] Spent output {}", output_id); - wallet_data.locked_outputs.remove(&output_id); - wallet_data.unspent_outputs.remove(&output_id); + wallet_ledger.locked_outputs.remove(&output_id); + wallet_ledger.unspent_outputs.remove(&output_id); // Update spent data fields - if let Some(output_data) = wallet_data.outputs.get_mut(&output_id) { + if let Some(output_data) = wallet_ledger.outputs.get_mut(&output_id) { if !output_data.is_spent() { log::warn!( "[SYNC] Setting output {} as spent without having the OutputConsumptionMetadata", @@ -97,14 +118,14 @@ where // Add new synced outputs for output_data in unspent_outputs { // Insert output, if it's unknown emit the NewOutputEvent - if wallet_data + if wallet_ledger .outputs .insert(output_data.output_id, output_data.clone()) .is_none() { #[cfg(feature = "events")] { - let transaction = wallet_data + let transaction = wallet_ledger .incoming_transactions .get(output_data.output_id.transaction_id()); self.emit(WalletEvent::NewOutput(Box::new(NewOutputEvent { @@ -118,14 +139,16 @@ where } }; if !output_data.is_spent() { - wallet_data.unspent_outputs.insert(output_data.output_id, output_data); + wallet_ledger.unspent_outputs.insert(output_data.output_id, output_data); } } #[cfg(feature = "storage")] { - log::debug!("[SYNC] storing wallet with new synced data"); - self.storage_manager().save_wallet_data(&wallet_data).await?; + log::debug!("[SYNC] storing wallet ledger with new synced data"); + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*wallet_ledger)) + .await?; } Ok(()) } @@ -136,16 +159,16 @@ where updated_transactions: Vec, spent_output_ids: Vec, output_ids_to_unlock: Vec, - ) -> crate::wallet::Result<()> { + ) -> Result<(), WalletError> { log::debug!("[SYNC] Update wallet with new synced transactions"); - let mut wallet_data = self.data_mut().await; + let mut wallet_ledger = self.ledger_mut().await; for transaction in updated_transactions { match transaction.inclusion_state { InclusionState::Confirmed | InclusionState::Conflicting | InclusionState::UnknownPruned => { let transaction_id = transaction.payload.transaction().id(); - wallet_data.pending_transactions.remove(&transaction_id); + wallet_ledger.pending_transactions.remove(&transaction_id); log::debug!( "[SYNC] inclusion_state of {transaction_id} changed to {:?}", transaction.inclusion_state @@ -161,13 +184,13 @@ where } _ => {} } - wallet_data + wallet_ledger .transactions .insert(transaction.payload.transaction().id(), transaction.clone()); } for output_to_unlock in &spent_output_ids { - if let Some(output_data) = wallet_data.outputs.get_mut(output_to_unlock) { + if let Some(output_data) = wallet_ledger.outputs.get_mut(output_to_unlock) { if !output_data.is_spent() { log::warn!( "[SYNC] Setting output {} as spent without having the OutputConsumptionMetadata", @@ -182,13 +205,13 @@ where )); } } - wallet_data.locked_outputs.remove(output_to_unlock); - wallet_data.unspent_outputs.remove(output_to_unlock); + wallet_ledger.locked_outputs.remove(output_to_unlock); + wallet_ledger.unspent_outputs.remove(output_to_unlock); log::debug!("[SYNC] Unlocked spent output {}", output_to_unlock); } for output_to_unlock in &output_ids_to_unlock { - wallet_data.locked_outputs.remove(output_to_unlock); + wallet_ledger.locked_outputs.remove(output_to_unlock); log::debug!( "[SYNC] Unlocked unspent output {} because of a conflicting transaction", output_to_unlock @@ -197,27 +220,11 @@ where #[cfg(feature = "storage")] { - log::debug!("[SYNC] storing wallet with new synced transactions"); - self.storage_manager().save_wallet_data(&wallet_data).await?; + log::debug!("[SYNC] storing wallet ledger with new synced transactions"); + self.storage_manager() + .save_wallet_ledger(&WalletLedgerDto::from(&*wallet_ledger)) + .await?; } Ok(()) } - - /// Update the wallet address with a possible new Bech32 HRP and clear the inaccessible incoming transactions. - pub(crate) async fn update_bech32_hrp(&self) -> crate::wallet::Result<()> { - let bech32_hrp = self.client().get_bech32_hrp().await?; - log::debug!("updating wallet data with new bech32 hrp: {}", bech32_hrp); - let mut wallet_data = self.data_mut().await; - - wallet_data.address.hrp = bech32_hrp; - wallet_data.inaccessible_incoming_transactions.clear(); - - #[cfg(feature = "storage")] - { - log::debug!("[save] wallet data with updated bech32 hrp",); - self.storage_manager().save_wallet_data(&wallet_data).await?; - } - - Ok(()) - } } diff --git a/sdk/tests/client/addresses.rs b/sdk/tests/client/addresses.rs index 33515fe33d..a4a180ac01 100644 --- a/sdk/tests/client/addresses.rs +++ b/sdk/tests/client/addresses.rs @@ -11,9 +11,12 @@ use iota_sdk::{ constants::{IOTA_BECH32_HRP, IOTA_COIN_TYPE, IOTA_TESTNET_BECH32_HRP, SHIMMER_BECH32_HRP, SHIMMER_COIN_TYPE}, generate_mnemonic, secret::{GenerateAddressOptions, SecretManager}, - Client, Result, + Client, ClientError, + }, + types::block::{ + address::{Address, Hrp}, + protocol::iota_mainnet_protocol_parameters, }, - types::block::address::{Address, Hrp}, }; use pretty_assertions::assert_eq; use serde::{Deserialize, Serialize}; @@ -62,7 +65,11 @@ async fn evm_addresses() { #[tokio::test] async fn public_key_to_address() { - let client = Client::builder().finish().await.unwrap(); + let client = Client::builder() + .with_protocol_parameters(iota_mainnet_protocol_parameters().clone()) + .finish() + .await + .unwrap(); let hex_public_key = "0x2baaf3bca8ace9f862e60184bd3e79df25ff230f7eaaa4c7f03daa9833ba854a"; let public_key_address = client @@ -251,8 +258,12 @@ async fn address_generation() { } #[tokio::test] -async fn search_address() -> Result<()> { - let client = Client::builder().finish().await.unwrap(); +async fn search_address() -> Result<(), ClientError> { + let client = Client::builder() + .with_protocol_parameters(iota_mainnet_protocol_parameters().clone()) + .finish() + .await + .unwrap(); let secret_manager = SecretManager::try_from_mnemonic(generate_mnemonic()?)?; @@ -311,7 +322,7 @@ async fn search_address() -> Result<()> { .await; match res { - Err(iota_sdk::client::Error::InputAddressNotFound { .. }) => {} + Err(iota_sdk::client::ClientError::InputAddressNotFound { .. }) => {} _ => panic!("should not have found search address range & public"), } diff --git a/sdk/tests/client/client_builder.rs b/sdk/tests/client/client_builder.rs index b5f7853e87..c92f8795f4 100644 --- a/sdk/tests/client/client_builder.rs +++ b/sdk/tests/client/client_builder.rs @@ -3,7 +3,7 @@ use iota_sdk::{ client::{Client, ClientBuilder}, - types::block::protocol::ProtocolParameters, + types::block::protocol::iota_mainnet_protocol_parameters, }; #[tokio::test] @@ -36,7 +36,7 @@ async fn client_builder() { "minQuorumSize": 3, "quorumThreshold": 66, "userAgent": "iota-client/2.0.1-rc.3", - "protocolParameters": ProtocolParameters::default(), + "protocolParameters": iota_mainnet_protocol_parameters(), "apiTimeout": { "secs": 15, "nanos": 0 diff --git a/sdk/tests/client/common/constants.rs b/sdk/tests/client/common/constants.rs index 7f7a02fb2b..c06eb0d4e6 100644 --- a/sdk/tests/client/common/constants.rs +++ b/sdk/tests/client/common/constants.rs @@ -4,3 +4,5 @@ pub static NODE_LOCAL: &str = "http://localhost:8050"; pub static FAUCET_URL: &str = "http://localhost:8091/api/enqueue"; + +pub static DEFAULT_MNEMONIC: &str = "inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak"; diff --git a/sdk/tests/client/common/mod.rs b/sdk/tests/client/common/mod.rs index ef018ba902..5e249dca6c 100644 --- a/sdk/tests/client/common/mod.rs +++ b/sdk/tests/client/common/mod.rs @@ -10,7 +10,7 @@ mod constants; // }; use iota_sdk::client::Client; -pub use self::constants::{FAUCET_URL, NODE_LOCAL}; +pub use self::constants::{DEFAULT_MNEMONIC, FAUCET_URL, NODE_LOCAL}; /// Sets up a Client with node health ignored. pub async fn setup_client_with_node_health_ignored() -> Client { @@ -62,3 +62,15 @@ pub async fn setup_client_with_node_health_ignored() -> Client { // } // panic!("Faucet no longer wants to hand over coins"); // } + +#[allow(dead_code)] +pub(crate) fn setup(path: &str) -> Result<(), Box> { + // Ignore error in case the path didn't exist yet. + std::fs::remove_dir_all(path).ok(); + Ok(()) +} + +#[allow(dead_code)] +pub(crate) fn tear_down(path: &str) -> Result<(), Box> { + Ok(std::fs::remove_dir_all(path)?) +} diff --git a/sdk/tests/client/error.rs b/sdk/tests/client/error.rs index 6e2c562a72..54cbda14db 100644 --- a/sdk/tests/client/error.rs +++ b/sdk/tests/client/error.rs @@ -2,20 +2,20 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::{ - client::{api::input_selection::Error as IsaError, Error}, - types::block::Error as BlockError, + client::{api::transaction_builder::TransactionBuilderError, ClientError}, + types::block::BlockError, }; use pretty_assertions::assert_eq; #[test] fn stringified_error() { - let error = Error::InvalidAmount("0".into()); + let error = ClientError::InvalidAmount("0".into()); assert_eq!( &serde_json::to_string(&error).unwrap(), "{\"type\":\"invalidAmount\",\"error\":\"invalid amount in API response: 0\"}" ); - let error = Error::TimeNotSynced { + let error = ClientError::TimeNotSynced { current_time: 0, tangle_time: 10000, }; @@ -24,24 +24,24 @@ fn stringified_error() { "{\"type\":\"timeNotSynced\",\"error\":\"local time 0 doesn't match the tangle time: 10000\"}" ); - let error = Error::PlaceholderSecretManager; + let error = ClientError::PlaceholderSecretManager; assert_eq!( &serde_json::to_string(&error).unwrap(), "{\"type\":\"placeholderSecretManager\",\"error\":\"placeholderSecretManager can't be used for address generation or signing\"}" ); - let error = Error::InputSelection(IsaError::InsufficientAmount { + let error = ClientError::TransactionBuilder(TransactionBuilderError::InsufficientAmount { found: 0, required: 100, }); assert_eq!( &serde_json::to_string(&error).unwrap(), - "{\"type\":\"inputSelection\",\"error\":\"insufficient amount: found 0, required 100\"}" + "{\"type\":\"transactionBuilder\",\"error\":\"insufficient amount: found 0, required 100\"}" ); - let error = Error::InputSelection(IsaError::Block(BlockError::InvalidAddress)); + let error = ClientError::TransactionBuilder(TransactionBuilderError::Block(BlockError::UnsupportedAddressKind(6))); assert_eq!( &serde_json::to_string(&error).unwrap(), - "{\"type\":\"inputSelection\",\"error\":\"invalid address provided\"}" + "{\"type\":\"transactionBuilder\",\"error\":\"unsupported address kind: 6\"}" ); } diff --git a/sdk/tests/client/high_level.rs b/sdk/tests/client/high_level.rs index c120a3215f..ba8e625e76 100644 --- a/sdk/tests/client/high_level.rs +++ b/sdk/tests/client/high_level.rs @@ -8,10 +8,10 @@ use crate::client::{common::setup_client_with_node_health_ignored, node_api::set #[ignore] #[tokio::test] -async fn test_find_inputs_from_transaction_id() { +async fn test_get_transaction_inputs() { let client = setup_client_with_node_health_ignored().await; let (_block_id, transaction_id) = setup_transaction_block(&client).await; - let inputs = client.inputs_from_transaction_id(&transaction_id).await.unwrap(); + let inputs = client.get_transaction_inputs(&transaction_id).await.unwrap(); assert_eq!(inputs.len(), 1); } diff --git a/sdk/tests/client/mnemonic.rs b/sdk/tests/client/mnemonic.rs index 8fa86d69ab..04c48d304b 100644 --- a/sdk/tests/client/mnemonic.rs +++ b/sdk/tests/client/mnemonic.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crypto::keys::bip39::Mnemonic; -use iota_sdk::client::{Client, Result}; +use iota_sdk::client::{Client, ClientError}; #[tokio::test] -async fn mnemonic() -> Result<()> { +async fn mnemonic() -> Result<(), ClientError> { let mnemonic = Client::generate_mnemonic()?; assert!(Client::mnemonic_to_hex_seed(mnemonic).is_ok()); assert!(Client::mnemonic_to_hex_seed(Mnemonic::from("until fire hat mountain zoo grocery real deny advance change marble taste goat ivory wheat bubble panic banner tattoo client ticket action race rocket".to_owned())).is_ok()); diff --git a/sdk/tests/client/mod.rs b/sdk/tests/client/mod.rs index 00643214d6..768fef6bbc 100644 --- a/sdk/tests/client/mod.rs +++ b/sdk/tests/client/mod.rs @@ -6,7 +6,6 @@ mod client_builder; mod common; mod error; mod high_level; -mod input_selection; mod input_signing_data; mod mnemonic; #[cfg(feature = "mqtt")] @@ -14,6 +13,7 @@ mod mqtt; mod node_api; mod secret_manager; mod signing; +mod transaction_builder; use std::{collections::HashMap, hash::Hash, str::FromStr}; @@ -55,7 +55,7 @@ const BECH32_ADDRESS_ED25519_0: &str = "rms1qr2xsmt3v3eyp2ja80wd2sq8xx0fslefmxgu const BECH32_ADDRESS_ED25519_1: &str = "rms1qqhvvur9xfj6yhgsxfa4f8xst7vz9zxeu3vcxds8mh4a6jlpteq9xrajhtf"; const BECH32_ADDRESS_ED25519_2: &str = "rms1qr47gz3xxjqpjrwd0yu5glhqrth6w0t08npney8000ust2lcw2r92j5a8rt"; const BECH32_ADDRESS_ACCOUNT_1: &str = "rms1pqg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zws5524"; // Corresponds to ACCOUNT_ID_1 -const BECH32_ADDRESS_ACCOUNT_2: &str = "rms1pq3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zymxrh9z"; // Corresponds to ACCOUNT_ID_2 +const _BECH32_ADDRESS_ACCOUNT_2: &str = "rms1pq3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zymxrh9z"; // Corresponds to ACCOUNT_ID_2 const BECH32_ADDRESS_NFT_1: &str = "rms1zqg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zxddmy7"; // Corresponds to NFT_ID_1 const _BECH32_ADDRESS_NFT_2: &str = "rms1zq3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zynm6ctf"; // Corresponds to NFT_ID_2 const SLOT_INDEX: SlotIndex = SlotIndex(10); @@ -65,6 +65,7 @@ const SLOT_COMMITMENT_ID: SlotCommitmentId = SlotCommitmentHash::null().const_in enum Build<'a> { Basic { amount: u64, + mana: u64, address: Address, native_token: Option<(&'a str, u64)>, sender: Option
, @@ -74,6 +75,7 @@ enum Build<'a> { }, Nft { amount: u64, + mana: u64, nft_id: NftId, address: Address, sender: Option
, @@ -83,6 +85,7 @@ enum Build<'a> { }, Account { amount: u64, + mana: u64, account_id: AccountId, address: Address, sender: Option
, @@ -111,6 +114,7 @@ impl<'a> Build<'a> { match self { Build::Basic { amount, + mana, address, native_token, sender, @@ -119,6 +123,7 @@ impl<'a> Build<'a> { expiration, } => { let mut builder = BasicOutputBuilder::new_with_amount(amount) + .with_mana(mana) .add_unlock_condition(AddressUnlockCondition::new(address.clone())); if let Some((id, amount)) = native_token { @@ -149,6 +154,7 @@ impl<'a> Build<'a> { } Build::Nft { amount, + mana, nft_id, address, sender, @@ -157,6 +163,7 @@ impl<'a> Build<'a> { expiration, } => { let mut builder = NftOutputBuilder::new_with_amount(amount, nft_id) + .with_mana(mana) .add_unlock_condition(AddressUnlockCondition::new(address)); if let Some(sender) = sender { @@ -180,12 +187,14 @@ impl<'a> Build<'a> { } Build::Account { amount, + mana, account_id, address, sender, issuer, } => { let mut builder = AccountOutputBuilder::new_with_amount(amount, account_id) + .with_mana(mana) .add_unlock_condition(AddressUnlockCondition::new(address)); if let Some(sender) = sender { @@ -278,37 +287,26 @@ where count(a) == count(b) } -fn is_remainder_or_return(output: &Output, amount: u64, address: Address, native_token: Option<(&str, u64)>) -> bool { - if let Output::Basic(output) = output { - if output.amount() != amount { - return false; - } - - if let [UnlockCondition::Address(address_unlock_condition)] = output.unlock_conditions().as_ref() { - if address_unlock_condition.address() != &address { - return false; - } - } else { - return false; - } +fn assert_remainder_or_return(output: &Output, amount: u64, address: Address, native_token: Option<(&str, u64)>) { + let output = output.as_basic(); + assert_eq!(amount, output.amount()); - match output.features().as_ref() { - [] | [Feature::NativeToken(_)] => {} - _ => return false, - } + if let [UnlockCondition::Address(address_unlock_condition)] = output.unlock_conditions().as_ref() { + assert_eq!(&address, address_unlock_condition.address()); + } else { + panic!("no address unlock condition"); + } - if let Some((token_id, amount)) = native_token { - let native_token = NativeToken::new(TokenId::from_str(token_id).unwrap(), amount).unwrap(); + match output.features().as_ref() { + [] | [Feature::NativeToken(_)] => {} + _ => panic!("incorrect features"), + } - if output.native_token().unwrap() != &native_token { - return false; - } - } else if output.native_token().is_some() { - return false; - } + if let Some((token_id, amount)) = native_token { + let native_token = NativeToken::new(TokenId::from_str(token_id).unwrap(), amount).unwrap(); - true - } else { - false + assert_eq!(&native_token, output.native_token().unwrap()); + } else if output.native_token().is_some() { + panic!("no native token provided but native token exists on output"); } } diff --git a/sdk/tests/client/node_api/core.rs b/sdk/tests/client/node_api/core.rs index e508c91263..e0fc8d7b07 100644 --- a/sdk/tests/client/node_api/core.rs +++ b/sdk/tests/client/node_api/core.rs @@ -5,8 +5,9 @@ use iota_sdk::{ client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::BasicOutputQueryParameters, Client, - NodeInfoWrapper, + api::GetAddressesOptions, + node_api::{core::routes::NodeInfoResponse, indexer::query_parameters::BasicOutputQueryParameters}, + Client, }, types::{ api::core::TransactionState, @@ -36,7 +37,7 @@ async fn test_get_health() { #[ignore] #[tokio::test] async fn test_get_info() { - let r = Client::get_node_info(NODE_LOCAL, None).await.unwrap(); + let r = Client::get_info(NODE_LOCAL, None).await.unwrap(); println!("{r:#?}"); } @@ -157,7 +158,7 @@ async fn test_get_output_raw() { let output_id = OutputId::new(transaction_id, 0); let output = client.get_output(&output_id).await.unwrap(); - let output_raw = Output::unpack_verified( + let output_raw = Output::unpack_bytes_verified( client.get_output_raw(&output_id).await.unwrap(), &client.get_protocol_parameters().await.unwrap(), ) @@ -184,7 +185,7 @@ async fn test_get_included_block_raw() { let (_block_id, transaction_id) = setup_transaction_block(&client).await; let block = client.get_included_block(&transaction_id).await.unwrap(); - let block_raw = Block::unpack_verified( + let block_raw = Block::unpack_bytes_verified( client.get_included_block_raw(&transaction_id).await.unwrap(), &client.get_protocol_parameters().await.unwrap(), ) @@ -199,15 +200,15 @@ async fn test_call_plugin_route() { let c = setup_client_with_node_health_ignored().await; // we call the "custom" plugin "node info" - let plugin_res: NodeInfoWrapper = c + let plugin_res: NodeInfoResponse = c .call_plugin_route("api/core/v2/", "GET", "info", vec![], None) .await .unwrap(); - let info = c.get_info().await.unwrap(); + let node_info = c.get_node_info().await.unwrap(); // Just check name as info can change between 2 calls - assert_eq!(plugin_res.node_info.name, info.node_info.name); + assert_eq!(plugin_res.info.name, node_info.info.name); } #[ignore] diff --git a/sdk/tests/client/node_api/indexer.rs b/sdk/tests/client/node_api/indexer.rs index c74adddbda..f7c979978b 100644 --- a/sdk/tests/client/node_api/indexer.rs +++ b/sdk/tests/client/node_api/indexer.rs @@ -23,7 +23,7 @@ // #[ignore] // #[tokio::test] -// async fn get_alias_output_id_test() -> Result<()> { +// async fn get_alias_output_id_test() -> Result<(), ClientError> { // let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; // let protocol_parameters = client.get_protocol_parameters().await?; @@ -55,7 +55,7 @@ // #[ignore] // #[tokio::test] -// async fn get_nft_output_id_test() -> Result<()> { +// async fn get_nft_output_id_test() -> Result<(), ClientError> { // let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; // let protocol_parameters = client.get_protocol_parameters().await?; @@ -85,7 +85,7 @@ // #[ignore] // #[tokio::test] -// async fn get_foundry_output_id_test() -> Result<()> { +// async fn get_foundry_output_id_test() -> Result<(), ClientError> { // let (client, secret_manager) = create_client_and_secret_manager_with_funds(None).await?; // let protocol_parameters = client.get_protocol_parameters().await?; diff --git a/sdk/tests/client/node_api/mod.rs b/sdk/tests/client/node_api/mod.rs index 6b36cde54c..1d5a9edb0c 100644 --- a/sdk/tests/client/node_api/mod.rs +++ b/sdk/tests/client/node_api/mod.rs @@ -108,7 +108,7 @@ pub async fn setup_transaction_block(client: &Client) -> (BlockId, TransactionId // TODO uncomment // // helper function to get the output id for the first alias output -// fn get_alias_output_id(payload: &Payload) -> Result { +// fn get_alias_output_id(payload: &Payload) -> Result { // match payload { // Payload::Transaction(tx_payload) => { // for (index, output) in tx_payload.transaction().as_regular().outputs().iter().enumerate() { @@ -123,7 +123,7 @@ pub async fn setup_transaction_block(client: &Client) -> (BlockId, TransactionId // } // // helper function to get the output id for the first foundry output -// fn get_foundry_output_id(payload: &Payload) -> Result { +// fn get_foundry_output_id(payload: &Payload) -> Result { // match payload { // Payload::Transaction(tx_payload) => { // for (index, output) in tx_payload.transaction().as_regular().outputs().iter().enumerate() { @@ -138,7 +138,7 @@ pub async fn setup_transaction_block(client: &Client) -> (BlockId, TransactionId // } // // helper function to get the output id for the first NFT output -// fn get_nft_output_id(payload: &Payload) -> Result { +// fn get_nft_output_id(payload: &Payload) -> Result { // match payload { // Payload::Transaction(tx_payload) => { // for (index, output) in tx_payload.transaction().as_regular().outputs().iter().enumerate() { diff --git a/sdk/tests/client/secret_manager/address_generation.rs b/sdk/tests/client/secret_manager/address_generation.rs new file mode 100644 index 0000000000..7ff9584cce --- /dev/null +++ b/sdk/tests/client/secret_manager/address_generation.rs @@ -0,0 +1,111 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "stronghold")] +use crypto::keys::bip39::Mnemonic; +use crypto::keys::bip44::Bip44; +#[cfg(feature = "stronghold")] +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +#[cfg(feature = "ledger_nano")] +use iota_sdk::client::secret::{ledger_nano::LedgerSecretManager, GenerateAddressOptions}; +#[cfg(feature = "events")] +use iota_sdk::wallet::events::{WalletEvent, WalletEventType}; +use iota_sdk::{ + client::{ + api::GetAddressesOptions, + constants::{IOTA_COIN_TYPE, SHIMMER_COIN_TYPE}, + secret::{mnemonic::MnemonicSecretManager, SecretManager}, + ClientError, + }, + types::block::address::{Hrp, ToBech32Ext}, + wallet::{ClientOptions, Wallet}, +}; +use pretty_assertions::assert_eq; + +use crate::client::common::{setup, tear_down, DEFAULT_MNEMONIC, NODE_LOCAL}; + +#[tokio::test] +async fn address_generation_mnemonic() -> Result<(), Box> { + let secret_manager = + SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic(DEFAULT_MNEMONIC.to_owned())?); + + let address = secret_manager + .generate_ed25519_address(IOTA_COIN_TYPE, 0, 0, "smr", None) + .await?; + + assert_eq!( + address, + // Address generated with bip32 path: [44, 4218, 0, 0, 0]. + "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" + ); + + Ok(()) +} + +#[cfg(feature = "stronghold")] +#[tokio::test] +async fn address_generation_stronghold() -> Result<(), Box> { + let storage_path = "test-storage/address_generation_stronghold"; + setup(storage_path)?; + + iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + + let secret_manager = StrongholdSecretManager::builder() + .password("some_hopefully_secure_password".to_owned()) + .build(format!("{}/test.stronghold", storage_path))?; + + secret_manager + .store_mnemonic(Mnemonic::from(DEFAULT_MNEMONIC.to_string())) + .await?; + + let secret_manager = SecretManager::Stronghold(secret_manager); + + let address = secret_manager + .generate_ed25519_address(IOTA_COIN_TYPE, 0, 0, "smr", None) + .await?; + + assert_eq!( + address, + // Address generated with bip32 path: [44, 4218, 0, 0, 0]. + "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" + ); + + tear_down(storage_path) +} + +#[tokio::test] +#[cfg(feature = "ledger_nano")] +#[ignore = "requires ledger nano instance"] +async fn address_generation_ledger() -> Result<(), Box> { + let mut secret_manager = LedgerSecretManager::new(true); + secret_manager.non_interactive = true; + + let secret_manager = SecretManager::LedgerNano(secret_manager); + + let address = secret_manager + .generate_ed25519_address(IOTA_COIN_TYPE, 0, 0, "smr", None) + .await?; + + assert_eq!( + address.to_bech32_unchecked("smr"), + // Address generated with bip32 path: [44, 4218, 0, 0, 0]. + // This address was generated with a MnemonicSecretManager and the ledger simulator mnemonic. + // "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster + // seven myth punch hobby comfort wild raise skin". + "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" + ); + + Ok(()) +} + +#[tokio::test] +async fn address_generation_placeholder() { + let secret_manager = SecretManager::Placeholder; + + assert!(matches!( + secret_manager + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, "smr", None) + .await, + Err(ClientError::PlaceholderSecretManager) + )); +} diff --git a/sdk/tests/client/secret_manager/mnemonic.rs b/sdk/tests/client/secret_manager/mnemonic.rs index e97e1a962a..84cf4e6cbe 100644 --- a/sdk/tests/client/secret_manager/mnemonic.rs +++ b/sdk/tests/client/secret_manager/mnemonic.rs @@ -2,27 +2,25 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::client::{ - api::GetAddressesOptions, constants::SHIMMER_TESTNET_BECH32_HRP, secret::SecretManager, Result, + api::GetAddressesOptions, + constants::{SHIMMER_COIN_TYPE, SHIMMER_TESTNET_BECH32_HRP}, + secret::SecretManager, + ClientError, }; use pretty_assertions::assert_eq; #[tokio::test] -async fn mnemonic_secret_manager() -> Result<()> { +async fn mnemonic_secret_manager() -> Result<(), ClientError> { let dto = r#"{"mnemonic": "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast"}"#; let secret_manager: SecretManager = dto.parse()?; - let addresses = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::default() - .with_bech32_hrp(SHIMMER_TESTNET_BECH32_HRP) - .with_account_index(0) - .with_range(0..1), - ) + let address = secret_manager + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, SHIMMER_TESTNET_BECH32_HRP, None) .await .unwrap(); assert_eq!( - addresses[0], + address, "rms1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6v3ea5a" ); diff --git a/sdk/tests/client/secret_manager/mod.rs b/sdk/tests/client/secret_manager/mod.rs index 4e2a7988d5..4ed72f4a0b 100644 --- a/sdk/tests/client/secret_manager/mod.rs +++ b/sdk/tests/client/secret_manager/mod.rs @@ -1,6 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod address_generation; mod mnemonic; #[cfg(feature = "private_key_secret_manager")] mod private_key; diff --git a/sdk/tests/client/secret_manager/private_key.rs b/sdk/tests/client/secret_manager/private_key.rs index 934a529d3c..1ec0b8bc98 100644 --- a/sdk/tests/client/secret_manager/private_key.rs +++ b/sdk/tests/client/secret_manager/private_key.rs @@ -5,12 +5,12 @@ use iota_sdk::client::{ api::GetAddressesOptions, constants::SHIMMER_TESTNET_BECH32_HRP, secret::{private_key::PrivateKeySecretManager, SecretManager}, - Result, + ClientError, }; use pretty_assertions::assert_eq; #[tokio::test] -async fn private_key_secret_manager_hex() -> Result<()> { +async fn private_key_secret_manager_hex() -> Result<(), ClientError> { let dto = r#"{"privateKey": "0x9e845b327c44e28bdd206c7c9eff09c40680bc2512add57280baf5b064d7e6f6"}"#; let secret_manager: SecretManager = dto.parse()?; @@ -64,7 +64,7 @@ async fn private_key_secret_manager_hex() -> Result<()> { } #[tokio::test] -async fn private_key_secret_manager_bs58() -> Result<()> { +async fn private_key_secret_manager_bs58() -> Result<(), ClientError> { let secret_manager = SecretManager::from(PrivateKeySecretManager::try_from_b58( "BfnURR6WSXJA6RyBr3WqGU99UzrVbWk9GSQgJqKtTRxZ", )?); diff --git a/sdk/tests/client/secret_manager/stronghold.rs b/sdk/tests/client/secret_manager/stronghold.rs index b915d9364f..bb087859ee 100644 --- a/sdk/tests/client/secret_manager/stronghold.rs +++ b/sdk/tests/client/secret_manager/stronghold.rs @@ -2,12 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::client::{ - api::GetAddressesOptions, constants::SHIMMER_TESTNET_BECH32_HRP, secret::SecretManager, Result, + api::GetAddressesOptions, + constants::{SHIMMER_COIN_TYPE, SHIMMER_TESTNET_BECH32_HRP}, + secret::SecretManager, + ClientError, }; use pretty_assertions::assert_eq; #[tokio::test] -async fn stronghold_secret_manager() -> Result<()> { +async fn stronghold_secret_manager() -> Result<(), ClientError> { iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); let dto = r#"{"stronghold": {"password": "some_hopefully_secure_password", "snapshotPath": "snapshot_test_dir/test.stronghold"}}"#; @@ -24,18 +27,13 @@ async fn stronghold_secret_manager() -> Result<()> { panic!("expect a Stronghold secret manager, but it's not the case!"); } - let addresses = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::default() - .with_bech32_hrp(SHIMMER_TESTNET_BECH32_HRP) - .with_account_index(0) - .with_range(0..1), - ) + let address = secret_manager + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, SHIMMER_TESTNET_BECH32_HRP, None) .await .unwrap(); assert_eq!( - addresses[0], + address, "rms1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6v3ea5a" ); @@ -52,7 +50,7 @@ async fn stronghold_secret_manager() -> Result<()> { } #[tokio::test] -async fn stronghold_mnemonic_missing() -> Result<()> { +async fn stronghold_mnemonic_missing() -> Result<(), ClientError> { iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); // Cleanup of a possibly failed run @@ -64,16 +62,12 @@ async fn stronghold_mnemonic_missing() -> Result<()> { // Generating addresses will fail because no mnemonic has been stored let error = SecretManager::Stronghold(stronghold_secret_manager) - .generate_ed25519_addresses( - GetAddressesOptions::default(), - // .with_bech32_hrp(SHIMMER_TESTNET_BECH32_HRP) - // .with_coin_type(iota_sdk::client::constants::SHIMMER_COIN_TYPE) - ) + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, SHIMMER_TESTNET_BECH32_HRP, None) .await .unwrap_err(); match error { - iota_sdk::client::Error::Stronghold(iota_sdk::client::stronghold::Error::MnemonicMissing) => {} + iota_sdk::client::ClientError::Stronghold(iota_sdk::client::stronghold::Error::MnemonicMissing) => {} _ => panic!("expected StrongholdMnemonicMissing error"), } diff --git a/sdk/tests/client/signing/account.rs b/sdk/tests/client/signing/account.rs index f485646c99..f1eb6c5bec 100644 --- a/sdk/tests/client/signing/account.rs +++ b/sdk/tests/client/signing/account.rs @@ -6,20 +6,20 @@ use std::str::FromStr; use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ - api::{ - transaction::validate_signed_transaction_payload_length, verify_semantic, GetAddressesOptions, - PreparedTransactionData, - }, + api::{GetAddressesOptions, PreparedTransactionData}, constants::SHIMMER_COIN_TYPE, secret::{SecretManage, SecretManager}, - Client, Result, + Client, }, types::block::{ address::{AccountAddress, Address}, input::{Input, UtxoInput}, output::AccountId, - payload::{signed_transaction::Transaction, SignedTransactionPayload}, - protocol::protocol_parameters, + payload::{ + signed_transaction::{Transaction, TransactionCapabilityFlag}, + SignedTransactionPayload, + }, + protocol::iota_mainnet_protocol_parameters, slot::SlotIndex, unlock::{SignatureUnlock, Unlock}, }, @@ -33,7 +33,7 @@ use crate::client::{ }; #[tokio::test] -async fn sign_account_state_transition() -> Result<()> { +async fn sign_account_state_transition() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -46,7 +46,7 @@ async fn sign_account_state_transition() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let account_id = AccountId::from_str(ACCOUNT_ID_1)?; let slot_index = SlotIndex::from(10); @@ -54,7 +54,8 @@ async fn sign_account_state_transition() -> Result<()> { [( Account { amount: 1_000_000, - account_id: account_id, + mana: 0, + account_id, address: address.clone(), sender: None, issuer: None, @@ -66,7 +67,8 @@ async fn sign_account_state_transition() -> Result<()> { let outputs = build_outputs([Account { amount: 1_000_000, - account_id: account_id, + mana: 0, + account_id, address: address.clone(), sender: None, issuer: None, @@ -81,7 +83,8 @@ async fn sign_account_state_transition() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -91,7 +94,7 @@ async fn sign_account_state_transition() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -99,24 +102,15 @@ async fn sign_account_state_transition() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; - - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + tx_payload.validate_length()?; - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } #[tokio::test] -async fn account_reference_unlocks() -> Result<()> { +async fn account_reference_unlocks() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -129,7 +123,7 @@ async fn account_reference_unlocks() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let account_id = AccountId::from_str(ACCOUNT_ID_1)?; let account_address = Address::Account(AccountAddress::new(account_id)); let slot_index = SlotIndex::from(10); @@ -139,7 +133,8 @@ async fn account_reference_unlocks() -> Result<()> { ( Account { amount: 1_000_000, - account_id: account_id, + mana: 0, + account_id, address: address.clone(), sender: None, issuer: None, @@ -149,6 +144,7 @@ async fn account_reference_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: account_address.clone(), native_token: None, sender: None, @@ -161,6 +157,7 @@ async fn account_reference_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: account_address.clone(), native_token: None, sender: None, @@ -177,13 +174,15 @@ async fn account_reference_unlocks() -> Result<()> { let outputs = build_outputs([ Account { amount: 1_000_000, - account_id: account_id, - address: address, + mana: 0, + account_id, + address, sender: None, issuer: None, }, Basic { amount: 2_000_000, + mana: 0, address: account_address, native_token: None, sender: None, @@ -202,7 +201,8 @@ async fn account_reference_unlocks() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -212,7 +212,7 @@ async fn account_reference_unlocks() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 3); @@ -232,18 +232,9 @@ async fn account_reference_unlocks() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; - - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + tx_payload.validate_length()?; - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } diff --git a/sdk/tests/client/signing/basic.rs b/sdk/tests/client/signing/basic.rs index 9fa7e3f3bb..9afce0e84e 100644 --- a/sdk/tests/client/signing/basic.rs +++ b/sdk/tests/client/signing/basic.rs @@ -4,18 +4,18 @@ use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ - api::{ - transaction::validate_signed_transaction_payload_length, verify_semantic, GetAddressesOptions, - PreparedTransactionData, - }, + api::{GetAddressesOptions, PreparedTransactionData}, constants::SHIMMER_COIN_TYPE, secret::{SecretManage, SecretManager}, - Client, Result, + Client, }, types::block::{ input::{Input, UtxoInput}, - payload::{signed_transaction::Transaction, SignedTransactionPayload}, - protocol::protocol_parameters, + payload::{ + signed_transaction::{Transaction, TransactionCapabilityFlag}, + SignedTransactionPayload, + }, + protocol::iota_mainnet_protocol_parameters, slot::SlotIndex, unlock::{SignatureUnlock, Unlock}, }, @@ -25,7 +25,7 @@ use pretty_assertions::assert_eq; use crate::client::{build_inputs, build_outputs, Build::Basic}; #[tokio::test] -async fn single_ed25519_unlock() -> Result<()> { +async fn single_ed25519_unlock() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address_0 = secret_manager @@ -38,13 +38,14 @@ async fn single_ed25519_unlock() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_index = SlotIndex::from(10); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: address_0.clone(), native_token: None, sender: None, @@ -59,6 +60,7 @@ async fn single_ed25519_unlock() -> Result<()> { let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: address_0, native_token: None, sender: None, @@ -76,7 +78,8 @@ async fn single_ed25519_unlock() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -86,7 +89,7 @@ async fn single_ed25519_unlock() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -94,24 +97,15 @@ async fn single_ed25519_unlock() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; - - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } #[tokio::test] -async fn ed25519_reference_unlocks() -> Result<()> { +async fn ed25519_reference_unlocks() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address_0 = secret_manager @@ -124,7 +118,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_index = SlotIndex::from(10); let inputs = build_inputs( @@ -132,6 +126,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: address_0.clone(), native_token: None, sender: None, @@ -144,6 +139,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: address_0.clone(), native_token: None, sender: None, @@ -156,6 +152,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: address_0.clone(), native_token: None, sender: None, @@ -171,6 +168,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: address_0, native_token: None, sender: None, @@ -188,7 +186,8 @@ async fn ed25519_reference_unlocks() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -198,7 +197,7 @@ async fn ed25519_reference_unlocks() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 3); @@ -218,24 +217,15 @@ async fn ed25519_reference_unlocks() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; - - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } #[tokio::test] -async fn two_signature_unlocks() -> Result<()> { +async fn two_signature_unlocks() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address_0 = secret_manager @@ -257,7 +247,7 @@ async fn two_signature_unlocks() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_index = SlotIndex::from(10); let inputs = build_inputs( @@ -265,6 +255,7 @@ async fn two_signature_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: address_0.clone(), native_token: None, sender: None, @@ -277,6 +268,7 @@ async fn two_signature_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: address_1, native_token: None, sender: None, @@ -292,6 +284,7 @@ async fn two_signature_unlocks() -> Result<()> { let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: address_0, native_token: None, sender: None, @@ -309,7 +302,8 @@ async fn two_signature_unlocks() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -319,7 +313,7 @@ async fn two_signature_unlocks() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 2); @@ -328,18 +322,9 @@ async fn two_signature_unlocks() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; - - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } diff --git a/sdk/tests/client/signing/delegation.rs b/sdk/tests/client/signing/delegation.rs index 8fcb872c29..e1289923c5 100644 --- a/sdk/tests/client/signing/delegation.rs +++ b/sdk/tests/client/signing/delegation.rs @@ -6,22 +6,23 @@ use std::collections::BTreeMap; use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ - api::{ - transaction::validate_signed_transaction_payload_length, verify_semantic, GetAddressesOptions, - PreparedTransactionData, - }, + api::{GetAddressesOptions, PreparedTransactionData}, constants::SHIMMER_COIN_TYPE, secret::{SecretManage, SecretManager}, - Client, Result, + Client, }, types::block::{ context_input::{CommitmentContextInput, RewardContextInput}, input::{Input, UtxoInput}, output::DelegationId, - payload::{signed_transaction::Transaction, SignedTransactionPayload}, - protocol::protocol_parameters, + payload::{ + signed_transaction::{Transaction, TransactionCapabilityFlag}, + PayloadError, SignedTransactionPayload, + }, + protocol::iota_mainnet_protocol_parameters, rand::{address::rand_account_address, output::rand_delegation_id, slot::rand_slot_commitment_id}, semantic::TransactionFailureReason, + slot::SlotCommitmentHash, unlock::SignatureUnlock, }, }; @@ -33,7 +34,7 @@ use crate::client::{ }; #[tokio::test] -async fn valid_creation() -> Result<()> { +async fn valid_creation() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -46,7 +47,7 @@ async fn valid_creation() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id = rand_slot_commitment_id(); let slot_index = slot_commitment_id.slot_index(); @@ -54,6 +55,7 @@ async fn valid_creation() -> Result<()> { [( Basic { amount: 1_000_000, + mana: 0, address: address.clone(), native_token: None, sender: None, @@ -86,7 +88,8 @@ async fn valid_creation() -> Result<()> { .with_outputs(outputs) .with_creation_slot(slot_index + 1) .with_context_inputs([CommitmentContextInput::new(slot_commitment_id).into()]) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -96,7 +99,7 @@ async fn valid_creation() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -104,24 +107,15 @@ async fn valid_creation() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; - - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + tx_payload.validate_length()?; - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } #[tokio::test] -async fn creation_missing_commitment_input() -> Result<()> { +async fn creation_missing_commitment_input() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -134,7 +128,7 @@ async fn creation_missing_commitment_input() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id = rand_slot_commitment_id(); let slot_index = slot_commitment_id.slot_index(); @@ -142,6 +136,7 @@ async fn creation_missing_commitment_input() -> Result<()> { [( Basic { amount: 1_000_000, + mana: 0, address: address.clone(), native_token: None, sender: None, @@ -164,7 +159,7 @@ async fn creation_missing_commitment_input() -> Result<()> { end_epoch: 0, }]); - let transaction = Transaction::builder(protocol_parameters.network_id()) + let err = Transaction::builder(protocol_parameters.network_id()) .with_inputs( inputs .iter() @@ -173,43 +168,17 @@ async fn creation_missing_commitment_input() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; - - let prepared_transaction_data = PreparedTransactionData { - transaction, - inputs_data: inputs, - remainders: Vec::new(), - mana_rewards: Default::default(), - }; - - let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) - .await?; - - assert_eq!(unlocks.len(), 1); - assert_eq!((*unlocks).first().unwrap().kind(), SignatureUnlock::KIND); - - let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - - validate_signed_transaction_payload_length(&tx_payload)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters) + .unwrap_err(); - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; - - assert_eq!( - conflict, - Some(TransactionFailureReason::DelegationCommitmentInputMissing) - ); + assert_eq!(err, PayloadError::MissingCommitmentInputForDelegationOutput); Ok(()) } #[tokio::test] -async fn non_null_id_creation() -> Result<()> { +async fn non_null_id_creation() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -222,7 +191,7 @@ async fn non_null_id_creation() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id = rand_slot_commitment_id(); let slot_index = slot_commitment_id.slot_index(); @@ -230,6 +199,7 @@ async fn non_null_id_creation() -> Result<()> { [( Basic { amount: 1_000_000, + mana: 0, address: address.clone(), native_token: None, sender: None, @@ -246,7 +216,7 @@ async fn non_null_id_creation() -> Result<()> { amount: 1_000_000, delegation_amount: 1_000_000, delegation_id: rand_delegation_id(), - address: address, + address, validator_address: rand_account_address(), start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id), end_epoch: 0, @@ -261,7 +231,11 @@ async fn non_null_id_creation() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .with_context_inputs([ + CommitmentContextInput::new(SlotCommitmentHash::null().into_slot_commitment_id(0)).into(), + ]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -271,7 +245,7 @@ async fn non_null_id_creation() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -279,22 +253,17 @@ async fn non_null_id_creation() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::NewChainOutputHasNonZeroedId)); + assert_eq!(conflict, Err(TransactionFailureReason::NewChainOutputHasNonZeroedId)); Ok(()) } #[tokio::test] -async fn mismatch_amount_creation() -> Result<()> { +async fn mismatch_amount_creation() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -307,7 +276,7 @@ async fn mismatch_amount_creation() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id = rand_slot_commitment_id(); let slot_index = slot_commitment_id.slot_index(); @@ -315,6 +284,7 @@ async fn mismatch_amount_creation() -> Result<()> { [( Basic { amount: 1_000_000, + mana: 0, address: address.clone(), native_token: None, sender: None, @@ -331,7 +301,7 @@ async fn mismatch_amount_creation() -> Result<()> { amount: 1_000_000, delegation_amount: 1_500_000, delegation_id: DelegationId::null(), - address: address, + address, validator_address: rand_account_address(), start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id), end_epoch: 0, @@ -346,7 +316,11 @@ async fn mismatch_amount_creation() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .with_context_inputs([ + CommitmentContextInput::new(SlotCommitmentHash::null().into_slot_commitment_id(0)).into(), + ]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -356,7 +330,7 @@ async fn mismatch_amount_creation() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -364,22 +338,17 @@ async fn mismatch_amount_creation() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::DelegationAmountMismatch)); + assert_eq!(conflict, Err(TransactionFailureReason::DelegationAmountMismatch)); Ok(()) } #[tokio::test] -async fn non_zero_end_epoch_creation() -> Result<()> { +async fn non_zero_end_epoch_creation() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -392,7 +361,7 @@ async fn non_zero_end_epoch_creation() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id = rand_slot_commitment_id(); let slot_index = slot_commitment_id.slot_index(); @@ -400,6 +369,7 @@ async fn non_zero_end_epoch_creation() -> Result<()> { [( Basic { amount: 1_000_000, + mana: 0, address: address.clone(), native_token: None, sender: None, @@ -416,7 +386,7 @@ async fn non_zero_end_epoch_creation() -> Result<()> { amount: 1_000_000, delegation_amount: 1_000_000, delegation_id: DelegationId::null(), - address: address, + address, validator_address: rand_account_address(), start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id), end_epoch: 100, @@ -431,7 +401,11 @@ async fn non_zero_end_epoch_creation() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .with_context_inputs([ + CommitmentContextInput::new(SlotCommitmentHash::null().into_slot_commitment_id(0)).into(), + ]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -441,7 +415,7 @@ async fn non_zero_end_epoch_creation() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -449,22 +423,17 @@ async fn non_zero_end_epoch_creation() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::DelegationEndEpochNotZero)); + assert_eq!(conflict, Err(TransactionFailureReason::DelegationEndEpochNotZero)); Ok(()) } #[tokio::test] -async fn invalid_start_epoch_creation() -> Result<()> { +async fn invalid_start_epoch_creation() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -477,7 +446,7 @@ async fn invalid_start_epoch_creation() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id = rand_slot_commitment_id(); let slot_index = slot_commitment_id.slot_index(); @@ -485,6 +454,7 @@ async fn invalid_start_epoch_creation() -> Result<()> { [( Basic { amount: 1_000_000, + mana: 0, address: address.clone(), native_token: None, sender: None, @@ -501,7 +471,7 @@ async fn invalid_start_epoch_creation() -> Result<()> { amount: 1_000_000, delegation_amount: 1_000_000, delegation_id: DelegationId::null(), - address: address, + address, validator_address: rand_account_address(), start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id) + 5, end_epoch: 0, @@ -516,8 +486,9 @@ async fn invalid_start_epoch_creation() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) + .with_capabilities([TransactionCapabilityFlag::BurnMana]) .with_context_inputs([CommitmentContextInput::new(slot_commitment_id).into()]) - .finish_with_params(&protocol_parameters)?; + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -527,7 +498,7 @@ async fn invalid_start_epoch_creation() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -535,22 +506,17 @@ async fn invalid_start_epoch_creation() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::DelegationStartEpochInvalid)); + assert_eq!(conflict, Err(TransactionFailureReason::DelegationStartEpochInvalid)); Ok(()) } #[tokio::test] -async fn delay_not_null_id() -> Result<()> { +async fn delay_not_null_id() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -563,7 +529,7 @@ async fn delay_not_null_id() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id_1 = rand_slot_commitment_id(); let slot_index_1 = slot_commitment_id_1.slot_index(); let slot_commitment_id_2 = rand_slot_commitment_id() @@ -595,7 +561,7 @@ async fn delay_not_null_id() -> Result<()> { amount: 1_000_000, delegation_amount: 1_000_000, delegation_id, - address: address, + address, validator_address, start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id_1), end_epoch: *protocol_parameters.delegation_end_epoch(slot_commitment_id_2), @@ -614,7 +580,8 @@ async fn delay_not_null_id() -> Result<()> { CommitmentContextInput::new(slot_commitment_id_2).into(), RewardContextInput::new(0)?.into(), ]) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -624,7 +591,7 @@ async fn delay_not_null_id() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -632,25 +599,20 @@ async fn delay_not_null_id() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); assert_eq!( conflict, - Some(TransactionFailureReason::DelegationOutputTransitionedTwice) + Err(TransactionFailureReason::DelegationOutputTransitionedTwice) ); Ok(()) } #[tokio::test] -async fn delay_modified_amount() -> Result<()> { +async fn delay_modified_amount() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -663,7 +625,7 @@ async fn delay_modified_amount() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id_1 = rand_slot_commitment_id(); let slot_index_1 = slot_commitment_id_1.slot_index(); let slot_commitment_id_2 = rand_slot_commitment_id() @@ -695,7 +657,7 @@ async fn delay_modified_amount() -> Result<()> { amount: 1_000_000, delegation_amount: 900_000, delegation_id, - address: address, + address, validator_address, start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id_1), end_epoch: *protocol_parameters.delegation_end_epoch(slot_commitment_id_2), @@ -714,7 +676,8 @@ async fn delay_modified_amount() -> Result<()> { CommitmentContextInput::new(slot_commitment_id_2).into(), RewardContextInput::new(0)?.into(), ]) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -724,7 +687,7 @@ async fn delay_modified_amount() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -732,22 +695,17 @@ async fn delay_modified_amount() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::DelegationModified)); + assert_eq!(conflict, Err(TransactionFailureReason::DelegationModified)); Ok(()) } #[tokio::test] -async fn delay_modified_validator() -> Result<()> { +async fn delay_modified_validator() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -760,7 +718,7 @@ async fn delay_modified_validator() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id_1 = rand_slot_commitment_id(); let slot_index_1 = slot_commitment_id_1.slot_index(); let slot_commitment_id_2 = rand_slot_commitment_id() @@ -792,7 +750,7 @@ async fn delay_modified_validator() -> Result<()> { amount: 1_000_000, delegation_amount: 1_000_000, delegation_id, - address: address, + address, validator_address: rand_account_address(), start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id_1), end_epoch: *protocol_parameters.delegation_end_epoch(slot_commitment_id_2), @@ -811,7 +769,8 @@ async fn delay_modified_validator() -> Result<()> { CommitmentContextInput::new(slot_commitment_id_2).into(), RewardContextInput::new(0)?.into(), ]) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -821,7 +780,7 @@ async fn delay_modified_validator() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -829,22 +788,17 @@ async fn delay_modified_validator() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::DelegationModified)); + assert_eq!(conflict, Err(TransactionFailureReason::DelegationModified)); Ok(()) } #[tokio::test] -async fn delay_modified_start_epoch() -> Result<()> { +async fn delay_modified_start_epoch() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -857,7 +811,7 @@ async fn delay_modified_start_epoch() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id_1 = rand_slot_commitment_id(); let slot_index_1 = slot_commitment_id_1.slot_index(); let slot_commitment_id_2 = rand_slot_commitment_id() @@ -889,7 +843,7 @@ async fn delay_modified_start_epoch() -> Result<()> { amount: 1_000_000, delegation_amount: 1_000_000, delegation_id, - address: address, + address, validator_address, start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id_1) + 1, end_epoch: *protocol_parameters.delegation_end_epoch(slot_commitment_id_2), @@ -908,7 +862,8 @@ async fn delay_modified_start_epoch() -> Result<()> { CommitmentContextInput::new(slot_commitment_id_2).into(), RewardContextInput::new(0)?.into(), ]) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -918,7 +873,7 @@ async fn delay_modified_start_epoch() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -926,22 +881,17 @@ async fn delay_modified_start_epoch() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::DelegationModified)); + assert_eq!(conflict, Err(TransactionFailureReason::DelegationModified)); Ok(()) } #[tokio::test] -async fn delay_pre_registration_slot_end_epoch() -> Result<()> { +async fn delay_pre_registration_slot_end_epoch() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -954,7 +904,7 @@ async fn delay_pre_registration_slot_end_epoch() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id_1 = rand_slot_commitment_id(); let slot_index_1 = slot_commitment_id_1.slot_index(); let slot_commitment_id_2 = rand_slot_commitment_id() @@ -986,7 +936,7 @@ async fn delay_pre_registration_slot_end_epoch() -> Result<()> { amount: 1_000_000, delegation_amount: 1_000_000, delegation_id, - address: address, + address, validator_address, start_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id_1), end_epoch: *protocol_parameters.delegation_start_epoch(slot_commitment_id_1) + 1, @@ -1005,7 +955,8 @@ async fn delay_pre_registration_slot_end_epoch() -> Result<()> { CommitmentContextInput::new(slot_commitment_id_2).into(), RewardContextInput::new(0)?.into(), ]) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -1015,7 +966,7 @@ async fn delay_pre_registration_slot_end_epoch() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -1023,22 +974,17 @@ async fn delay_pre_registration_slot_end_epoch() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::DelegationEndEpochInvalid)); + assert_eq!(conflict, Err(TransactionFailureReason::DelegationEndEpochInvalid)); Ok(()) } #[tokio::test] -async fn destroy_null_id() -> Result<()> { +async fn destroy_null_id() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -1051,7 +997,7 @@ async fn destroy_null_id() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id_1 = rand_slot_commitment_id(); let slot_index_1 = slot_commitment_id_1.slot_index(); let slot_commitment_id_2 = rand_slot_commitment_id() @@ -1079,6 +1025,7 @@ async fn destroy_null_id() -> Result<()> { let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address, native_token: None, sender: None, @@ -1100,7 +1047,8 @@ async fn destroy_null_id() -> Result<()> { CommitmentContextInput::new(slot_commitment_id_2).into(), RewardContextInput::new(0)?.into(), ]) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let mut mana_rewards = BTreeMap::default(); mana_rewards.insert(*inputs[0].output_id(), 0); @@ -1113,7 +1061,7 @@ async fn destroy_null_id() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -1121,24 +1069,15 @@ async fn destroy_null_id() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; - - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + tx_payload.validate_length()?; - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } #[tokio::test] -async fn destroy_reward_missing() -> Result<()> { +async fn destroy_reward_missing() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address = secret_manager @@ -1151,7 +1090,7 @@ async fn destroy_reward_missing() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let slot_commitment_id_1 = rand_slot_commitment_id(); let slot_index_1 = slot_commitment_id_1.slot_index(); let slot_commitment_id_2 = rand_slot_commitment_id() @@ -1179,6 +1118,7 @@ async fn destroy_reward_missing() -> Result<()> { let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address, native_token: None, sender: None, @@ -1197,7 +1137,8 @@ async fn destroy_reward_missing() -> Result<()> { .with_outputs(outputs) .with_creation_slot(slot_index_2 + 1) .with_context_inputs([CommitmentContextInput::new(slot_commitment_id_2).into()]) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -1207,7 +1148,7 @@ async fn destroy_reward_missing() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 1); @@ -1215,16 +1156,11 @@ async fn destroy_reward_missing() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + let conflict = prepared_transaction_data.verify_semantic(protocol_parameters); - assert_eq!(conflict, Some(TransactionFailureReason::DelegationRewardInputMissing)); + assert_eq!(conflict, Err(TransactionFailureReason::DelegationRewardInputMissing)); Ok(()) } diff --git a/sdk/tests/client/signing/mod.rs b/sdk/tests/client/signing/mod.rs index f08d6bfbe0..668f1a20b3 100644 --- a/sdk/tests/client/signing/mod.rs +++ b/sdk/tests/client/signing/mod.rs @@ -11,13 +11,9 @@ use std::str::FromStr; use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ - api::{ - input_selection::InputSelection, transaction::validate_signed_transaction_payload_length, verify_semantic, - GetAddressesOptions, PreparedTransactionData, - }, + api::{transaction_builder::TransactionBuilder, GetAddressesOptions, PreparedTransactionData}, constants::SHIMMER_COIN_TYPE, secret::{SecretManage, SecretManager}, - Result, }, types::block::{ address::{AccountAddress, Address, NftAddress}, @@ -25,9 +21,8 @@ use iota_sdk::{ input::{Input, UtxoInput}, output::{AccountId, NftId}, payload::{signed_transaction::Transaction, SignedTransactionPayload}, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, slot::{SlotCommitmentHash, SlotCommitmentId, SlotIndex}, - unlock::{SignatureUnlock, Unlock}, }, }; use pretty_assertions::assert_eq; @@ -39,13 +34,13 @@ use crate::client::{ }; #[tokio::test] -async fn all_combined() -> Result<()> { +async fn all_combined() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic( // mnemonic needs to be hardcoded to make the ordering deterministic "mirror add nothing long orphan hat this rough scare gallery fork twelve old shrug voyage job table obscure mimic holiday possible proud giraffe fan".to_owned(), )?; - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let ed25519_bech32_addresses = secret_manager .generate_ed25519_addresses( @@ -80,6 +75,7 @@ async fn all_combined() -> Result<()> { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: nft_1.clone(), sender: None, @@ -90,6 +86,7 @@ async fn all_combined() -> Result<()> { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: ed25519_0.clone(), sender: None, @@ -100,6 +97,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: account_1.clone(), native_token: None, sender: None, @@ -112,6 +110,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: account_2.clone(), native_token: None, sender: None, @@ -124,6 +123,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: account_2, native_token: None, sender: None, @@ -136,6 +136,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: nft_2.clone(), native_token: None, sender: None, @@ -148,6 +149,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: nft_2, native_token: None, sender: None, @@ -160,6 +162,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: nft_4.clone(), native_token: None, sender: None, @@ -172,6 +175,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: ed25519_0.clone(), native_token: None, sender: None, @@ -184,6 +188,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: ed25519_1.clone(), native_token: None, sender: None, @@ -196,6 +201,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: ed25519_2.clone(), native_token: None, sender: None, @@ -208,6 +214,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: ed25519_2.clone(), native_token: None, sender: None, @@ -220,6 +227,7 @@ async fn all_combined() -> Result<()> { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: ed25519_0.clone(), sender: None, @@ -232,6 +240,7 @@ async fn all_combined() -> Result<()> { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: account_1.clone(), sender: None, @@ -245,6 +254,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 2_000_000, + mana: 0, address: ed25519_0.clone(), native_token: None, sender: None, @@ -257,6 +267,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 2_000_000, + mana: 0, address: ed25519_0.clone(), native_token: None, sender: None, @@ -269,6 +280,7 @@ async fn all_combined() -> Result<()> { ( Basic { amount: 2_000_000, + mana: 0, address: ed25519_0.clone(), native_token: None, sender: None, @@ -281,6 +293,7 @@ async fn all_combined() -> Result<()> { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_3, address: account_1.clone(), sender: None, @@ -293,6 +306,7 @@ async fn all_combined() -> Result<()> { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_4, address: account_1, sender: None, @@ -309,6 +323,7 @@ async fn all_combined() -> Result<()> { let outputs = build_outputs([ Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: nft_1, sender: None, @@ -316,6 +331,7 @@ async fn all_combined() -> Result<()> { }, Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: ed25519_0.clone(), sender: None, @@ -323,6 +339,7 @@ async fn all_combined() -> Result<()> { }, Basic { amount: 10_000_000, + mana: 0, address: ed25519_0.clone(), native_token: None, sender: None, @@ -332,6 +349,7 @@ async fn all_combined() -> Result<()> { }, Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: ed25519_0.clone(), sender: None, @@ -341,6 +359,7 @@ async fn all_combined() -> Result<()> { }, Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: ed25519_0.clone(), sender: None, @@ -350,6 +369,7 @@ async fn all_combined() -> Result<()> { }, Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_3, address: ed25519_0.clone(), sender: None, @@ -359,6 +379,7 @@ async fn all_combined() -> Result<()> { }, Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_4, address: ed25519_0.clone(), sender: None, @@ -368,7 +389,7 @@ async fn all_combined() -> Result<()> { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ed25519_0, ed25519_1, ed25519_2], @@ -376,7 +397,7 @@ async fn all_combined() -> Result<()> { slot_commitment_id, protocol_parameters.clone(), ) - .select() + .finish() .unwrap(); let transaction = Transaction::builder(protocol_parameters.network_id()) @@ -392,7 +413,7 @@ async fn all_combined() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index) - .finish_with_params(&protocol_parameters)?; + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -402,101 +423,20 @@ async fn all_combined() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; - assert_eq!(unlocks.len(), 15); - assert_eq!((*unlocks).first().unwrap().kind(), SignatureUnlock::KIND); - match (*unlocks).get(1).unwrap() { - Unlock::Reference(a) => { - assert_eq!(a.index(), 0); - } - _ => panic!("Invalid unlock 1"), - } - assert_eq!((*unlocks).get(2).unwrap().kind(), SignatureUnlock::KIND); - assert_eq!((*unlocks).get(3).unwrap().kind(), SignatureUnlock::KIND); - match (*unlocks).get(4).unwrap() { - Unlock::Reference(a) => { - assert_eq!(a.index(), 3); - } - _ => panic!("Invalid unlock 4"), - } - match (*unlocks).get(5).unwrap() { - Unlock::Reference(a) => { - assert_eq!(a.index(), 3); - } - _ => panic!("Invalid unlock 5"), - } - match (*unlocks).get(6).unwrap() { - Unlock::Account(a) => { - assert_eq!(a.index(), 5); - } - _ => panic!("Invalid unlock 6"), - } - match (*unlocks).get(7).unwrap() { - Unlock::Account(a) => { - assert_eq!(a.index(), 5); - } - _ => panic!("Invalid unlock 7"), - } - match (*unlocks).get(8).unwrap() { - Unlock::Reference(a) => { - assert_eq!(a.index(), 3); - } - _ => panic!("Invalid unlock 8"), - } - - match (*unlocks).get(9).unwrap() { - Unlock::Nft(a) => { - assert_eq!(a.index(), 8); - } - _ => panic!("Invalid unlock 9"), - } - match (*unlocks).get(10).unwrap() { - Unlock::Account(a) => { - assert_eq!(a.index(), 9); - } - _ => panic!("Invalid unlock 10"), - } - match (*unlocks).get(11).unwrap() { - Unlock::Account(a) => { - assert_eq!(a.index(), 9); - } - _ => panic!("Invalid unlock 11"), - } - match (*unlocks).get(12).unwrap() { - Unlock::Account(a) => { - assert_eq!(a.index(), 9); - } - _ => panic!("Invalid unlock 12"), - } - match (*unlocks).get(13).unwrap() { - Unlock::Nft(a) => { - assert_eq!(a.index(), 11); - } - _ => panic!("Invalid unlock 13"), - } - match (*unlocks).get(14).unwrap() { - Unlock::Nft(a) => { - assert_eq!(a.index(), 10); - } - _ => panic!("Invalid unlock 14"), - } + assert_eq!(unlocks.len(), 13); + assert_eq!(unlocks.iter().filter(|u| u.is_signature()).count(), 3); + assert_eq!(unlocks.iter().filter(|u| u.is_reference()).count(), 4); + assert_eq!(unlocks.iter().filter(|u| u.is_account()).count(), 3); + assert_eq!(unlocks.iter().filter(|u| u.is_nft()).count(), 3); let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; - - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; + tx_payload.validate_length()?; - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } diff --git a/sdk/tests/client/signing/nft.rs b/sdk/tests/client/signing/nft.rs index b42c300f8e..2c2fcc9d0c 100644 --- a/sdk/tests/client/signing/nft.rs +++ b/sdk/tests/client/signing/nft.rs @@ -6,20 +6,20 @@ use std::str::FromStr; use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ - api::{ - transaction::validate_signed_transaction_payload_length, verify_semantic, GetAddressesOptions, - PreparedTransactionData, - }, + api::{GetAddressesOptions, PreparedTransactionData}, constants::SHIMMER_COIN_TYPE, secret::{SecretManage, SecretManager}, - Client, Result, + Client, }, types::block::{ address::{Address, NftAddress}, input::{Input, UtxoInput}, output::NftId, - payload::{signed_transaction::Transaction, SignedTransactionPayload}, - protocol::protocol_parameters, + payload::{ + signed_transaction::{Transaction, TransactionCapabilityFlag}, + SignedTransactionPayload, + }, + protocol::iota_mainnet_protocol_parameters, slot::SlotIndex, unlock::{SignatureUnlock, Unlock}, }, @@ -33,7 +33,7 @@ use crate::client::{ }; #[tokio::test] -async fn nft_reference_unlocks() -> Result<()> { +async fn nft_reference_unlocks() -> Result<(), Box> { let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic()?)?; let address_0 = secret_manager @@ -46,7 +46,7 @@ async fn nft_reference_unlocks() -> Result<()> { .clone() .into_inner(); - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let nft_id = NftId::from_str(NFT_ID_1)?; let nft_address = Address::Nft(NftAddress::new(nft_id)); let slot_index = SlotIndex::from(10); @@ -56,7 +56,8 @@ async fn nft_reference_unlocks() -> Result<()> { ( Nft { amount: 1_000_000, - nft_id: nft_id, + mana: 0, + nft_id, address: address_0.clone(), sender: None, issuer: None, @@ -68,6 +69,7 @@ async fn nft_reference_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: nft_address.clone(), native_token: None, sender: None, @@ -80,6 +82,7 @@ async fn nft_reference_unlocks() -> Result<()> { ( Basic { amount: 1_000_000, + mana: 0, address: nft_address.clone(), native_token: None, sender: None, @@ -96,7 +99,8 @@ async fn nft_reference_unlocks() -> Result<()> { let outputs = build_outputs([ Nft { amount: 1_000_000, - nft_id: nft_id, + mana: 0, + nft_id, address: address_0, sender: None, issuer: None, @@ -105,6 +109,7 @@ async fn nft_reference_unlocks() -> Result<()> { }, Basic { amount: 2_000_000, + mana: 0, address: nft_address, native_token: None, sender: None, @@ -123,7 +128,8 @@ async fn nft_reference_unlocks() -> Result<()> { ) .with_outputs(outputs) .with_creation_slot(slot_index + 1) - .finish_with_params(&protocol_parameters)?; + .with_capabilities([TransactionCapabilityFlag::BurnMana]) + .finish_with_params(protocol_parameters)?; let prepared_transaction_data = PreparedTransactionData { transaction, @@ -133,7 +139,7 @@ async fn nft_reference_unlocks() -> Result<()> { }; let unlocks = secret_manager - .transaction_unlocks(&prepared_transaction_data, &protocol_parameters) + .transaction_unlocks(&prepared_transaction_data, protocol_parameters) .await?; assert_eq!(unlocks.len(), 3); @@ -153,18 +159,9 @@ async fn nft_reference_unlocks() -> Result<()> { let tx_payload = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?; - validate_signed_transaction_payload_length(&tx_payload)?; + tx_payload.validate_length()?; - let conflict = verify_semantic( - &prepared_transaction_data.inputs_data, - &tx_payload, - prepared_transaction_data.mana_rewards, - protocol_parameters, - )?; - - if let Some(conflict) = conflict { - panic!("{conflict:?}, with {tx_payload:#?}"); - } + prepared_transaction_data.verify_semantic(protocol_parameters)?; Ok(()) } diff --git a/sdk/tests/client/input_selection/account_outputs.rs b/sdk/tests/client/transaction_builder/account_outputs.rs similarity index 71% rename from sdk/tests/client/input_selection/account_outputs.rs rename to sdk/tests/client/transaction_builder/account_outputs.rs index 64a55c4fcb..a04fe40d95 100644 --- a/sdk/tests/client/input_selection/account_outputs.rs +++ b/sdk/tests/client/transaction_builder/account_outputs.rs @@ -5,24 +5,26 @@ use std::str::FromStr; use iota_sdk::{ client::{ - api::input_selection::{Burn, Error, InputSelection, Requirement}, + api::transaction_builder::{Burn, Requirement, TransactionBuilder, TransactionBuilderError}, secret::types::InputSigningData, }, types::block::{ - address::Address, + address::{Address, ImplicitAccountCreationAddress}, mana::ManaAllotment, output::{ - feature::SenderFeature, unlock_condition::AddressUnlockCondition, AccountId, AccountOutputBuilder, - BasicOutputBuilder, Output, + feature::{BlockIssuerFeature, BlockIssuerKeys, Ed25519PublicKeyHashBlockIssuerKey}, + unlock_condition::AddressUnlockCondition, + AccountId, AccountOutputBuilder, BasicOutputBuilder, Output, }, - protocol::protocol_parameters, + payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, + protocol::iota_mainnet_protocol_parameters, rand::output::{rand_output_id_with_slot_index, rand_output_metadata_with_id}, }, }; use pretty_assertions::{assert_eq, assert_ne}; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, + assert_remainder_or_return, build_inputs, build_outputs, unsorted_eq, Build::{Account, Basic}, ACCOUNT_ID_0, ACCOUNT_ID_1, ACCOUNT_ID_2, BECH32_ADDRESS_ACCOUNT_1, BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1, BECH32_ADDRESS_NFT_1, SLOT_COMMITMENT_ID, SLOT_INDEX, @@ -30,13 +32,14 @@ use crate::client::{ #[test] fn input_account_eq_output_account() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -48,13 +51,14 @@ fn input_account_eq_output_account() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -62,22 +66,23 @@ fn input_account_eq_output_account() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn transition_account_id_zero() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( [( Account { amount: 1_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -90,13 +95,14 @@ fn transition_account_id_zero() { let account_id = AccountId::from(inputs[0].output_id()); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -104,16 +110,16 @@ fn transition_account_id_zero() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } // #[test] // fn input_amount_lt_output_amount() { -// let protocol_parameters = protocol_parameters(); +// let protocol_parameters = iota_mainnet_protocol_parameters().clone(); // let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); // let inputs = build_inputs([Account( @@ -138,7 +144,7 @@ fn transition_account_id_zero() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs, // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -158,7 +164,7 @@ fn transition_account_id_zero() { // #[test] // fn input_amount_lt_output_amount_2() { -// let protocol_parameters = protocol_parameters(); +// let protocol_parameters = iota_mainnet_protocol_parameters().clone(); // let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); // let inputs = build_inputs([ @@ -186,7 +192,7 @@ fn transition_account_id_zero() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs, // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -206,7 +212,7 @@ fn transition_account_id_zero() { // #[test] // fn basic_output_with_account_input() { -// let protocol_parameters = protocol_parameters(); +// let protocol_parameters = iota_mainnet_protocol_parameters().clone(); // let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); // let inputs = build_inputs([Account( @@ -231,7 +237,7 @@ fn transition_account_id_zero() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs.clone(), // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -247,13 +253,14 @@ fn transition_account_id_zero() { #[test] fn create_account() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -267,13 +274,14 @@ fn create_account() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -281,7 +289,7 @@ fn create_account() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -299,13 +307,14 @@ fn create_account() { #[test] fn burn_account() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Account { amount: 2_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -317,6 +326,7 @@ fn burn_account() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -325,7 +335,7 @@ fn burn_account() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -334,16 +344,20 @@ fn burn_account() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id_2)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } // #[test] // fn not_enough_storage_deposit_for_remainder() { -// let protocol_parameters = protocol_parameters(); +// let protocol_parameters = iota_mainnet_protocol_parameters().clone(); // let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); // let inputs = build_inputs([Account( @@ -369,7 +383,7 @@ fn burn_account() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs, // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -388,13 +402,14 @@ fn burn_account() { #[test] fn missing_input_for_account_output() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -408,13 +423,14 @@ fn missing_input_for_account_output() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -422,17 +438,17 @@ fn missing_input_for_account_output() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Account(account_id))) if account_id == account_id_2 + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Account(account_id))) if account_id == account_id_2 )); } #[test] fn missing_input_for_account_output_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); @@ -441,6 +457,7 @@ fn missing_input_for_account_output_2() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -451,6 +468,7 @@ fn missing_input_for_account_output_2() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -465,13 +483,14 @@ fn missing_input_for_account_output_2() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -479,23 +498,24 @@ fn missing_input_for_account_output_2() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Account(account_id))) if account_id == account_id_2 + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Account(account_id))) if account_id == account_id_2 )); } #[test] fn missing_input_for_account_output_but_created() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -509,13 +529,14 @@ fn missing_input_for_account_output_but_created() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -523,14 +544,14 @@ fn missing_input_for_account_output_but_created() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(selected.is_ok()); } #[test] fn account_in_output_and_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -538,6 +559,7 @@ fn account_in_output_and_sender() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -548,6 +570,7 @@ fn account_in_output_and_sender() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -565,6 +588,7 @@ fn account_in_output_and_sender() { .unwrap(); let mut outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), @@ -574,7 +598,7 @@ fn account_in_output_and_sender() { }]); outputs.push(account_output); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -582,22 +606,23 @@ fn account_in_output_and_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn missing_ed25519_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -609,13 +634,14 @@ fn missing_ed25519_sender() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -623,23 +649,24 @@ fn missing_ed25519_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() )); } #[test] fn missing_ed25519_issuer_created() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -653,13 +680,14 @@ fn missing_ed25519_issuer_created() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -667,23 +695,24 @@ fn missing_ed25519_issuer_created() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() )); } #[test] fn missing_ed25519_issuer_transition() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( [( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -695,13 +724,14 @@ fn missing_ed25519_issuer_transition() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -709,20 +739,21 @@ fn missing_ed25519_issuer_transition() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(selected.is_ok()); } #[test] fn missing_account_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -734,13 +765,14 @@ fn missing_account_sender() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -748,23 +780,24 @@ fn missing_account_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() )); } #[test] fn missing_account_issuer_created() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -778,13 +811,14 @@ fn missing_account_issuer_created() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -792,23 +826,24 @@ fn missing_account_issuer_created() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() )); } #[test] fn missing_account_issuer_transition() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -820,13 +855,14 @@ fn missing_account_issuer_transition() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -834,20 +870,21 @@ fn missing_account_issuer_transition() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(selected.is_ok()); } #[test] fn missing_nft_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -859,13 +896,14 @@ fn missing_nft_sender() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: Some(Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap()), issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -873,23 +911,24 @@ fn missing_nft_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() )); } #[test] fn missing_nft_issuer_created() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -903,13 +942,14 @@ fn missing_nft_issuer_created() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: Some(Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap()), }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -917,23 +957,24 @@ fn missing_nft_issuer_created() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() )); } #[test] fn missing_nft_issuer_transition() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( [( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -945,13 +986,14 @@ fn missing_nft_issuer_transition() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: Some(Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap()), }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -959,14 +1001,14 @@ fn missing_nft_issuer_transition() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(selected.is_ok()); } #[test] fn increase_account_amount() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -974,6 +1016,7 @@ fn increase_account_amount() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -984,6 +1027,7 @@ fn increase_account_amount() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -998,13 +1042,14 @@ fn increase_account_amount() { ); let outputs = build_outputs([Account { amount: 3_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1012,16 +1057,16 @@ fn increase_account_amount() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn decrease_account_amount() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -1029,6 +1074,7 @@ fn decrease_account_amount() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1039,6 +1085,7 @@ fn decrease_account_amount() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1053,13 +1100,14 @@ fn decrease_account_amount() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1067,7 +1115,7 @@ fn decrease_account_amount() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); @@ -1076,19 +1124,19 @@ fn decrease_account_amount() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } }); } #[test] fn prefer_basic_to_account() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -1096,6 +1144,7 @@ fn prefer_basic_to_account() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1106,6 +1155,7 @@ fn prefer_basic_to_account() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1120,6 +1170,7 @@ fn prefer_basic_to_account() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1128,7 +1179,7 @@ fn prefer_basic_to_account() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1136,7 +1187,7 @@ fn prefer_basic_to_account() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); @@ -1146,7 +1197,7 @@ fn prefer_basic_to_account() { #[test] fn take_amount_from_account_to_fund_basic() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -1154,6 +1205,7 @@ fn take_amount_from_account_to_fund_basic() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1164,6 +1216,7 @@ fn take_amount_from_account_to_fund_basic() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1178,6 +1231,7 @@ fn take_amount_from_account_to_fund_basic() { ); let outputs = build_outputs([Basic { amount: 1_200_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1186,7 +1240,7 @@ fn take_amount_from_account_to_fund_basic() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1194,7 +1248,7 @@ fn take_amount_from_account_to_fund_basic() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1218,7 +1272,7 @@ fn take_amount_from_account_to_fund_basic() { #[test] fn account_burn_should_validate_account_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -1226,6 +1280,7 @@ fn account_burn_should_validate_account_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1238,6 +1293,7 @@ fn account_burn_should_validate_account_sender() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1250,6 +1306,7 @@ fn account_burn_should_validate_account_sender() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), @@ -1258,7 +1315,7 @@ fn account_burn_should_validate_account_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1267,28 +1324,32 @@ fn account_burn_should_validate_account_sender() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id_1)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); // One output should be added for the remainder. assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ) } }); } #[test] fn account_burn_should_validate_account_address() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -1296,6 +1357,7 @@ fn account_burn_should_validate_account_address() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap(), native_token: None, sender: None, @@ -1308,6 +1370,7 @@ fn account_burn_should_validate_account_address() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1320,6 +1383,7 @@ fn account_burn_should_validate_account_address() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1328,7 +1392,7 @@ fn account_burn_should_validate_account_address() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1337,34 +1401,39 @@ fn account_burn_should_validate_account_address() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id_1)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); // One output should be added for the remainder. assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ) } }); } #[test] fn transitioned_zero_account_id_no_longer_is_zero() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( [( Account { amount: 2_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1376,6 +1445,7 @@ fn transitioned_zero_account_id_no_longer_is_zero() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1384,7 +1454,7 @@ fn transitioned_zero_account_id_no_longer_is_zero() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1392,7 +1462,7 @@ fn transitioned_zero_account_id_no_longer_is_zero() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1416,7 +1486,7 @@ fn transitioned_zero_account_id_no_longer_is_zero() { #[test] fn two_accounts_required() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); @@ -1425,6 +1495,7 @@ fn two_accounts_required() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1435,6 +1506,7 @@ fn two_accounts_required() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1447,6 +1519,7 @@ fn two_accounts_required() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1455,7 +1528,7 @@ fn two_accounts_required() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1463,7 +1536,7 @@ fn two_accounts_required() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1495,13 +1568,14 @@ fn two_accounts_required() { #[test] fn state_controller_sender_required() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( [( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1513,6 +1587,7 @@ fn state_controller_sender_required() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()), @@ -1521,7 +1596,7 @@ fn state_controller_sender_required() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1529,7 +1604,7 @@ fn state_controller_sender_required() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1539,13 +1614,14 @@ fn state_controller_sender_required() { #[test] fn state_controller_sender_required_already_selected() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( [( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1558,6 +1634,7 @@ fn state_controller_sender_required_already_selected() { let outputs = build_outputs([ Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1565,6 +1642,7 @@ fn state_controller_sender_required_already_selected() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()), @@ -1574,7 +1652,7 @@ fn state_controller_sender_required_already_selected() { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1583,22 +1661,23 @@ fn state_controller_sender_required_already_selected() { protocol_parameters, ) .with_required_inputs([*inputs[0].output_id()]) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn state_transition_and_required() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( [( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1610,13 +1689,14 @@ fn state_transition_and_required() { ); let outputs = build_outputs([Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1625,22 +1705,23 @@ fn state_transition_and_required() { protocol_parameters, ) .with_required_inputs([*inputs[0].output_id()]) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn remainder_address_in_state_controller() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( [( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1652,13 +1733,14 @@ fn remainder_address_in_state_controller() { ); let outputs = build_outputs([Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, issuer: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1666,7 +1748,7 @@ fn remainder_address_in_state_controller() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1674,39 +1756,42 @@ fn remainder_address_in_state_controller() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ) } }); } #[test] fn min_allot_account_mana() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); - let mut inputs = Vec::new(); let mana_input_amount = 1_000_000; + let required_allotment = 7864; - let account_output = AccountOutputBuilder::new_with_amount(2_000_000, account_id_1) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .with_mana(mana_input_amount) - .finish_output() - .unwrap(); - inputs.push(InputSigningData { - output: account_output, - output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), - chain: None, - }); + let inputs = build_inputs( + [( + Account { + amount: 2_000_000, + mana: mana_input_amount, + account_id: account_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }, + None, + )], + Some(SLOT_INDEX), + ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()), @@ -1715,7 +1800,7 @@ fn min_allot_account_mana() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1723,34 +1808,33 @@ fn min_allot_account_mana() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .with_min_mana_allotment(account_id_1, 500) - .select() + .with_min_mana_allotment(account_id_1, 2) + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); assert_eq!(selected.transaction.allotments().len(), 1); - let mana_cost = 230_000; assert_eq!( selected.transaction.allotments()[0], - ManaAllotment::new(account_id_1, mana_cost).unwrap() + ManaAllotment::new(account_id_1, required_allotment).unwrap() ); assert_eq!( selected.transaction.outputs()[1].as_account().mana(), - mana_input_amount - mana_cost + mana_input_amount - required_allotment ); } #[test] fn min_allot_account_mana_additional() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); - let additional_allotment = 1000; - let txn_required_mana_allotment = 240_000; + let provided_allotment = 1000; + let required_allotment = 7900; // The account does not have enough to cover the requirement - let account_mana = txn_required_mana_allotment - 100; + let account_mana = required_allotment - 100; // But there is additional available mana elsewhere let additional_available_mana = 111; @@ -1783,13 +1867,10 @@ fn min_allot_account_mana_additional() { .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) - .add_feature(SenderFeature::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) .finish_output() .unwrap()]; - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1797,9 +1878,9 @@ fn min_allot_account_mana_additional() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .with_min_mana_allotment(account_id_1, 500) - .with_mana_allotments(Some((account_id_1, additional_allotment))) - .select() + .with_min_mana_allotment(account_id_1, 2) + .with_mana_allotments(Some((account_id_1, provided_allotment))) + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1809,26 +1890,26 @@ fn min_allot_account_mana_additional() { assert_eq!(selected.transaction.allotments().len(), 1); assert_eq!( selected.transaction.allotments()[0], - ManaAllotment::new(account_id_1, txn_required_mana_allotment).unwrap() + ManaAllotment::new(account_id_1, required_allotment).unwrap() ); assert_eq!( selected.transaction.outputs().iter().map(|o| o.mana()).sum::(), - account_mana + additional_available_mana - txn_required_mana_allotment + account_mana + additional_available_mana - required_allotment ); } #[test] fn min_allot_account_mana_cannot_select_additional() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); - let additional_allotment = 1000; - let txn_required_mana_allotment = 271_000; + let provided_allotment = 1000; + let required_allotment = 7900; // The account does not have enough to cover the requirement - let account_mana = txn_required_mana_allotment - 100; + let account_mana = required_allotment - 100; // But there is additional available mana elsewhere - let additional_available_mana = additional_allotment + 111; + let additional_available_mana = 111; let inputs = [ AccountOutputBuilder::new_with_amount(2_000_000, account_id_1) @@ -1839,10 +1920,10 @@ fn min_allot_account_mana_cannot_select_additional() { .finish_output() .unwrap(), BasicOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters()) - .with_mana(additional_available_mana) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) + .with_mana(additional_available_mana) .finish_output() .unwrap(), ]; @@ -1855,7 +1936,7 @@ fn min_allot_account_mana_cannot_select_additional() { }) .collect::>(); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), None, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1863,60 +1944,142 @@ fn min_allot_account_mana_cannot_select_additional() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .with_min_mana_allotment(account_id_1, 500) - .with_mana_allotments(Some((account_id_2, additional_allotment))) + .with_min_mana_allotment(account_id_1, 2) + .with_mana_allotments(Some((account_id_2, provided_allotment))) .with_required_inputs([*inputs[0].output_id()]) .disable_additional_input_selection() - .select() + .finish() .unwrap_err(); assert!( - matches!(selected, Error::AdditionalInputsRequired(_)), + matches!(selected, TransactionBuilderError::AdditionalInputsRequired(_)), "expected AdditionalInputsRequired, found {selected:?}" ); } #[test] fn min_allot_account_mana_requirement_twice() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); - let inputs = [ - AccountOutputBuilder::new_with_amount(2_000_000, account_id_1) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .with_mana(1000) - .finish_output() - .unwrap(), - BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .with_mana(100) - .finish_output() - .unwrap(), - ]; - let inputs = inputs - .into_iter() - .map(|input| InputSigningData { - output: input, - output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), - chain: None, - }) - .collect::>(); + let required_allotment = 7900; - let outputs = build_outputs([Basic { + let inputs = build_inputs( + [ + ( + Account { + amount: 2_000_000, + mana: required_allotment, + account_id: account_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }, + None, + ), + ( + Basic { + amount: 1_000_000, + mana: 100, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_min_mana_allotment(account_id_1, 2) + .with_required_inputs([*inputs[1].output_id()]) + .finish() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 2); + let account_output = selected + .transaction + .outputs() + .iter() + .filter_map(Output::as_account_opt) + .find(|o| o.account_id() == &account_id_1) + .unwrap(); + assert_eq!(selected.transaction.allotments().len(), 1); + assert_eq!( + selected.transaction.allotments()[0], + ManaAllotment::new(account_id_1, required_allotment).unwrap() + ); + assert_eq!(account_output.mana(), 100); +} + +#[test] +fn min_allot_account_mana_requirement_covered() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + + let provided_allotment = 7900; + + let inputs = build_inputs( + [ + ( + Account { + amount: 2_000_000, + mana: provided_allotment - 100, + account_id: account_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }, + None, + ), + ( + Basic { + amount: 1_000_000, + mana: 100, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); + + // Must manually add account output with mana reduced for the manual allotment + let account_output = AccountOutputBuilder::from(inputs[0].output.as_account()) + .with_mana(0) + .finish_output() + .unwrap(); + + let mut outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, - sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()), + sender: None, sdruc: None, timelock: None, expiration: None, }]); + outputs.push(account_output); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1925,64 +2088,77 @@ fn min_allot_account_mana_requirement_twice() { protocol_parameters, ) .with_min_mana_allotment(account_id_1, 2) - .select() + .with_mana_allotments(Some((account_id_1, provided_allotment))) + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); assert_eq!(selected.transaction.allotments().len(), 1); - let mana_cost = 960; assert_eq!( selected.transaction.allotments()[0], - ManaAllotment::new(account_id_1, mana_cost).unwrap() + ManaAllotment::new(account_id_1, provided_allotment).unwrap() ); - assert_eq!(selected.transaction.outputs()[1].as_account().mana(), 140); + assert_eq!(selected.transaction.outputs()[1].as_account().mana(), 0); } #[test] -fn min_allot_account_mana_requirement_covered() { - let protocol_parameters = protocol_parameters(); +fn min_allot_account_mana_requirement_covered_2() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); - let additional_allotment = 1100; + let provided_allotment = 7900; - let inputs = [ - AccountOutputBuilder::new_with_amount(2_000_000, account_id_1) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .with_mana(1000) - .finish_output() - .unwrap(), - BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .with_mana(100) - .finish_output() - .unwrap(), - ]; - let inputs = inputs - .into_iter() - .map(|input| InputSigningData { - output: input, - output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), - chain: None, - }) - .collect::>(); + let inputs = build_inputs( + [ + ( + Account { + amount: 2_000_000, + mana: 100, + account_id: account_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }, + None, + ), + ( + Basic { + amount: 1_000_000, + mana: provided_allotment - 100, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); - let outputs = build_outputs([Basic { + // Must manually add account output with mana reduced for the manual allotment + let account_output = AccountOutputBuilder::from(inputs[0].output.as_account()) + .with_mana(0) + .finish_output() + .unwrap(); + + let mut outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, - sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()), + sender: None, sdruc: None, timelock: None, expiration: None, }]); + outputs.push(account_output); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1991,8 +2167,8 @@ fn min_allot_account_mana_requirement_covered() { protocol_parameters, ) .with_min_mana_allotment(account_id_1, 2) - .with_mana_allotments(Some((account_id_1, additional_allotment))) - .select() + .with_mana_allotments(Some((account_id_1, provided_allotment))) + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -2001,54 +2177,55 @@ fn min_allot_account_mana_requirement_covered() { assert_eq!(selected.transaction.allotments().len(), 1); assert_eq!( selected.transaction.allotments()[0], - ManaAllotment::new(account_id_1, additional_allotment).unwrap() + ManaAllotment::new(account_id_1, provided_allotment).unwrap() ); assert_eq!(selected.transaction.outputs()[1].as_account().mana(), 0); } #[test] -fn min_allot_account_mana_requirement_covered_2() { - let protocol_parameters = protocol_parameters(); +fn implicit_account_transition() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + let ed25519_address = Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(); - let additional_allotment = 1100; - - let inputs = [ - AccountOutputBuilder::new_with_amount(2_000_000, account_id_1) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .with_mana(100) - .finish_output() - .unwrap(), - BasicOutputBuilder::new_with_amount(1_000_000) + let inputs = build_inputs( + [( + Basic { + amount: 1_000_000, + mana: 10000, + address: Address::ImplicitAccountCreation(ImplicitAccountCreationAddress::new( + **ed25519_address.as_ed25519(), + )), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + )], + Some(SLOT_INDEX), + ); + let input_output_id = *inputs[0].output_id(); + let account_id = AccountId::from(&input_output_id); + let outputs = vec![ + AccountOutputBuilder::new_with_amount(1_000_000, account_id) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) - .with_mana(1000) + .with_features([BlockIssuerFeature::new( + u32::MAX, + BlockIssuerKeys::from_vec(vec![ + Ed25519PublicKeyHashBlockIssuerKey::new(**ed25519_address.as_ed25519()).into(), + ]) + .unwrap(), + ) + .unwrap()]) .finish_output() .unwrap(), ]; - let inputs = inputs - .into_iter() - .map(|input| InputSigningData { - output: input, - output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), - chain: None, - }) - .collect::>(); - - let outputs = build_outputs([Basic { - amount: 1_000_000, - address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - native_token: None, - sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()), - sdruc: None, - timelock: None, - expiration: None, - }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -2056,18 +2233,134 @@ fn min_allot_account_mana_requirement_covered_2() { SLOT_COMMITMENT_ID, protocol_parameters, ) + .with_required_inputs(vec![input_output_id]) .with_min_mana_allotment(account_id_1, 2) - .with_mana_allotments(Some((account_id_1, additional_allotment))) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert_eq!(selected.transaction.outputs().len(), 2); - assert!(selected.transaction.outputs().contains(&outputs[0])); + assert_eq!(selected.transaction.outputs().len(), 1); + assert!(selected.transaction.outputs()[0].is_account()); assert_eq!(selected.transaction.allotments().len(), 1); assert_eq!( selected.transaction.allotments()[0], - ManaAllotment::new(account_id_1, additional_allotment).unwrap() + ManaAllotment::new(account_id_1, 9948).unwrap() ); - assert_eq!(selected.transaction.outputs()[1].as_account().mana(), 0); + // One remainder Mana + assert_eq!(selected.transaction.outputs()[0].mana(), 52); +} + +#[test] +fn auto_transition_account_less_than_min() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + + let small_amount = 5; + + let inputs = build_inputs( + [( + Account { + amount: small_amount, + mana: 0, + account_id: account_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }, + None, + )], + Some(SLOT_INDEX), + ); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .with_required_inputs([*inputs[0].output_id()]) + .finish() + .unwrap_err(); + + let min_amount = AccountOutputBuilder::from(inputs[0].output.as_account()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) + .finish_output() + .unwrap() + .amount(); + + assert_eq!( + selected, + TransactionBuilderError::InsufficientAmount { + found: small_amount, + required: min_amount + }, + ); +} + +#[test] +fn auto_transition_account_less_than_min_additional() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + + let small_amount = 5; + + let inputs = build_inputs( + [ + ( + Account { + amount: small_amount, + mana: 0, + account_id: account_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }, + None, + ), + ( + Basic { + amount: 1_000_000, + mana: 0, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + native_token: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .with_required_inputs([*inputs[0].output_id()]) + .finish() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 2); + let min_amount = AccountOutputBuilder::from(inputs[0].output.as_account()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) + .finish_output() + .unwrap() + .amount(); + let account_output = selected + .transaction + .outputs() + .iter() + .filter_map(Output::as_account_opt) + .find(|o| o.account_id() == &account_id_1) + .unwrap(); + assert_eq!(account_output.amount(), min_amount); } diff --git a/sdk/tests/client/input_selection/basic_outputs.rs b/sdk/tests/client/transaction_builder/basic_outputs.rs similarity index 84% rename from sdk/tests/client/input_selection/basic_outputs.rs rename to sdk/tests/client/transaction_builder/basic_outputs.rs index 6cc3658429..aa9f36d366 100644 --- a/sdk/tests/client/input_selection/basic_outputs.rs +++ b/sdk/tests/client/transaction_builder/basic_outputs.rs @@ -4,17 +4,18 @@ use std::str::FromStr; use iota_sdk::{ - client::api::input_selection::{Error, InputSelection, Requirement}, + client::api::transaction_builder::{Requirement, TransactionBuilder, TransactionBuilderError}, types::block::{ address::{Address, AddressCapabilities, MultiAddress, RestrictedAddress, WeightedAddress}, - output::{AccountId, NftId}, - protocol::protocol_parameters, + mana::ManaAllotment, + output::{unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, NftId}, + protocol::iota_mainnet_protocol_parameters, }, }; use pretty_assertions::assert_eq; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, + assert_remainder_or_return, build_inputs, build_outputs, unsorted_eq, Build::{Account, Basic, Nft}, ACCOUNT_ID_0, ACCOUNT_ID_1, BECH32_ADDRESS_ACCOUNT_1, BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1, BECH32_ADDRESS_ED25519_2, BECH32_ADDRESS_NFT_1, BECH32_ADDRESS_REMAINDER, NFT_ID_0, NFT_ID_1, SLOT_COMMITMENT_ID, @@ -23,12 +24,13 @@ use crate::client::{ #[test] fn input_amount_equal_output_amount() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -42,6 +44,7 @@ fn input_amount_equal_output_amount() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -50,7 +53,7 @@ fn input_amount_equal_output_amount() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -58,21 +61,22 @@ fn input_amount_equal_output_amount() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn input_amount_lower_than_output_amount() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -86,6 +90,7 @@ fn input_amount_lower_than_output_amount() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -94,7 +99,7 @@ fn input_amount_lower_than_output_amount() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -102,11 +107,11 @@ fn input_amount_lower_than_output_amount() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientAmount { + Err(TransactionBuilderError::InsufficientAmount { found: 1_000_000, required: 2_000_000, }) @@ -115,13 +120,14 @@ fn input_amount_lower_than_output_amount() { #[test] fn input_amount_lower_than_output_amount_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -134,6 +140,7 @@ fn input_amount_lower_than_output_amount_2() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -148,6 +155,7 @@ fn input_amount_lower_than_output_amount_2() { ); let outputs = build_outputs([Basic { amount: 3_500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -156,7 +164,7 @@ fn input_amount_lower_than_output_amount_2() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -164,11 +172,11 @@ fn input_amount_lower_than_output_amount_2() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientAmount { + Err(TransactionBuilderError::InsufficientAmount { found: 3_000_000, required: 3_500_000, }) @@ -177,12 +185,13 @@ fn input_amount_lower_than_output_amount_2() { #[test] fn input_amount_greater_than_output_amount() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -196,6 +205,7 @@ fn input_amount_greater_than_output_amount() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -204,7 +214,7 @@ fn input_amount_greater_than_output_amount() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -212,7 +222,7 @@ fn input_amount_greater_than_output_amount() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -221,25 +231,26 @@ fn input_amount_greater_than_output_amount() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } }); } #[test] fn input_amount_greater_than_output_amount_with_remainder_address() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let remainder_address = Address::try_from_bech32(BECH32_ADDRESS_REMAINDER).unwrap(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -253,6 +264,7 @@ fn input_amount_greater_than_output_amount_with_remainder_address() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -261,7 +273,7 @@ fn input_amount_greater_than_output_amount_with_remainder_address() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -270,7 +282,7 @@ fn input_amount_greater_than_output_amount_with_remainder_address() { protocol_parameters, ) .with_remainder_address(remainder_address) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -279,25 +291,26 @@ fn input_amount_greater_than_output_amount_with_remainder_address() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_500_000, Address::try_from_bech32(BECH32_ADDRESS_REMAINDER).unwrap(), None, - )); + ); } }); } #[test] fn two_same_inputs_one_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -310,6 +323,7 @@ fn two_same_inputs_one_needed() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -324,6 +338,7 @@ fn two_same_inputs_one_needed() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -332,7 +347,7 @@ fn two_same_inputs_one_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -340,7 +355,7 @@ fn two_same_inputs_one_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); // One input has enough amount. @@ -350,25 +365,26 @@ fn two_same_inputs_one_needed() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } }); } #[test] fn two_inputs_one_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -381,6 +397,7 @@ fn two_inputs_one_needed() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -395,6 +412,7 @@ fn two_inputs_one_needed() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -403,7 +421,7 @@ fn two_inputs_one_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -411,22 +429,23 @@ fn two_inputs_one_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data, [inputs[0].clone()]); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_inputs_one_needed_reversed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -439,6 +458,7 @@ fn two_inputs_one_needed_reversed() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -453,6 +473,7 @@ fn two_inputs_one_needed_reversed() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -461,7 +482,7 @@ fn two_inputs_one_needed_reversed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -469,22 +490,23 @@ fn two_inputs_one_needed_reversed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data, [inputs[1].clone()]); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_inputs_both_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -497,6 +519,7 @@ fn two_inputs_both_needed() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -511,6 +534,7 @@ fn two_inputs_both_needed() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -519,7 +543,7 @@ fn two_inputs_both_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -527,22 +551,23 @@ fn two_inputs_both_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_inputs_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -555,6 +580,7 @@ fn two_inputs_remainder() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -569,6 +595,7 @@ fn two_inputs_remainder() { ); let outputs = build_outputs([Basic { amount: 2_500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -577,7 +604,7 @@ fn two_inputs_remainder() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -585,7 +612,7 @@ fn two_inputs_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -594,12 +621,12 @@ fn two_inputs_remainder() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } }); } @@ -629,7 +656,7 @@ fn two_inputs_remainder() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs, // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -648,7 +675,7 @@ fn two_inputs_remainder() { #[test] fn ed25519_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let sender = Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(); let inputs = build_inputs( @@ -656,6 +683,7 @@ fn ed25519_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -668,6 +696,7 @@ fn ed25519_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -680,6 +709,7 @@ fn ed25519_sender() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -692,6 +722,7 @@ fn ed25519_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -704,6 +735,7 @@ fn ed25519_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -718,6 +750,7 @@ fn ed25519_sender() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), @@ -726,7 +759,7 @@ fn ed25519_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [ @@ -737,7 +770,7 @@ fn ed25519_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); // Sender + another for amount @@ -754,12 +787,13 @@ fn ed25519_sender() { #[test] fn missing_ed25519_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 5_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -773,6 +807,7 @@ fn missing_ed25519_sender() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), @@ -781,7 +816,7 @@ fn missing_ed25519_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -789,17 +824,17 @@ fn missing_ed25519_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() )); } #[test] fn account_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -807,6 +842,7 @@ fn account_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -819,6 +855,7 @@ fn account_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -831,6 +868,7 @@ fn account_sender() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -841,6 +879,7 @@ fn account_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -853,6 +892,7 @@ fn account_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -867,6 +907,7 @@ fn account_sender() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), @@ -875,7 +916,7 @@ fn account_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -883,7 +924,7 @@ fn account_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); // Sender + another for amount @@ -901,7 +942,7 @@ fn account_sender() { #[test] fn account_sender_zero_id() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( @@ -909,6 +950,7 @@ fn account_sender_zero_id() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -921,6 +963,7 @@ fn account_sender_zero_id() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -934,6 +977,7 @@ fn account_sender_zero_id() { let account_id = AccountId::from(inputs[1].output_id()); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::from(account_id)), @@ -942,7 +986,7 @@ fn account_sender_zero_id() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -950,7 +994,7 @@ fn account_sender_zero_id() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -966,12 +1010,13 @@ fn account_sender_zero_id() { #[test] fn missing_account_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 5_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -985,6 +1030,7 @@ fn missing_account_sender() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), @@ -993,7 +1039,7 @@ fn missing_account_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1001,17 +1047,17 @@ fn missing_account_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() )); } #[test] fn nft_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -1019,6 +1065,7 @@ fn nft_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1031,6 +1078,7 @@ fn nft_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1043,6 +1091,7 @@ fn nft_sender() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1055,6 +1104,7 @@ fn nft_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1067,6 +1117,7 @@ fn nft_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1081,6 +1132,7 @@ fn nft_sender() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap()), @@ -1089,7 +1141,7 @@ fn nft_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1097,7 +1149,7 @@ fn nft_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); // Sender + another for amount @@ -1116,7 +1168,7 @@ fn nft_sender() { #[test] fn nft_sender_zero_id() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( @@ -1124,6 +1176,7 @@ fn nft_sender_zero_id() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1136,6 +1189,7 @@ fn nft_sender_zero_id() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1151,6 +1205,7 @@ fn nft_sender_zero_id() { let nft_id = NftId::from(inputs[1].output_id()); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::from(nft_id)), @@ -1159,7 +1214,7 @@ fn nft_sender_zero_id() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1167,7 +1222,7 @@ fn nft_sender_zero_id() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1183,12 +1238,13 @@ fn nft_sender_zero_id() { #[test] fn missing_nft_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 5_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1202,6 +1258,7 @@ fn missing_nft_sender() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap()), @@ -1210,7 +1267,7 @@ fn missing_nft_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1218,22 +1275,23 @@ fn missing_nft_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() )); } #[test] fn simple_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1247,6 +1305,7 @@ fn simple_remainder() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1255,7 +1314,7 @@ fn simple_remainder() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1263,7 +1322,7 @@ fn simple_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1271,12 +1330,12 @@ fn simple_remainder() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } }); } @@ -1306,7 +1365,7 @@ fn simple_remainder() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs, // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1344,7 +1403,7 @@ fn simple_remainder() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs, // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1363,12 +1422,13 @@ fn simple_remainder() { #[test] fn one_provided_one_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1382,6 +1442,7 @@ fn one_provided_one_needed() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1390,7 +1451,7 @@ fn one_provided_one_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1398,21 +1459,22 @@ fn one_provided_one_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn insufficient_amount() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1426,6 +1488,7 @@ fn insufficient_amount() { ); let outputs = build_outputs([Basic { amount: 1_250_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1434,7 +1497,7 @@ fn insufficient_amount() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1442,11 +1505,11 @@ fn insufficient_amount() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientAmount { + Err(TransactionBuilderError::InsufficientAmount { found: 1_000_000, required: 1_250_000, }) @@ -1455,13 +1518,14 @@ fn insufficient_amount() { #[test] fn two_inputs_remainder_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1474,6 +1538,7 @@ fn two_inputs_remainder_2() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1488,6 +1553,7 @@ fn two_inputs_remainder_2() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1496,7 +1562,7 @@ fn two_inputs_remainder_2() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1504,7 +1570,7 @@ fn two_inputs_remainder_2() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); @@ -1513,25 +1579,26 @@ fn two_inputs_remainder_2() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } }); } #[test] fn two_inputs_remainder_3() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1544,6 +1611,7 @@ fn two_inputs_remainder_3() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1557,7 +1625,8 @@ fn two_inputs_remainder_3() { Some(SLOT_INDEX), ); let outputs = build_outputs([Basic { - amount: 1_750_000, + amount: 2_750_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1566,7 +1635,7 @@ fn two_inputs_remainder_3() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1574,7 +1643,7 @@ fn two_inputs_remainder_3() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1582,12 +1651,12 @@ fn two_inputs_remainder_3() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, - 1_250_000, + 250_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } }); } @@ -1613,7 +1682,7 @@ fn two_inputs_remainder_3() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs.clone(), // outputs.clone(), // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1634,12 +1703,13 @@ fn two_inputs_remainder_3() { #[test] fn sender_already_selected() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -1653,6 +1723,7 @@ fn sender_already_selected() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), @@ -1661,7 +1732,7 @@ fn sender_already_selected() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -1673,21 +1744,22 @@ fn sender_already_selected() { protocol_parameters, ) .with_required_inputs([*inputs[0].output_id()]) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn single_mandatory_input() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -1701,6 +1773,7 @@ fn single_mandatory_input() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1709,7 +1782,7 @@ fn single_mandatory_input() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -1721,16 +1794,16 @@ fn single_mandatory_input() { protocol_parameters, ) .with_required_inputs([*inputs[0].output_id()]) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn too_many_inputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); // 129 inputs that would be required for the amount, but that's above max inputs let inputs = build_inputs( @@ -1738,6 +1811,7 @@ fn too_many_inputs() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1749,10 +1823,11 @@ fn too_many_inputs() { ) }) .take(129), - None, + Some(SLOT_INDEX), ); let outputs = build_outputs([Basic { amount: 129_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1761,7 +1836,7 @@ fn too_many_inputs() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1769,17 +1844,17 @@ fn too_many_inputs() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert_eq!( selected.unwrap_err(), - iota_sdk::client::api::input_selection::Error::InvalidInputCount(129) + iota_sdk::client::api::transaction_builder::TransactionBuilderError::InvalidInputCount(129) ) } #[test] fn more_than_max_inputs_only_one_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); // 1000 inputs where 129 would be needed for the required amount which is above the max inputs let mut inputs = build_inputs( @@ -1787,6 +1862,7 @@ fn more_than_max_inputs_only_one_needed() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1798,13 +1874,14 @@ fn more_than_max_inputs_only_one_needed() { ) }) .take(1000), - None, + Some(SLOT_INDEX), ); // Add the needed input let needed_input = build_inputs( [( Basic { amount: 129_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1820,6 +1897,7 @@ fn more_than_max_inputs_only_one_needed() { let outputs = build_outputs([Basic { amount: 129_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1828,7 +1906,7 @@ fn more_than_max_inputs_only_one_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1836,20 +1914,21 @@ fn more_than_max_inputs_only_one_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &needed_input)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn too_many_outputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { + mana: 0, amount: 2_000_000_000, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, @@ -1865,6 +1944,7 @@ fn too_many_outputs() { let outputs = build_outputs( std::iter::repeat_with(|| Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1875,7 +1955,7 @@ fn too_many_outputs() { .take(129), ); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1883,22 +1963,23 @@ fn too_many_outputs() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert_eq!( selected.unwrap_err(), - iota_sdk::client::api::input_selection::Error::InvalidOutputCount(129) + iota_sdk::client::api::transaction_builder::TransactionBuilderError::InvalidOutputCount(129) ) } #[test] fn too_many_outputs_with_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1914,6 +1995,7 @@ fn too_many_outputs_with_remainder() { let outputs = build_outputs( std::iter::repeat_with(|| Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1924,7 +2006,7 @@ fn too_many_outputs_with_remainder() { .take(128), ); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1932,18 +2014,18 @@ fn too_many_outputs_with_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert_eq!( selected.unwrap_err(), // 129 because of required remainder - iota_sdk::client::api::input_selection::Error::InvalidOutputCount(129) + iota_sdk::client::api::transaction_builder::TransactionBuilderError::InvalidOutputCount(129) ) } #[test] fn restricted_ed25519() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let address = Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(); let restricted = Address::from(RestrictedAddress::new(address.clone()).unwrap()); @@ -1952,6 +2034,7 @@ fn restricted_ed25519() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1964,6 +2047,7 @@ fn restricted_ed25519() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1976,6 +2060,7 @@ fn restricted_ed25519() { ( Basic { amount: 1_000_000, + mana: 0, address: restricted, native_token: None, sender: None, @@ -1988,6 +2073,7 @@ fn restricted_ed25519() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2000,6 +2086,7 @@ fn restricted_ed25519() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2014,6 +2101,7 @@ fn restricted_ed25519() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2022,7 +2110,7 @@ fn restricted_ed25519() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()], @@ -2030,17 +2118,17 @@ fn restricted_ed25519() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data, [inputs[2].clone()]); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn restricted_nft() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let nft_address = Address::from(nft_id_1); let restricted = Address::from(RestrictedAddress::new(nft_address.clone()).unwrap()); @@ -2050,6 +2138,7 @@ fn restricted_nft() { ( Basic { amount: 2_000_000, + mana: 0, address: restricted, native_token: None, sender: None, @@ -2062,6 +2151,7 @@ fn restricted_nft() { ( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -2076,6 +2166,7 @@ fn restricted_nft() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2084,7 +2175,7 @@ fn restricted_nft() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -2092,7 +2183,7 @@ fn restricted_nft() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -2102,7 +2193,7 @@ fn restricted_nft() { #[test] fn restricted_account() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let account_address = Address::from(account_id_1); let restricted = Address::from(RestrictedAddress::new(account_address.clone()).unwrap()); @@ -2112,6 +2203,7 @@ fn restricted_account() { ( Basic { amount: 3_000_000, + mana: 0, address: restricted, native_token: None, sender: None, @@ -2124,6 +2216,7 @@ fn restricted_account() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -2136,6 +2229,7 @@ fn restricted_account() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2144,7 +2238,7 @@ fn restricted_account() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -2152,7 +2246,7 @@ fn restricted_account() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -2162,7 +2256,7 @@ fn restricted_account() { #[test] fn restricted_ed25519_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let sender = Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(); let restricted_sender = Address::from(RestrictedAddress::new(sender.clone()).unwrap()); @@ -2171,6 +2265,7 @@ fn restricted_ed25519_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2183,6 +2278,7 @@ fn restricted_ed25519_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2195,6 +2291,7 @@ fn restricted_ed25519_sender() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -2207,6 +2304,7 @@ fn restricted_ed25519_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2219,6 +2317,7 @@ fn restricted_ed25519_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2233,6 +2332,7 @@ fn restricted_ed25519_sender() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(restricted_sender), @@ -2241,7 +2341,7 @@ fn restricted_ed25519_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -2252,7 +2352,7 @@ fn restricted_ed25519_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); // Sender + another for amount @@ -2269,7 +2369,7 @@ fn restricted_ed25519_sender() { #[test] fn multi_address_sender_already_fulfilled() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let sender_0 = Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(); let sender_1 = Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(); let sender_2 = Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap(); @@ -2290,6 +2390,7 @@ fn multi_address_sender_already_fulfilled() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2302,6 +2403,7 @@ fn multi_address_sender_already_fulfilled() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -2314,6 +2416,7 @@ fn multi_address_sender_already_fulfilled() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap(), native_token: None, sender: None, @@ -2328,6 +2431,7 @@ fn multi_address_sender_already_fulfilled() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(multi), @@ -2336,7 +2440,7 @@ fn multi_address_sender_already_fulfilled() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -2349,16 +2453,16 @@ fn multi_address_sender_already_fulfilled() { protocol_parameters, ) .with_required_inputs([*inputs[0].output_id(), *inputs[1].output_id(), *inputs[2].output_id()]) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn ed25519_backed_available_address() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let ed25519 = Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(); let restricted_address = Address::from( RestrictedAddress::new(ed25519.clone()) @@ -2371,6 +2475,7 @@ fn ed25519_backed_available_address() { ( Basic { amount: 1_000_000, + mana: 0, address: restricted_address.clone(), native_token: None, sender: None, @@ -2383,6 +2488,7 @@ fn ed25519_backed_available_address() { ( Basic { amount: 1_000_000, + mana: 0, address: ed25519.clone(), native_token: None, sender: None, @@ -2398,6 +2504,7 @@ fn ed25519_backed_available_address() { let outputs = build_outputs([ Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -2407,6 +2514,7 @@ fn ed25519_backed_available_address() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(restricted_address.clone()), @@ -2416,7 +2524,7 @@ fn ed25519_backed_available_address() { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), // Restricted address is provided, but it can also unlock the ed25519 one @@ -2425,10 +2533,66 @@ fn ed25519_backed_available_address() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); // Provided outputs assert_eq!(selected.transaction.outputs(), outputs); } + +#[test] +fn automatic_allotment_provided_in_and_output() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + + let inputs = build_inputs( + [( + Basic { + amount: 1_000_000, + mana: 7577, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + )], + Some(SLOT_INDEX), + ); + + let outputs = vec![ + BasicOutputBuilder::new_with_amount(1_000_000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_mana(1) + .finish_output() + .unwrap(), + ]; + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_min_mana_allotment(account_id_1, 2) + .finish() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 1); + assert!(selected.transaction.outputs().contains(&outputs[0])); + assert_eq!(selected.transaction.allotments().len(), 1); + let mana_cost = 7576; + assert_eq!( + selected.transaction.allotments()[0], + ManaAllotment::new(account_id_1, mana_cost).unwrap() + ); + assert_eq!(selected.transaction.outputs()[0].mana(), 1); +} diff --git a/sdk/tests/client/input_selection/burn.rs b/sdk/tests/client/transaction_builder/burn.rs similarity index 64% rename from sdk/tests/client/input_selection/burn.rs rename to sdk/tests/client/transaction_builder/burn.rs index f79687905f..7a183f3a64 100644 --- a/sdk/tests/client/input_selection/burn.rs +++ b/sdk/tests/client/transaction_builder/burn.rs @@ -7,17 +7,28 @@ use std::{ }; use iota_sdk::{ - client::api::input_selection::{Burn, Error, InputSelection, Requirement}, + client::{ + api::transaction_builder::{Burn, Requirement, TransactionBuilder, TransactionBuilderError}, + secret::types::InputSigningData, + }, types::block::{ address::Address, - output::{AccountId, ChainId, NftId, SimpleTokenScheme, TokenId}, - protocol::protocol_parameters, + output::{ + unlock_condition::AddressUnlockCondition, AccountId, AccountOutputBuilder, BasicOutputBuilder, ChainId, + NftId, SimpleTokenScheme, TokenId, + }, + payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, + protocol::iota_mainnet_protocol_parameters, + rand::output::{rand_output_id_with_slot_index, rand_output_metadata_with_id}, + slot::SlotIndex, }, }; use pretty_assertions::assert_eq; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, + assert_remainder_or_return, build_inputs, build_outputs, + transaction_builder::native_tokens::nt_remainder_min_storage_deposit, + unsorted_eq, Build::{Account, Basic, Foundry, Nft}, ACCOUNT_ID_0, ACCOUNT_ID_1, ACCOUNT_ID_2, BECH32_ADDRESS_ED25519_0, NFT_ID_0, NFT_ID_1, NFT_ID_2, SLOT_COMMITMENT_ID, SLOT_INDEX, TOKEN_ID_1, TOKEN_ID_2, @@ -25,7 +36,7 @@ use crate::client::{ #[test] fn burn_account_present() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -33,6 +44,7 @@ fn burn_account_present() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -43,6 +55,7 @@ fn burn_account_present() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -57,6 +70,7 @@ fn burn_account_present() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -65,7 +79,7 @@ fn burn_account_present() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -74,9 +88,13 @@ fn burn_account_present() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id_1)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[0]); assert_eq!(selected.transaction.outputs(), outputs); @@ -84,7 +102,7 @@ fn burn_account_present() { #[test] fn burn_account_present_and_required() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -92,6 +110,7 @@ fn burn_account_present_and_required() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -102,6 +121,7 @@ fn burn_account_present_and_required() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -116,6 +136,7 @@ fn burn_account_present_and_required() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -124,7 +145,7 @@ fn burn_account_present_and_required() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -134,9 +155,13 @@ fn burn_account_present_and_required() { ) .with_burn(Burn::new().add_account(account_id_1)) .with_required_inputs([*inputs[0].output_id()]) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[0]); assert_eq!(selected.transaction.outputs(), outputs); @@ -144,7 +169,7 @@ fn burn_account_present_and_required() { #[test] fn burn_account_id_zero() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( @@ -152,6 +177,7 @@ fn burn_account_id_zero() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -164,6 +190,7 @@ fn burn_account_id_zero() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -178,6 +205,7 @@ fn burn_account_id_zero() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -187,7 +215,7 @@ fn burn_account_id_zero() { }]); let nft_id = NftId::from(inputs[0].output_id()); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -196,9 +224,13 @@ fn burn_account_id_zero() { protocol_parameters, ) .with_burn(Burn::new().add_nft(nft_id)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyNftOutputs]) + ); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[0]); assert_eq!(selected.transaction.outputs(), outputs); @@ -206,13 +238,14 @@ fn burn_account_id_zero() { #[test] fn burn_account_absent() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -226,6 +259,7 @@ fn burn_account_absent() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -234,7 +268,7 @@ fn burn_account_absent() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -243,17 +277,18 @@ fn burn_account_absent() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id_1)) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Account(account_id))) if account_id == account_id_1 - )); + TransactionBuilderError::UnfulfillableRequirement(Requirement::Account(account_id_1)) + ); } #[test] fn burn_accounts_present() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); @@ -262,6 +297,7 @@ fn burn_accounts_present() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -272,6 +308,7 @@ fn burn_accounts_present() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -282,6 +319,7 @@ fn burn_accounts_present() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -296,6 +334,7 @@ fn burn_accounts_present() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -304,7 +343,7 @@ fn burn_accounts_present() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -313,16 +352,20 @@ fn burn_accounts_present() { protocol_parameters, ) .with_burn(Burn::new().set_accounts(HashSet::from([account_id_1, account_id_2]))) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn burn_account_in_outputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -330,6 +373,7 @@ fn burn_account_in_outputs() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -340,6 +384,7 @@ fn burn_account_in_outputs() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -355,6 +400,7 @@ fn burn_account_in_outputs() { let outputs = build_outputs([ Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -362,6 +408,7 @@ fn burn_account_in_outputs() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -371,7 +418,7 @@ fn burn_account_in_outputs() { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -380,17 +427,18 @@ fn burn_account_in_outputs() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id_1)) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::BurnAndTransition(ChainId::Account(account_id))) if account_id == account_id_1 - )); + TransactionBuilderError::BurnAndTransition(ChainId::Account(account_id_1)) + ); } #[test] fn burn_nft_present() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -398,6 +446,7 @@ fn burn_nft_present() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -410,6 +459,7 @@ fn burn_nft_present() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -424,6 +474,7 @@ fn burn_nft_present() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -432,7 +483,7 @@ fn burn_nft_present() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -441,9 +492,13 @@ fn burn_nft_present() { protocol_parameters, ) .with_burn(Burn::new().add_nft(nft_id_1)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyNftOutputs]) + ); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[0]); assert_eq!(selected.transaction.outputs(), outputs); @@ -451,7 +506,7 @@ fn burn_nft_present() { #[test] fn burn_nft_present_and_required() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -459,6 +514,7 @@ fn burn_nft_present_and_required() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -471,6 +527,7 @@ fn burn_nft_present_and_required() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -485,6 +542,7 @@ fn burn_nft_present_and_required() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -493,7 +551,7 @@ fn burn_nft_present_and_required() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -503,9 +561,13 @@ fn burn_nft_present_and_required() { ) .with_burn(Burn::new().add_nft(nft_id_1)) .with_required_inputs([*inputs[0].output_id()]) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyNftOutputs]) + ); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[0]); assert_eq!(selected.transaction.outputs(), outputs); @@ -513,7 +575,7 @@ fn burn_nft_present_and_required() { #[test] fn burn_nft_id_zero() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_0 = AccountId::from_str(ACCOUNT_ID_0).unwrap(); let inputs = build_inputs( @@ -521,6 +583,7 @@ fn burn_nft_id_zero() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -531,6 +594,7 @@ fn burn_nft_id_zero() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -545,6 +609,7 @@ fn burn_nft_id_zero() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -554,7 +619,7 @@ fn burn_nft_id_zero() { }]); let account_id = AccountId::from(inputs[0].output_id()); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -563,9 +628,13 @@ fn burn_nft_id_zero() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[0]); assert_eq!(selected.transaction.outputs(), outputs); @@ -573,13 +642,14 @@ fn burn_nft_id_zero() { #[test] fn burn_nft_absent() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -593,6 +663,7 @@ fn burn_nft_absent() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -601,7 +672,7 @@ fn burn_nft_absent() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -610,17 +681,18 @@ fn burn_nft_absent() { protocol_parameters, ) .with_burn(Burn::new().add_nft(nft_id_1)) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Nft(nft_id))) if nft_id == nft_id_1 - )); + TransactionBuilderError::UnfulfillableRequirement(Requirement::Nft(nft_id_1)) + ); } #[test] fn burn_nfts_present() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); @@ -629,6 +701,7 @@ fn burn_nfts_present() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -641,6 +714,7 @@ fn burn_nfts_present() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -653,6 +727,7 @@ fn burn_nfts_present() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -667,6 +742,7 @@ fn burn_nfts_present() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -675,7 +751,7 @@ fn burn_nfts_present() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -684,16 +760,20 @@ fn burn_nfts_present() { protocol_parameters, ) .with_burn(Burn::new().set_nfts(HashSet::from([nft_id_1, nft_id_2]))) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyNftOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn burn_nft_in_outputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -701,6 +781,7 @@ fn burn_nft_in_outputs() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -713,6 +794,7 @@ fn burn_nft_in_outputs() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -728,6 +810,7 @@ fn burn_nft_in_outputs() { let outputs = build_outputs([ Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -737,6 +820,7 @@ fn burn_nft_in_outputs() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -746,7 +830,7 @@ fn burn_nft_in_outputs() { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -755,17 +839,18 @@ fn burn_nft_in_outputs() { protocol_parameters, ) .with_burn(Burn::new().add_nft(nft_id_1)) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::BurnAndTransition(ChainId::Nft(nft_id))) if nft_id == nft_id_1 - )); + TransactionBuilderError::BurnAndTransition(ChainId::Nft(nft_id_1)) + ); } #[test] fn burn_foundry_present() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -783,6 +868,7 @@ fn burn_foundry_present() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -793,6 +879,7 @@ fn burn_foundry_present() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -807,6 +894,7 @@ fn burn_foundry_present() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -815,7 +903,7 @@ fn burn_foundry_present() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -824,9 +912,13 @@ fn burn_foundry_present() { protocol_parameters, ) .with_burn(Burn::new().add_foundry(inputs[0].output.as_foundry().id())) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyFoundryOutputs]) + ); assert_eq!(selected.inputs_data.len(), 2); assert!(selected.inputs_data.contains(&inputs[0])); assert!(selected.inputs_data.contains(&inputs[1])); @@ -835,12 +927,12 @@ fn burn_foundry_present() { selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { if output.is_basic() { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } else if output.is_account() { assert_eq!(output.amount(), 1_000_000); assert_eq!(*output.as_account().account_id(), account_id_1); @@ -860,7 +952,7 @@ fn burn_foundry_present() { #[test] fn burn_foundry_absent() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let foundry_id_1 = build_inputs( [( @@ -884,6 +976,7 @@ fn burn_foundry_absent() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -894,6 +987,7 @@ fn burn_foundry_absent() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -908,6 +1002,7 @@ fn burn_foundry_absent() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -916,7 +1011,7 @@ fn burn_foundry_absent() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -925,17 +1020,18 @@ fn burn_foundry_absent() { protocol_parameters, ) .with_burn(Burn::new().add_foundry(foundry_id_1)) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Foundry(foundry_id))) if foundry_id == foundry_id_1 - )); + TransactionBuilderError::UnfulfillableRequirement(Requirement::Foundry(foundry_id_1)) + ); } #[test] fn burn_foundries_present() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -963,6 +1059,7 @@ fn burn_foundries_present() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -975,6 +1072,7 @@ fn burn_foundries_present() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -983,7 +1081,7 @@ fn burn_foundries_present() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -995,9 +1093,13 @@ fn burn_foundries_present() { inputs[0].output.as_foundry().id(), inputs[1].output.as_foundry().id(), ]))) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyFoundryOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); @@ -1019,7 +1121,7 @@ fn burn_foundries_present() { #[test] fn burn_foundry_in_outputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -1037,6 +1139,7 @@ fn burn_foundry_in_outputs() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1059,6 +1162,7 @@ fn burn_foundry_in_outputs() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1069,7 +1173,7 @@ fn burn_foundry_in_outputs() { ]); let foundry_id_1 = inputs[0].output.as_foundry().id(); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1078,23 +1182,25 @@ fn burn_foundry_in_outputs() { protocol_parameters, ) .with_burn(Burn::new().add_foundry(foundry_id_1)) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::BurnAndTransition(ChainId::Foundry(foundry_id))) if foundry_id == foundry_id_1 - )); + TransactionBuilderError::BurnAndTransition(ChainId::Foundry(foundry_id_1)) + ); } #[test] fn burn_native_tokens() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1107,6 +1213,7 @@ fn burn_native_tokens() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_2, 100)), sender: None, @@ -1120,7 +1227,9 @@ fn burn_native_tokens() { Some(SLOT_INDEX), ); - let selected = InputSelection::new( + let nt_remainder_output_amount = nt_remainder_min_storage_deposit(&protocol_parameters); + + let selected = TransactionBuilder::new( inputs.clone(), None, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1132,30 +1241,33 @@ fn burn_native_tokens() { (TokenId::from_str(TOKEN_ID_1).unwrap(), 20), (TokenId::from_str(TOKEN_ID_2).unwrap(), 30), ]))) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnNativeTokens]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); - let nt_remainder_output_amount = 106000; - assert!( - is_remainder_or_return( - &selected.transaction.outputs()[0], - nt_remainder_output_amount, - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_1, 80)) - ) && is_remainder_or_return( - &selected.transaction.outputs()[1], - 2_000_000 - nt_remainder_output_amount, - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_2, 70)) - ) + + assert_remainder_or_return( + &selected.transaction.outputs()[0], + nt_remainder_output_amount, + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + Some((TOKEN_ID_1, 80)), + ); + assert_remainder_or_return( + &selected.transaction.outputs()[1], + 2_000_000 - nt_remainder_output_amount, + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + Some((TOKEN_ID_2, 70)), ); } #[test] fn burn_foundry_and_its_account() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -1173,6 +1285,7 @@ fn burn_foundry_and_its_account() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1183,6 +1296,7 @@ fn burn_foundry_and_its_account() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1197,6 +1311,7 @@ fn burn_foundry_and_its_account() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1205,7 +1320,7 @@ fn burn_foundry_and_its_account() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1218,9 +1333,16 @@ fn burn_foundry_and_its_account() { .add_foundry(inputs[0].output.as_foundry().id()) .add_account(account_id_1), ) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([ + TransactionCapabilityFlag::DestroyAccountOutputs, + TransactionCapabilityFlag::DestroyFoundryOutputs + ]) + ); assert_eq!(selected.inputs_data.len(), 2); assert!(selected.inputs_data.contains(&inputs[0])); assert!(selected.inputs_data.contains(&inputs[1])); @@ -1229,12 +1351,344 @@ fn burn_foundry_and_its_account() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } }); } + +#[test] +fn burn_mana() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + + let inputs = [BasicOutputBuilder::new_with_amount(1_000_000) + .with_mana(1000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap()]; + let inputs = inputs + .into_iter() + .map(|input| InputSigningData { + output: input, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), + chain: None, + }) + .collect::>(); + + let outputs = [BasicOutputBuilder::new_with_amount(1_000_000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_mana(500) + .finish_output() + .unwrap()]; + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_required_inputs([*inputs[0].output_id()]) + .with_burn(Burn::new().set_mana(true)) + .finish() + .unwrap(); + + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnMana]) + ); + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs(), &outputs); +} + +#[test] +fn burn_mana_need_additional() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + + let inputs = [ + BasicOutputBuilder::new_with_amount(100_000) + .with_mana(1000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + BasicOutputBuilder::new_with_amount(1_000_000) + .with_mana(200) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + ]; + let inputs = inputs + .into_iter() + .map(|input| InputSigningData { + output: input, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), + chain: None, + }) + .collect::>(); + + let outputs = [BasicOutputBuilder::new_with_amount(1_100_000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_mana(500) + .finish_output() + .unwrap()]; + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_required_inputs([*inputs[0].output_id()]) + .with_burn(Burn::new().set_mana(true)) + .finish() + .unwrap(); + + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnMana]) + ); + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 1); + assert_eq!(selected.transaction.outputs()[0].mana(), 700); +} + +#[test] +fn burn_mana_need_additional_account() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + + let inputs = [ + BasicOutputBuilder::new_with_amount(100_000) + .with_mana(1000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + AccountOutputBuilder::new_with_amount(1_200_000, account_id_1) + .with_mana(200) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + ]; + let inputs = inputs + .into_iter() + .map(|input| InputSigningData { + output: input, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), + chain: None, + }) + .collect::>(); + + let outputs = [BasicOutputBuilder::new_with_amount(1_100_000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_mana(500) + .finish_output() + .unwrap()]; + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_required_inputs([*inputs[0].output_id()]) + .with_burn(Burn::new().set_mana(true)) + .finish() + .unwrap(); + + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnMana]) + ); + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 2); + assert_eq!(selected.transaction.outputs()[0].mana(), 500); + assert_eq!(selected.transaction.outputs()[1].mana(), 200); +} + +#[test] +fn burn_generated_mana() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + + let inputs = [ + BasicOutputBuilder::new_with_amount(1_000_000) + .with_mana(1000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + BasicOutputBuilder::new_with_amount(1_000_000) + .with_mana(200) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + ]; + let inputs = inputs + .into_iter() + .map(|input| InputSigningData { + output: input, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SlotIndex(5))), + chain: None, + }) + .collect::>(); + + let outputs = [BasicOutputBuilder::new_with_amount(2_000_000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_mana(1200) + .finish_output() + .unwrap()]; + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_burn(Burn::new().set_generated_mana(true)) + .finish() + .unwrap(); + + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnMana]) + ); + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); +} + +#[test] +fn burn_generated_mana_remainder() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + + let inputs = [ + BasicOutputBuilder::new_with_amount(1_000_000) + .with_mana(1000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + BasicOutputBuilder::new_with_amount(1_000_000) + .with_mana(200) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + ]; + let inputs = inputs + .into_iter() + .map(|input| InputSigningData { + output: input, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SlotIndex(5))), + chain: None, + }) + .collect::>(); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_burn(Burn::new().set_generated_mana(true)) + .with_required_inputs(inputs.iter().map(|i| *i.output_id())) + .finish() + .unwrap(); + + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnMana]) + ); + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 1); + assert_eq!(selected.transaction.outputs()[0].mana(), 1200); +} + +#[test] +fn burn_generated_mana_account() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + + let inputs = [ + BasicOutputBuilder::new_with_amount(1_000_000) + .with_mana(1000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + AccountOutputBuilder::new_with_amount(1_000_000, account_id_1) + .with_mana(200) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .finish_output() + .unwrap(), + ]; + let inputs = inputs + .into_iter() + .map(|input| InputSigningData { + output: input, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SlotIndex(5))), + chain: None, + }) + .collect::>(); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_burn(Burn::new().set_generated_mana(true)) + .with_required_inputs(inputs.iter().map(|i| *i.output_id())) + .finish() + .unwrap(); + + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnMana]) + ); + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 2); + assert_eq!( + selected.transaction.outputs().iter().map(|o| o.mana()).sum::(), + 1200 + ); +} diff --git a/sdk/tests/client/input_selection/delegation_outputs.rs b/sdk/tests/client/transaction_builder/delegation_outputs.rs similarity index 78% rename from sdk/tests/client/input_selection/delegation_outputs.rs rename to sdk/tests/client/transaction_builder/delegation_outputs.rs index f9d8e2420f..cd0641a24f 100644 --- a/sdk/tests/client/input_selection/delegation_outputs.rs +++ b/sdk/tests/client/transaction_builder/delegation_outputs.rs @@ -3,7 +3,7 @@ use iota_sdk::{ client::{ - api::input_selection::{Burn, InputSelection}, + api::transaction_builder::{Burn, TransactionBuilder}, secret::types::InputSigningData, }, types::block::{ @@ -12,12 +12,11 @@ use iota_sdk::{ unlock_condition::AddressUnlockCondition, BasicOutputBuilder, DelegationId, DelegationOutputBuilder, OutputId, }, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, rand::{ address::rand_account_address, output::rand_output_metadata_with_id, transaction::rand_transaction_id_with_slot_index, }, - slot::SlotIndex, }, }; use pretty_assertions::assert_eq; @@ -26,7 +25,7 @@ use crate::client::{BECH32_ADDRESS_ED25519_0, SLOT_COMMITMENT_ID, SLOT_INDEX}; #[test] fn remainder_needed_for_mana() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let delegation_input = DelegationOutputBuilder::new_with_amount(1_000_000, DelegationId::null(), rand_account_address()) @@ -74,7 +73,7 @@ fn remainder_needed_for_mana() { let mana_rewards = 100; - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -84,7 +83,24 @@ fn remainder_needed_for_mana() { ) .with_burn(Burn::from(delegation_id)) .add_mana_rewards(delegation_output_id, mana_rewards) - .select() + .finish() + .unwrap(); + + let inputs = selected + .inputs_data + .iter() + .map(|input| (input.output_id(), &input.output)) + .collect::>(); + + // validating without rewards + iota_sdk::types::block::semantic::SemanticValidationContext::new( + &selected.transaction, + &inputs, + None, + None, + protocol_parameters, + ) + .validate() .unwrap(); assert_eq!(selected.inputs_data.len(), 2); @@ -97,7 +113,11 @@ fn remainder_needed_for_mana() { .iter() .map(|i| i .output - .available_mana(&protocol_parameters, SlotIndex(0), SLOT_INDEX) + .available_mana( + protocol_parameters, + i.output_id().transaction_id().slot_index(), + SLOT_INDEX + ) .unwrap()) .sum::(), selected.transaction.outputs().iter().map(|o| o.mana()).sum::() diff --git a/sdk/tests/client/input_selection/expiration.rs b/sdk/tests/client/transaction_builder/expiration.rs similarity index 84% rename from sdk/tests/client/input_selection/expiration.rs rename to sdk/tests/client/transaction_builder/expiration.rs index a074f221d8..7bd10813bb 100644 --- a/sdk/tests/client/input_selection/expiration.rs +++ b/sdk/tests/client/transaction_builder/expiration.rs @@ -4,18 +4,18 @@ use std::str::FromStr; use iota_sdk::{ - client::api::input_selection::{Error, InputSelection}, + client::api::transaction_builder::{TransactionBuilder, TransactionBuilderError}, types::block::{ address::Address, output::{AccountId, NftId}, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, slot::{SlotCommitmentHash, SlotIndex}, }, }; use pretty_assertions::assert_eq; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, + assert_remainder_or_return, build_inputs, build_outputs, unsorted_eq, Build::{Account, Basic, Nft}, ACCOUNT_ID_1, BECH32_ADDRESS_ACCOUNT_1, BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1, BECH32_ADDRESS_ED25519_2, NFT_ID_1, @@ -23,12 +23,13 @@ use crate::client::{ #[test] fn one_output_expiration_not_expired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -42,6 +43,7 @@ fn one_output_expiration_not_expired() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -50,7 +52,7 @@ fn one_output_expiration_not_expired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -58,19 +60,23 @@ fn one_output_expiration_not_expired() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select(); + .finish(); - assert!(matches!(selected, Err(Error::NoAvailableInputsProvided))); + assert!(matches!( + selected, + Err(TransactionBuilderError::NoAvailableInputsProvided) + )); } #[test] fn expiration_equal_timestamp() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -84,6 +90,7 @@ fn expiration_equal_timestamp() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -92,7 +99,7 @@ fn expiration_equal_timestamp() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -100,21 +107,22 @@ fn expiration_equal_timestamp() { SlotCommitmentHash::null().into_slot_commitment_id(199), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn one_output_expiration_expired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -128,6 +136,7 @@ fn one_output_expiration_expired() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -136,7 +145,7 @@ fn one_output_expiration_expired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -144,22 +153,23 @@ fn one_output_expiration_expired() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_outputs_one_expiration_expired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -172,6 +182,7 @@ fn two_outputs_one_expiration_expired() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -186,6 +197,7 @@ fn two_outputs_one_expiration_expired() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -194,7 +206,7 @@ fn two_outputs_one_expiration_expired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -202,23 +214,24 @@ fn two_outputs_one_expiration_expired() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[1]); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_outputs_one_unexpired_one_missing() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -231,6 +244,7 @@ fn two_outputs_one_unexpired_one_missing() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -245,6 +259,7 @@ fn two_outputs_one_unexpired_one_missing() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -253,7 +268,7 @@ fn two_outputs_one_unexpired_one_missing() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -261,23 +276,24 @@ fn two_outputs_one_unexpired_one_missing() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[1]); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_outputs_two_expired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -290,6 +306,7 @@ fn two_outputs_two_expired() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -302,6 +319,7 @@ fn two_outputs_two_expired() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -316,6 +334,7 @@ fn two_outputs_two_expired() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -324,7 +343,7 @@ fn two_outputs_two_expired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap()], @@ -332,23 +351,24 @@ fn two_outputs_two_expired() { SlotCommitmentHash::null().into_slot_commitment_id(199), protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[1]); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_outputs_two_expired_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -361,6 +381,7 @@ fn two_outputs_two_expired_2() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -375,6 +396,7 @@ fn two_outputs_two_expired_2() { ); let outputs = build_outputs([Basic { amount: 4_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -383,7 +405,7 @@ fn two_outputs_two_expired_2() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -394,21 +416,22 @@ fn two_outputs_two_expired_2() { SlotCommitmentHash::null().into_slot_commitment_id(199), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn expiration_expired_with_sdr() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -422,6 +445,7 @@ fn expiration_expired_with_sdr() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -430,7 +454,7 @@ fn expiration_expired_with_sdr() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -438,21 +462,22 @@ fn expiration_expired_with_sdr() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn expiration_expired_with_sdr_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -466,6 +491,7 @@ fn expiration_expired_with_sdr_2() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -474,7 +500,7 @@ fn expiration_expired_with_sdr_2() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -482,21 +508,22 @@ fn expiration_expired_with_sdr_2() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn expiration_expired_with_sdr_and_timelock() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -510,6 +537,7 @@ fn expiration_expired_with_sdr_and_timelock() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -518,7 +546,7 @@ fn expiration_expired_with_sdr_and_timelock() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -526,21 +554,22 @@ fn expiration_expired_with_sdr_and_timelock() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn expiration_expired_with_sdr_and_timelock_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -554,6 +583,7 @@ fn expiration_expired_with_sdr_and_timelock_2() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -562,7 +592,7 @@ fn expiration_expired_with_sdr_and_timelock_2() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -570,22 +600,23 @@ fn expiration_expired_with_sdr_and_timelock_2() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn sender_in_expiration() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -598,6 +629,7 @@ fn sender_in_expiration() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -610,6 +642,7 @@ fn sender_in_expiration() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -622,6 +655,7 @@ fn sender_in_expiration() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -634,6 +668,7 @@ fn sender_in_expiration() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -648,6 +683,7 @@ fn sender_in_expiration() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), @@ -656,7 +692,7 @@ fn sender_in_expiration() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -667,22 +703,23 @@ fn sender_in_expiration() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert!(selected.inputs_data.contains(&inputs[2])); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn sender_in_expiration_already_selected() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -696,6 +733,7 @@ fn sender_in_expiration_already_selected() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), @@ -704,7 +742,7 @@ fn sender_in_expiration_already_selected() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -716,21 +754,22 @@ fn sender_in_expiration_already_selected() { protocol_parameters, ) .with_required_inputs([*inputs[0].output_id()]) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn remainder_in_expiration() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap(), native_token: None, sender: None, @@ -744,6 +783,7 @@ fn remainder_in_expiration() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), @@ -752,7 +792,7 @@ fn remainder_in_expiration() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -763,7 +803,7 @@ fn remainder_in_expiration() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -771,24 +811,25 @@ fn remainder_in_expiration() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), - None - )); + None, + ); } }); } #[test] fn expiration_expired_non_ed25519_in_address_unlock_condition() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap(), native_token: None, sender: None, @@ -802,6 +843,7 @@ fn expiration_expired_non_ed25519_in_address_unlock_condition() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -810,7 +852,7 @@ fn expiration_expired_non_ed25519_in_address_unlock_condition() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -818,16 +860,16 @@ fn expiration_expired_non_ed25519_in_address_unlock_condition() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn expiration_expired_only_account_addresses() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -835,6 +877,7 @@ fn expiration_expired_only_account_addresses() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap(), native_token: None, sender: None, @@ -847,6 +890,7 @@ fn expiration_expired_only_account_addresses() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -860,6 +904,7 @@ fn expiration_expired_only_account_addresses() { let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -868,7 +913,7 @@ fn expiration_expired_only_account_addresses() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -876,7 +921,7 @@ fn expiration_expired_only_account_addresses() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -885,13 +930,14 @@ fn expiration_expired_only_account_addresses() { #[test] fn one_nft_output_expiration_unexpired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( [( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), sender: None, @@ -905,6 +951,7 @@ fn one_nft_output_expiration_unexpired() { ); let outputs = build_outputs([Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), sender: None, @@ -913,7 +960,7 @@ fn one_nft_output_expiration_unexpired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()], @@ -921,22 +968,23 @@ fn one_nft_output_expiration_unexpired() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn one_nft_output_expiration_expired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( [( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), sender: None, @@ -950,6 +998,7 @@ fn one_nft_output_expiration_expired() { ); let outputs = build_outputs([Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), sender: None, @@ -958,7 +1007,7 @@ fn one_nft_output_expiration_expired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -966,9 +1015,9 @@ fn one_nft_output_expiration_expired() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } diff --git a/sdk/tests/client/input_selection/foundry_outputs.rs b/sdk/tests/client/transaction_builder/foundry_outputs.rs similarity index 79% rename from sdk/tests/client/input_selection/foundry_outputs.rs rename to sdk/tests/client/transaction_builder/foundry_outputs.rs index 5c98a5568a..daf19c3dd7 100644 --- a/sdk/tests/client/input_selection/foundry_outputs.rs +++ b/sdk/tests/client/transaction_builder/foundry_outputs.rs @@ -5,36 +5,39 @@ use std::str::FromStr; use iota_sdk::{ client::{ - api::input_selection::{Burn, Error, InputSelection, Requirement}, + api::transaction_builder::{Burn, Requirement, TransactionBuilder, TransactionBuilderError}, secret::types::InputSigningData, }, types::block::{ address::{AccountAddress, Address}, output::{ - unlock_condition::AddressUnlockCondition, AccountId, AccountOutputBuilder, FoundryId, Output, - SimpleTokenScheme, TokenId, + unlock_condition::AddressUnlockCondition, AccountId, AccountOutputBuilder, FoundryId, FoundryOutputBuilder, + Output, SimpleTokenScheme, TokenId, }, - protocol::protocol_parameters, + payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, + protocol::iota_mainnet_protocol_parameters, rand::output::{rand_output_id_with_slot_index, rand_output_metadata_with_id}, + semantic::TransactionFailureReason, }, }; use pretty_assertions::assert_eq; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, + assert_remainder_or_return, build_inputs, build_outputs, unsorted_eq, Build::{Account, Basic, Foundry}, ACCOUNT_ID_1, ACCOUNT_ID_2, BECH32_ADDRESS_ED25519_0, SLOT_COMMITMENT_ID, SLOT_INDEX, }; #[test] fn missing_input_account_for_foundry() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -54,7 +57,7 @@ fn missing_input_account_for_foundry() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -62,11 +65,11 @@ fn missing_input_account_for_foundry() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Account(account_id))) if account_id == account_id_2 + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Account(account_id))) if account_id == account_id_2 )); } @@ -94,7 +97,7 @@ fn missing_input_account_for_foundry() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs.clone(), // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -110,7 +113,7 @@ fn missing_input_account_for_foundry() { #[test] fn minted_native_tokens_in_new_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( @@ -118,6 +121,7 @@ fn minted_native_tokens_in_new_remainder() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -130,6 +134,7 @@ fn minted_native_tokens_in_new_remainder() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -148,7 +153,7 @@ fn minted_native_tokens_in_new_remainder() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -156,7 +161,7 @@ fn minted_native_tokens_in_new_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -173,7 +178,7 @@ fn minted_native_tokens_in_new_remainder() { #[test] fn minted_native_tokens_in_provided_output() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let foundry_id = FoundryId::build(&AccountAddress::from(account_id_2), 1, SimpleTokenScheme::KIND); let token_id = TokenId::from(foundry_id); @@ -183,6 +188,7 @@ fn minted_native_tokens_in_provided_output() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -195,6 +201,7 @@ fn minted_native_tokens_in_provided_output() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -215,6 +222,7 @@ fn minted_native_tokens_in_provided_output() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((&token_id.to_string(), 100)), sender: None, @@ -224,7 +232,7 @@ fn minted_native_tokens_in_provided_output() { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -232,7 +240,7 @@ fn minted_native_tokens_in_provided_output() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -244,7 +252,7 @@ fn minted_native_tokens_in_provided_output() { #[test] fn melt_native_tokens() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let mut inputs = build_inputs( @@ -252,6 +260,7 @@ fn melt_native_tokens() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -297,7 +306,7 @@ fn melt_native_tokens() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -305,7 +314,7 @@ fn melt_native_tokens() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -322,7 +331,7 @@ fn melt_native_tokens() { #[test] fn destroy_foundry_with_account_state_transition() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( @@ -330,6 +339,7 @@ fn destroy_foundry_with_account_state_transition() { ( Account { amount: 50_300, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -357,7 +367,7 @@ fn destroy_foundry_with_account_state_transition() { // Account output gets the amount from the foundry output added let outputs = [account_output]; - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -366,9 +376,13 @@ fn destroy_foundry_with_account_state_transition() { protocol_parameters, ) .with_burn(Burn::new().add_foundry(inputs[1].output.as_foundry().id())) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyFoundryOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); // Account next state assert_eq!(selected.transaction.outputs().len(), 1); @@ -376,7 +390,7 @@ fn destroy_foundry_with_account_state_transition() { #[test] fn destroy_foundry_with_account_burn() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( @@ -384,6 +398,7 @@ fn destroy_foundry_with_account_burn() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -406,6 +421,7 @@ fn destroy_foundry_with_account_burn() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -414,7 +430,7 @@ fn destroy_foundry_with_account_burn() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -427,27 +443,34 @@ fn destroy_foundry_with_account_burn() { .add_foundry(inputs[1].output.as_foundry().id()) .add_account(account_id_2), ) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([ + TransactionCapabilityFlag::DestroyAccountOutputs, + TransactionCapabilityFlag::DestroyFoundryOutputs + ]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } }); } #[test] fn prefer_basic_to_foundry() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -455,6 +478,7 @@ fn prefer_basic_to_foundry() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -475,6 +499,7 @@ fn prefer_basic_to_foundry() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -489,6 +514,7 @@ fn prefer_basic_to_foundry() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -497,7 +523,7 @@ fn prefer_basic_to_foundry() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -505,7 +531,7 @@ fn prefer_basic_to_foundry() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); @@ -515,7 +541,7 @@ fn prefer_basic_to_foundry() { #[test] fn simple_foundry_transition_basic_not_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let mut inputs = build_inputs( @@ -523,6 +549,7 @@ fn simple_foundry_transition_basic_not_needed() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -566,7 +593,7 @@ fn simple_foundry_transition_basic_not_needed() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -574,7 +601,7 @@ fn simple_foundry_transition_basic_not_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 2); @@ -600,7 +627,7 @@ fn simple_foundry_transition_basic_not_needed() { #[test] fn simple_foundry_transition_basic_not_needed_with_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let mut inputs = build_inputs( @@ -608,6 +635,7 @@ fn simple_foundry_transition_basic_not_needed_with_remainder() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -650,7 +678,7 @@ fn simple_foundry_transition_basic_not_needed_with_remainder() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -658,7 +686,7 @@ fn simple_foundry_transition_basic_not_needed_with_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 2); @@ -679,12 +707,12 @@ fn simple_foundry_transition_basic_not_needed_with_remainder() { Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap() ); } else if output.is_basic() { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } else { panic!("unexpected output type") } @@ -718,7 +746,7 @@ fn simple_foundry_transition_basic_not_needed_with_remainder() { // Some(BECH32_ADDRESS_ACCOUNT_SENDER), // )]; -// let selected = InputSelection::new(inputs.clone(), outputs.clone(), [],protocol_parameters) +// let selected = TransactionBuilder::new(inputs.clone(), outputs.clone(), [],protocol_parameters) // .select() // .unwrap(); @@ -761,7 +789,7 @@ fn simple_foundry_transition_basic_not_needed_with_remainder() { #[test] fn mint_and_burn_at_the_same_time() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let foundry_id = FoundryId::build(&AccountAddress::from(account_id_1), 1, SimpleTokenScheme::KIND); let token_id = TokenId::from(foundry_id); @@ -800,7 +828,7 @@ fn mint_and_burn_at_the_same_time() { native_token: Some((&token_id.to_string(), 110)), }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -809,17 +837,18 @@ fn mint_and_burn_at_the_same_time() { protocol_parameters, ) .with_burn(Burn::new().add_native_token(token_id, 10)) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Foundry(id))) if id == foundry_id - )); + TransactionBuilderError::Semantic(TransactionFailureReason::NativeTokenSumUnbalanced) + ); } #[test] fn take_amount_from_account_and_foundry_to_fund_basic() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let foundry_id = FoundryId::build(&AccountAddress::from(account_id_1), 0, SimpleTokenScheme::KIND); let token_id = TokenId::from(foundry_id); @@ -829,6 +858,7 @@ fn take_amount_from_account_and_foundry_to_fund_basic() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -865,6 +895,7 @@ fn take_amount_from_account_and_foundry_to_fund_basic() { }); let outputs = build_outputs([Basic { amount: 3_200_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -873,7 +904,7 @@ fn take_amount_from_account_and_foundry_to_fund_basic() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -881,7 +912,7 @@ fn take_amount_from_account_and_foundry_to_fund_basic() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -902,7 +933,7 @@ fn take_amount_from_account_and_foundry_to_fund_basic() { #[test] fn create_native_token_but_burn_account() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let foundry_id = FoundryId::build(&AccountAddress::from(account_id_1), 1, SimpleTokenScheme::KIND); let token_id = TokenId::from(foundry_id); @@ -912,6 +943,7 @@ fn create_native_token_but_burn_account() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -940,7 +972,7 @@ fn create_native_token_but_burn_account() { native_token: Some((&token_id.to_string(), 100)), }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -949,28 +981,32 @@ fn create_native_token_but_burn_account() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id_1)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); // One output should be added for the remainder. assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 2_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } }); } #[test] fn melted_tokens_not_provided() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let foundry_id = FoundryId::build(&AccountAddress::from(account_id_1), 1, SimpleTokenScheme::KIND); let token_id_1 = TokenId::from(foundry_id); @@ -980,6 +1016,7 @@ fn melted_tokens_not_provided() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1008,7 +1045,7 @@ fn melted_tokens_not_provided() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1016,11 +1053,11 @@ fn melted_tokens_not_provided() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientNativeTokenAmount { + Err(TransactionBuilderError::InsufficientNativeTokenAmount { token_id, found, required, @@ -1029,7 +1066,7 @@ fn melted_tokens_not_provided() { #[test] fn burned_tokens_not_provided() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let foundry_id = FoundryId::build(&AccountAddress::from(account_id_1), 0, SimpleTokenScheme::KIND); let token_id_1 = TokenId::from(foundry_id); @@ -1039,6 +1076,7 @@ fn burned_tokens_not_provided() { ( Account { amount: 2_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1067,7 +1105,7 @@ fn burned_tokens_not_provided() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1076,20 +1114,22 @@ fn burned_tokens_not_provided() { protocol_parameters, ) .with_burn(Burn::new().add_native_token(token_id_1, 100)) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::InsufficientNativeTokenAmount { - token_id, - found, - required, - }) if token_id == token_id_1 && found.as_u32() == 0 && required.as_u32() == 100)); + TransactionBuilderError::InsufficientNativeTokenAmount { + token_id: token_id_1, + found: 0.into(), + required: 100.into(), + } + ); } #[test] fn foundry_in_outputs_and_required() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let mut inputs = build_inputs( @@ -1125,7 +1165,7 @@ fn foundry_in_outputs_and_required() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1134,7 +1174,7 @@ fn foundry_in_outputs_and_required() { protocol_parameters, ) .with_required_inputs([*inputs[1].output_id()]) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1149,7 +1189,7 @@ fn foundry_in_outputs_and_required() { #[test] fn melt_and_burn_native_tokens() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let foundry_id = FoundryId::build(&AccountAddress::from(account_id), 1, SimpleTokenScheme::KIND); let token_id = TokenId::from(foundry_id); @@ -1159,6 +1199,7 @@ fn melt_and_burn_native_tokens() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1201,7 +1242,7 @@ fn melt_and_burn_native_tokens() { native_token: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1211,9 +1252,13 @@ fn melt_and_burn_native_tokens() { ) // Burn 456 native tokens .with_burn(Burn::new().add_native_token(token_id, 456)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnNativeTokens]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); // Account next state + foundry + basic output with native tokens assert_eq!(selected.transaction.outputs().len(), 3); @@ -1226,3 +1271,158 @@ fn melt_and_burn_native_tokens() { } }); } + +#[test] +fn auto_transition_foundry_less_than_min() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + let foundry_id = FoundryId::build(&AccountAddress::from(account_id), 1, SimpleTokenScheme::KIND); + let token_id = TokenId::from(foundry_id); + + let small_amount_foundry = 5; + let small_amount_account = 10; + + let mut inputs = build_inputs( + [( + Foundry { + amount: small_amount_foundry, + account_id, + serial_number: 1, + token_scheme: SimpleTokenScheme::new(1000, 0, 1000).unwrap(), + native_token: Some((&token_id.to_string(), 1000)), + }, + None, + )], + Some(SLOT_INDEX), + ); + let account_output = AccountOutputBuilder::new_with_amount(small_amount_account, account_id) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_foundry_counter(1) + .finish_output() + .unwrap(); + inputs.push(InputSigningData { + output: account_output, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), + chain: None, + }); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .with_required_inputs([*inputs[0].output_id()]) + .finish() + .unwrap_err(); + + let min_amount = FoundryOutputBuilder::from(inputs[0].output.as_foundry()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) + .finish_output() + .unwrap() + .amount() + + AccountOutputBuilder::from(inputs[1].output.as_account()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) + .finish_output() + .unwrap() + .amount(); + + assert_eq!( + selected, + TransactionBuilderError::InsufficientAmount { + found: small_amount_foundry + small_amount_account, + required: min_amount + }, + ); +} + +#[test] +fn auto_transition_foundry_less_than_min_additional() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + let foundry_id = FoundryId::build(&AccountAddress::from(account_id), 1, SimpleTokenScheme::KIND); + let token_id = TokenId::from(foundry_id); + + let small_amount = 5; + + let mut inputs = build_inputs( + [ + ( + Foundry { + amount: small_amount, + account_id, + serial_number: 1, + token_scheme: SimpleTokenScheme::new(1000, 0, 1000).unwrap(), + native_token: Some((&token_id.to_string(), 1000)), + }, + None, + ), + ( + Basic { + amount: 1_000_000, + mana: 0, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + native_token: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); + let account_output = AccountOutputBuilder::new_with_amount(1_000_000, account_id) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_foundry_counter(1) + .finish_output() + .unwrap(); + inputs.push(InputSigningData { + output: account_output, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SLOT_INDEX)), + chain: None, + }); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .with_required_inputs([*inputs[0].output_id()]) + .finish() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 3); + let min_amount_foundry = FoundryOutputBuilder::from(inputs[0].output.as_foundry()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) + .finish_output() + .unwrap() + .amount(); + let foundry_output = selected + .transaction + .outputs() + .iter() + .filter_map(Output::as_foundry_opt) + .find(|o| o.id() == foundry_id) + .unwrap(); + let account_output = selected + .transaction + .outputs() + .iter() + .filter_map(Output::as_account_opt) + .find(|o| o.account_id() == &account_id) + .unwrap(); + assert_eq!(foundry_output.amount(), min_amount_foundry); + assert_eq!(account_output.amount(), 1_000_000); +} diff --git a/sdk/tests/client/input_selection/mod.rs b/sdk/tests/client/transaction_builder/mod.rs similarity index 100% rename from sdk/tests/client/input_selection/mod.rs rename to sdk/tests/client/transaction_builder/mod.rs diff --git a/sdk/tests/client/input_selection/native_tokens.rs b/sdk/tests/client/transaction_builder/native_tokens.rs similarity index 83% rename from sdk/tests/client/input_selection/native_tokens.rs rename to sdk/tests/client/transaction_builder/native_tokens.rs index 2bd0267942..3e6edeeef6 100644 --- a/sdk/tests/client/input_selection/native_tokens.rs +++ b/sdk/tests/client/transaction_builder/native_tokens.rs @@ -4,26 +4,32 @@ use std::str::FromStr; use iota_sdk::{ - client::api::input_selection::{Burn, Error, InputSelection}, - types::block::{address::Address, output::TokenId, protocol::protocol_parameters}, + client::api::transaction_builder::{Burn, TransactionBuilder, TransactionBuilderError}, + types::block::{ + address::Address, + output::{unlock_condition::AddressUnlockCondition, BasicOutputBuilder, NativeToken, TokenId}, + payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, + protocol::{iota_mainnet_protocol_parameters, ProtocolParameters}, + }, }; use pretty_assertions::assert_eq; use primitive_types::U256; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, Build::Basic, BECH32_ADDRESS_ED25519_0, + assert_remainder_or_return, build_inputs, build_outputs, unsorted_eq, Build::Basic, BECH32_ADDRESS_ED25519_0, SLOT_COMMITMENT_ID, SLOT_INDEX, TOKEN_ID_1, TOKEN_ID_2, }; #[test] fn two_native_tokens_one_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 150)), sender: None, @@ -36,6 +42,7 @@ fn two_native_tokens_one_needed() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_2, 100)), sender: None, @@ -50,6 +57,7 @@ fn two_native_tokens_one_needed() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 150)), sender: None, @@ -58,7 +66,7 @@ fn two_native_tokens_one_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -66,7 +74,7 @@ fn two_native_tokens_one_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data[0], inputs[0]); @@ -76,13 +84,14 @@ fn two_native_tokens_one_needed() { #[test] fn two_native_tokens_both_needed_plus_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -95,6 +104,7 @@ fn two_native_tokens_both_needed_plus_remainder() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_2, 150)), sender: None, @@ -110,6 +120,7 @@ fn two_native_tokens_both_needed_plus_remainder() { let outputs = build_outputs([ Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -119,6 +130,7 @@ fn two_native_tokens_both_needed_plus_remainder() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_2, 100)), sender: None, @@ -128,7 +140,7 @@ fn two_native_tokens_both_needed_plus_remainder() { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -136,7 +148,7 @@ fn two_native_tokens_both_needed_plus_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -144,25 +156,26 @@ fn two_native_tokens_both_needed_plus_remainder() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 2_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_2, 50)) - )); + Some((TOKEN_ID_2, 50)), + ); } }); } #[test] fn three_inputs_two_needed_plus_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -175,6 +188,7 @@ fn three_inputs_two_needed_plus_remainder() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -187,6 +201,7 @@ fn three_inputs_two_needed_plus_remainder() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -201,6 +216,7 @@ fn three_inputs_two_needed_plus_remainder() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 120)), sender: None, @@ -209,7 +225,7 @@ fn three_inputs_two_needed_plus_remainder() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -217,7 +233,7 @@ fn three_inputs_two_needed_plus_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 2); @@ -225,25 +241,26 @@ fn three_inputs_two_needed_plus_remainder() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_1, 80)) - )); + Some((TOKEN_ID_1, 80)), + ); } }); } #[test] fn three_inputs_two_needed_no_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -256,6 +273,7 @@ fn three_inputs_two_needed_no_remainder() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -268,6 +286,7 @@ fn three_inputs_two_needed_no_remainder() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -282,6 +301,7 @@ fn three_inputs_two_needed_no_remainder() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -290,7 +310,7 @@ fn three_inputs_two_needed_no_remainder() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -298,7 +318,7 @@ fn three_inputs_two_needed_no_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 2); @@ -307,12 +327,13 @@ fn three_inputs_two_needed_no_remainder() { #[test] fn insufficient_native_tokens_one_input() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -326,6 +347,7 @@ fn insufficient_native_tokens_one_input() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 150)), sender: None, @@ -334,7 +356,7 @@ fn insufficient_native_tokens_one_input() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -342,11 +364,11 @@ fn insufficient_native_tokens_one_input() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientNativeTokenAmount { + Err(TransactionBuilderError::InsufficientNativeTokenAmount { token_id, found, required, @@ -355,13 +377,14 @@ fn insufficient_native_tokens_one_input() { #[test] fn insufficient_native_tokens_three_inputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -374,6 +397,7 @@ fn insufficient_native_tokens_three_inputs() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -386,6 +410,7 @@ fn insufficient_native_tokens_three_inputs() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -400,6 +425,7 @@ fn insufficient_native_tokens_three_inputs() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 301)), sender: None, @@ -408,7 +434,7 @@ fn insufficient_native_tokens_three_inputs() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -416,11 +442,11 @@ fn insufficient_native_tokens_three_inputs() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientNativeTokenAmount { + Err(TransactionBuilderError::InsufficientNativeTokenAmount { token_id, found, required, @@ -429,13 +455,14 @@ fn insufficient_native_tokens_three_inputs() { #[test] fn burn_and_send_at_the_same_time() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -448,6 +475,7 @@ fn burn_and_send_at_the_same_time() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_2, 100)), sender: None, @@ -462,6 +490,7 @@ fn burn_and_send_at_the_same_time() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 50)), sender: None, @@ -470,7 +499,7 @@ fn burn_and_send_at_the_same_time() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -483,32 +512,37 @@ fn burn_and_send_at_the_same_time() { .add_native_token(TokenId::from_str(TOKEN_ID_1).unwrap(), 10) .add_native_token(TokenId::from_str(TOKEN_ID_2).unwrap(), 100), ) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnNativeTokens]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_1, 40)) - )); + Some((TOKEN_ID_1, 40)), + ); } }); } #[test] fn burn_one_input_no_output() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -521,7 +555,7 @@ fn burn_one_input_no_output() { Some(SLOT_INDEX), ); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), None, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -530,28 +564,33 @@ fn burn_one_input_no_output() { protocol_parameters, ) .with_burn(Burn::new().add_native_token(TokenId::from_str(TOKEN_ID_1).unwrap(), 50)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::BurnNativeTokens]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 1); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[0], 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_1, 50)) - )); + Some((TOKEN_ID_1, 50)), + ); } #[test] fn multiple_native_tokens() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -564,6 +603,7 @@ fn multiple_native_tokens() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_2, 100)), sender: None, @@ -578,6 +618,7 @@ fn multiple_native_tokens() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -586,7 +627,7 @@ fn multiple_native_tokens() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -594,22 +635,23 @@ fn multiple_native_tokens() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert!(selected.inputs_data.contains(&inputs[0])); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn insufficient_native_tokens() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -623,6 +665,7 @@ fn insufficient_native_tokens() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 150)), sender: None, @@ -631,7 +674,7 @@ fn insufficient_native_tokens() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -639,11 +682,11 @@ fn insufficient_native_tokens() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientNativeTokenAmount { + Err(TransactionBuilderError::InsufficientNativeTokenAmount { token_id, found, required, @@ -652,12 +695,13 @@ fn insufficient_native_tokens() { #[test] fn insufficient_native_tokens_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -671,6 +715,7 @@ fn insufficient_native_tokens_2() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 150)), sender: None, @@ -679,7 +724,7 @@ fn insufficient_native_tokens_2() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -687,11 +732,11 @@ fn insufficient_native_tokens_2() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientNativeTokenAmount { + Err(TransactionBuilderError::InsufficientNativeTokenAmount { token_id, found, required, @@ -700,12 +745,13 @@ fn insufficient_native_tokens_2() { #[test] fn insufficient_amount_for_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -719,6 +765,7 @@ fn insufficient_amount_for_remainder() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 50)), sender: None, @@ -727,7 +774,9 @@ fn insufficient_amount_for_remainder() { expiration: None, }]); - let selected = InputSelection::new( + let nt_remainder_min_storage_deposit = nt_remainder_min_storage_deposit(&protocol_parameters); + + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -735,25 +784,26 @@ fn insufficient_amount_for_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); - assert!(matches!( - selected, - Err(Error::InsufficientAmount { + assert_eq!( + selected.unwrap_err(), + TransactionBuilderError::InsufficientAmount { found: 1_000_000, - required: 1_106_000, - }) - )); + required: 1_000_000 + nt_remainder_min_storage_deposit, + } + ); } #[test] fn single_output_native_token_no_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -767,6 +817,7 @@ fn single_output_native_token_no_remainder() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -775,7 +826,7 @@ fn single_output_native_token_no_remainder() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -783,21 +834,22 @@ fn single_output_native_token_no_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn single_output_native_token_remainder_1() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -811,6 +863,7 @@ fn single_output_native_token_remainder_1() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 50)), sender: None, @@ -819,7 +872,7 @@ fn single_output_native_token_remainder_1() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -827,28 +880,29 @@ fn single_output_native_token_remainder_1() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[0], 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_1, 50)) - )); + Some((TOKEN_ID_1, 50)), + ); } #[test] fn single_output_native_token_remainder_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -862,6 +916,7 @@ fn single_output_native_token_remainder_2() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -870,7 +925,7 @@ fn single_output_native_token_remainder_2() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -878,29 +933,30 @@ fn single_output_native_token_remainder_2() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[1], 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } #[test] fn two_basic_outputs_1() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -913,6 +969,7 @@ fn two_basic_outputs_1() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -927,6 +984,7 @@ fn two_basic_outputs_1() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -935,7 +993,7 @@ fn two_basic_outputs_1() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -943,30 +1001,40 @@ fn two_basic_outputs_1() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); - assert!(selected.inputs_data.contains(&inputs[0])); + assert!(selected.inputs_data.contains(&inputs[0]) || selected.inputs_data.contains(&inputs[1])); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( - &selected.transaction.outputs()[1], - 500_000, - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_1, 100)), - )); + if selected.inputs_data.contains(&inputs[0]) { + assert_remainder_or_return( + &selected.transaction.outputs()[1], + 500_000, + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + Some((TOKEN_ID_1, 100)), + ); + } else { + assert_remainder_or_return( + &selected.transaction.outputs()[1], + 500_000, + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + Some((TOKEN_ID_1, 200)), + ); + } } #[test] fn two_basic_outputs_2() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -979,6 +1047,7 @@ fn two_basic_outputs_2() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -993,6 +1062,7 @@ fn two_basic_outputs_2() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 50)), sender: None, @@ -1001,7 +1071,7 @@ fn two_basic_outputs_2() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1009,30 +1079,31 @@ fn two_basic_outputs_2() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert!(selected.inputs_data.contains(&inputs[0])); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[1], 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), Some((TOKEN_ID_1, 50)), - )); + ); } #[test] fn two_basic_outputs_3() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1045,6 +1116,7 @@ fn two_basic_outputs_3() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -1059,6 +1131,7 @@ fn two_basic_outputs_3() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 75)), sender: None, @@ -1067,7 +1140,7 @@ fn two_basic_outputs_3() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1075,30 +1148,31 @@ fn two_basic_outputs_3() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert!(selected.inputs_data.contains(&inputs[0])); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[1], 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), Some((TOKEN_ID_1, 25)), - )); + ); } #[test] fn two_basic_outputs_4() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1111,6 +1185,7 @@ fn two_basic_outputs_4() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -1125,6 +1200,7 @@ fn two_basic_outputs_4() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1133,7 +1209,7 @@ fn two_basic_outputs_4() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1141,30 +1217,31 @@ fn two_basic_outputs_4() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert!(selected.inputs_data.contains(&inputs[0])); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[1], 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } #[test] fn two_basic_outputs_5() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1177,6 +1254,7 @@ fn two_basic_outputs_5() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -1191,6 +1269,7 @@ fn two_basic_outputs_5() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1199,7 +1278,7 @@ fn two_basic_outputs_5() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1207,30 +1286,31 @@ fn two_basic_outputs_5() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert!(selected.inputs_data.contains(&inputs[0])); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[1], 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } #[test] fn two_basic_outputs_6() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1243,6 +1323,7 @@ fn two_basic_outputs_6() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -1257,6 +1338,7 @@ fn two_basic_outputs_6() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 250)), sender: None, @@ -1265,7 +1347,7 @@ fn two_basic_outputs_6() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1273,29 +1355,30 @@ fn two_basic_outputs_6() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[1], 1_500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), Some((TOKEN_ID_1, 50)), - )); + ); } #[test] fn two_basic_outputs_7() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1308,6 +1391,7 @@ fn two_basic_outputs_7() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -1322,6 +1406,7 @@ fn two_basic_outputs_7() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 300)), sender: None, @@ -1330,7 +1415,7 @@ fn two_basic_outputs_7() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1338,29 +1423,30 @@ fn two_basic_outputs_7() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[1], 1_500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } #[test] fn two_basic_outputs_8() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1373,6 +1459,7 @@ fn two_basic_outputs_8() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 200)), sender: None, @@ -1387,6 +1474,7 @@ fn two_basic_outputs_8() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 350)), sender: None, @@ -1395,7 +1483,7 @@ fn two_basic_outputs_8() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1403,11 +1491,11 @@ fn two_basic_outputs_8() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientNativeTokenAmount { + Err(TransactionBuilderError::InsufficientNativeTokenAmount { token_id, found, required, @@ -1416,13 +1504,14 @@ fn two_basic_outputs_8() { #[test] fn two_basic_outputs_native_tokens_not_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1435,6 +1524,7 @@ fn two_basic_outputs_native_tokens_not_needed() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1449,6 +1539,7 @@ fn two_basic_outputs_native_tokens_not_needed() { ); let outputs = build_outputs([Basic { amount: 500_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1457,7 +1548,7 @@ fn two_basic_outputs_native_tokens_not_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1465,30 +1556,31 @@ fn two_basic_outputs_native_tokens_not_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert!(selected.inputs_data.contains(&inputs[1])); assert_eq!(selected.transaction.outputs().len(), 2); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[1], 500_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), None, - )); + ); } #[test] fn multiple_remainders() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 5_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1501,6 +1593,7 @@ fn multiple_remainders() { ( Basic { amount: 5_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1513,6 +1606,7 @@ fn multiple_remainders() { ( Basic { amount: 5_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_1, 100)), sender: None, @@ -1525,6 +1619,7 @@ fn multiple_remainders() { ( Basic { amount: 5_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: Some((TOKEN_ID_2, 100)), sender: None, @@ -1539,6 +1634,7 @@ fn multiple_remainders() { ); let outputs = build_outputs([Basic { amount: 15_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1547,7 +1643,9 @@ fn multiple_remainders() { expiration: None, }]); - let selected = InputSelection::new( + let nt_remainder_min_storage_deposit = nt_remainder_min_storage_deposit(&protocol_parameters); + + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1555,32 +1653,45 @@ fn multiple_remainders() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 4); assert_eq!(selected.transaction.outputs().len(), 3); assert!(selected.transaction.outputs().contains(&outputs[0])); - let nt_remainder_min_storage_deposit = 106000; + selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!( - is_remainder_or_return( + if output.native_token().unwrap().token_id().to_string() == TOKEN_ID_1 { + assert_remainder_or_return( output, nt_remainder_min_storage_deposit, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_1, 300)) - ) || is_remainder_or_return( + Some((TOKEN_ID_1, 300)), + ); + } else { + assert_remainder_or_return( output, 5_000_000 - nt_remainder_min_storage_deposit, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - Some((TOKEN_ID_2, 100)) - ) - ); + Some((TOKEN_ID_2, 100)), + ); + } } }); } +pub fn nt_remainder_min_storage_deposit(protocol_parameters: &ProtocolParameters) -> u64 { + BasicOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters()) + .add_unlock_condition(AddressUnlockCondition::from( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_native_token(NativeToken::new(TokenId::from_str(TOKEN_ID_1).unwrap(), 1).unwrap()) + .finish_output() + .unwrap() + .amount() +} + // #[test] // fn higher_nts_count_but_below_max_native_tokens() { // let protocol_parameters = protocol_parameters(); @@ -1627,7 +1738,7 @@ fn multiple_remainders() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs.clone(), // outputs.clone(), // addresses([BECH32_ADDRESS_ED25519_0]), diff --git a/sdk/tests/client/input_selection/nft_outputs.rs b/sdk/tests/client/transaction_builder/nft_outputs.rs similarity index 77% rename from sdk/tests/client/input_selection/nft_outputs.rs rename to sdk/tests/client/transaction_builder/nft_outputs.rs index 8ba0ab353c..1f243a8b3a 100644 --- a/sdk/tests/client/input_selection/nft_outputs.rs +++ b/sdk/tests/client/transaction_builder/nft_outputs.rs @@ -5,20 +5,22 @@ use std::str::FromStr; use iota_sdk::{ client::{ - api::input_selection::{Burn, Error, InputSelection, Requirement}, + api::transaction_builder::{Burn, Requirement, TransactionBuilder, TransactionBuilderError}, secret::types::InputSigningData, }, types::block::{ address::Address, output::{feature::MetadataFeature, unlock_condition::AddressUnlockCondition, NftId, NftOutputBuilder, Output}, - protocol::protocol_parameters, + payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, + protocol::iota_mainnet_protocol_parameters, rand::output::{rand_output_id_with_slot_index, rand_output_metadata_with_id}, + semantic::TransactionFailureReason, }, }; use pretty_assertions::{assert_eq, assert_ne}; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, + assert_remainder_or_return, build_inputs, build_outputs, unsorted_eq, Build::{Basic, Nft}, BECH32_ADDRESS_ACCOUNT_1, BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1, BECH32_ADDRESS_NFT_1, NFT_ID_0, NFT_ID_1, NFT_ID_2, SLOT_COMMITMENT_ID, SLOT_INDEX, @@ -26,13 +28,14 @@ use crate::client::{ #[test] fn input_nft_eq_output_nft() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); let inputs = build_inputs( [( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -46,6 +49,7 @@ fn input_nft_eq_output_nft() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -54,7 +58,7 @@ fn input_nft_eq_output_nft() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -62,22 +66,23 @@ fn input_nft_eq_output_nft() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn transition_nft_id_zero() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( [( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -92,6 +97,7 @@ fn transition_nft_id_zero() { let nft_id = NftId::from(inputs[0].output_id()); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -100,7 +106,7 @@ fn transition_nft_id_zero() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -108,11 +114,11 @@ fn transition_nft_id_zero() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } // #[test] @@ -142,7 +148,7 @@ fn transition_nft_id_zero() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs, // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -189,7 +195,7 @@ fn transition_nft_id_zero() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs.clone(), // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -205,13 +211,14 @@ fn transition_nft_id_zero() { #[test] fn mint_nft() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -225,6 +232,7 @@ fn mint_nft() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -233,7 +241,7 @@ fn mint_nft() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -241,7 +249,7 @@ fn mint_nft() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -259,13 +267,14 @@ fn mint_nft() { #[test] fn burn_nft() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); let inputs = build_inputs( [( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -279,6 +288,7 @@ fn burn_nft() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -287,7 +297,7 @@ fn burn_nft() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -296,11 +306,15 @@ fn burn_nft() { protocol_parameters, ) .with_burn(Burn::new().add_nft(nft_id_2)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyNftOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } // #[test] @@ -331,7 +345,7 @@ fn burn_nft() { // None, // )]); -// let selected = InputSelection::new( +// let selected = TransactionBuilder::new( // inputs, // outputs, // [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -350,13 +364,14 @@ fn burn_nft() { #[test] fn missing_input_for_nft_output() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -370,6 +385,7 @@ fn missing_input_for_nft_output() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -378,7 +394,7 @@ fn missing_input_for_nft_output() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -386,23 +402,24 @@ fn missing_input_for_nft_output() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Nft(nft_id))) if nft_id == nft_id_2 + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Nft(nft_id))) if nft_id == nft_id_2 )); } #[test] fn missing_input_for_nft_output_but_created() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -416,6 +433,7 @@ fn missing_input_for_nft_output_but_created() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -424,7 +442,7 @@ fn missing_input_for_nft_output_but_created() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -432,14 +450,14 @@ fn missing_input_for_nft_output_but_created() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(selected.is_ok()); } #[test] fn nft_in_output_and_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -447,6 +465,7 @@ fn nft_in_output_and_sender() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -459,6 +478,7 @@ fn nft_in_output_and_sender() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -474,6 +494,7 @@ fn nft_in_output_and_sender() { let outputs = build_outputs([ Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -483,6 +504,7 @@ fn nft_in_output_and_sender() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap()), @@ -492,7 +514,7 @@ fn nft_in_output_and_sender() { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -500,7 +522,7 @@ fn nft_in_output_and_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -517,13 +539,14 @@ fn nft_in_output_and_sender() { #[test] fn missing_ed25519_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); let inputs = build_inputs( [( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -537,6 +560,7 @@ fn missing_ed25519_sender() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()), @@ -545,7 +569,7 @@ fn missing_ed25519_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -553,23 +577,24 @@ fn missing_ed25519_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() )); } #[test] fn missing_ed25519_issuer_created() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -583,6 +608,7 @@ fn missing_ed25519_issuer_created() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -591,7 +617,7 @@ fn missing_ed25519_issuer_created() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -599,23 +625,24 @@ fn missing_ed25519_issuer_created() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap() )); } #[test] fn missing_ed25519_issuer_transition() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( [( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -629,6 +656,7 @@ fn missing_ed25519_issuer_transition() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -637,7 +665,7 @@ fn missing_ed25519_issuer_transition() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -645,20 +673,21 @@ fn missing_ed25519_issuer_transition() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(selected.is_ok()); } #[test] fn missing_account_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); let inputs = build_inputs( [( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -672,6 +701,7 @@ fn missing_account_sender() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), @@ -680,7 +710,7 @@ fn missing_account_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -688,23 +718,24 @@ fn missing_account_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() )); } #[test] fn missing_account_issuer_created() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -718,6 +749,7 @@ fn missing_account_issuer_created() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -726,7 +758,7 @@ fn missing_account_issuer_created() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -734,23 +766,24 @@ fn missing_account_issuer_created() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap() )); } #[test] fn missing_account_issuer_transition() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); let inputs = build_inputs( [( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -764,6 +797,7 @@ fn missing_account_issuer_transition() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -772,7 +806,7 @@ fn missing_account_issuer_transition() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -780,20 +814,21 @@ fn missing_account_issuer_transition() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(selected.is_ok()); } #[test] fn missing_nft_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); let inputs = build_inputs( [( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -807,6 +842,7 @@ fn missing_nft_sender() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: Some(Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap()), @@ -815,7 +851,7 @@ fn missing_nft_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -823,23 +859,24 @@ fn missing_nft_sender() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Sender(sender))) if sender == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() )); } #[test] fn missing_nft_issuer_created() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -853,6 +890,7 @@ fn missing_nft_issuer_created() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -861,7 +899,7 @@ fn missing_nft_issuer_created() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -869,23 +907,24 @@ fn missing_nft_issuer_created() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() + Err(TransactionBuilderError::UnfulfillableRequirement(Requirement::Issuer(issuer))) if issuer == Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap() )); } #[test] fn missing_nft_issuer_transition() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_2 = NftId::from_str(NFT_ID_2).unwrap(); let inputs = build_inputs( [( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -899,6 +938,7 @@ fn missing_nft_issuer_transition() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -907,7 +947,7 @@ fn missing_nft_issuer_transition() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -915,14 +955,14 @@ fn missing_nft_issuer_transition() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(selected.is_ok()); } #[test] fn increase_nft_amount() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -930,6 +970,7 @@ fn increase_nft_amount() { ( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -942,6 +983,7 @@ fn increase_nft_amount() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -956,6 +998,7 @@ fn increase_nft_amount() { ); let outputs = build_outputs([Nft { amount: 3_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -964,7 +1007,7 @@ fn increase_nft_amount() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -972,16 +1015,16 @@ fn increase_nft_amount() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn decrease_nft_amount() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -989,6 +1032,7 @@ fn decrease_nft_amount() { ( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1001,6 +1045,7 @@ fn decrease_nft_amount() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1015,6 +1060,7 @@ fn decrease_nft_amount() { ); let outputs = build_outputs([Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1023,7 +1069,7 @@ fn decrease_nft_amount() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1031,7 +1077,7 @@ fn decrease_nft_amount() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); @@ -1040,19 +1086,19 @@ fn decrease_nft_amount() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } }); } #[test] fn prefer_basic_to_nft() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -1060,6 +1106,7 @@ fn prefer_basic_to_nft() { ( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1072,6 +1119,7 @@ fn prefer_basic_to_nft() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1086,6 +1134,7 @@ fn prefer_basic_to_nft() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1094,7 +1143,7 @@ fn prefer_basic_to_nft() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1102,7 +1151,7 @@ fn prefer_basic_to_nft() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); @@ -1112,7 +1161,7 @@ fn prefer_basic_to_nft() { #[test] fn take_amount_from_nft_to_fund_basic() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -1120,6 +1169,7 @@ fn take_amount_from_nft_to_fund_basic() { ( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1132,6 +1182,7 @@ fn take_amount_from_nft_to_fund_basic() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1146,6 +1197,7 @@ fn take_amount_from_nft_to_fund_basic() { ); let outputs = build_outputs([Basic { amount: 1_200_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1154,7 +1206,7 @@ fn take_amount_from_nft_to_fund_basic() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1162,7 +1214,7 @@ fn take_amount_from_nft_to_fund_basic() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1184,7 +1236,7 @@ fn take_amount_from_nft_to_fund_basic() { #[test] fn nft_burn_should_validate_nft_sender() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -1192,6 +1244,7 @@ fn nft_burn_should_validate_nft_sender() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1204,6 +1257,7 @@ fn nft_burn_should_validate_nft_sender() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1218,6 +1272,7 @@ fn nft_burn_should_validate_nft_sender() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap()), @@ -1226,7 +1281,7 @@ fn nft_burn_should_validate_nft_sender() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1235,16 +1290,20 @@ fn nft_burn_should_validate_nft_sender() { protocol_parameters, ) .with_burn(Burn::new().add_nft(nft_id_1)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyNftOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn nft_burn_should_validate_nft_address() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); let inputs = build_inputs( @@ -1252,6 +1311,7 @@ fn nft_burn_should_validate_nft_address() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_NFT_1).unwrap(), native_token: None, sender: None, @@ -1264,6 +1324,7 @@ fn nft_burn_should_validate_nft_address() { ( Nft { amount: 1_000_000, + mana: 0, nft_id: nft_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1278,6 +1339,7 @@ fn nft_burn_should_validate_nft_address() { ); let outputs = build_outputs([Basic { amount: 3_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1286,7 +1348,7 @@ fn nft_burn_should_validate_nft_address() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1295,22 +1357,27 @@ fn nft_burn_should_validate_nft_address() { protocol_parameters, ) .with_burn(Burn::new().add_nft(nft_id_1)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyNftOutputs]) + ); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn transitioned_zero_nft_id_no_longer_is_zero() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_0 = NftId::from_str(NFT_ID_0).unwrap(); let inputs = build_inputs( [( Nft { amount: 2_000_000, + mana: 0, nft_id: nft_id_0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -1324,6 +1391,7 @@ fn transitioned_zero_nft_id_no_longer_is_zero() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -1332,7 +1400,7 @@ fn transitioned_zero_nft_id_no_longer_is_zero() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1340,7 +1408,7 @@ fn transitioned_zero_nft_id_no_longer_is_zero() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -1363,7 +1431,7 @@ fn transitioned_zero_nft_id_no_longer_is_zero() { #[test] fn changed_immutable_metadata() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); #[cfg(feature = "irc_27")] @@ -1410,7 +1478,7 @@ fn changed_immutable_metadata() { let outputs = [updated_nft_output]; - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -1418,12 +1486,130 @@ fn changed_immutable_metadata() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::UnfulfillableRequirement(Requirement::Nft( - nft_id, - ))) if nft_id == nft_id_1 - )); + TransactionBuilderError::Semantic(TransactionFailureReason::ChainOutputImmutableFeaturesChanged) + ); +} + +#[test] +fn auto_transition_nft_less_than_min() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); + + let small_amount = 5; + + let inputs = build_inputs( + [( + Nft { + amount: small_amount, + mana: 0, + nft_id: nft_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + sdruc: None, + expiration: None, + }, + None, + )], + Some(SLOT_INDEX), + ); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .with_required_inputs([*inputs[0].output_id()]) + .finish() + .unwrap_err(); + + let min_amount = NftOutputBuilder::from(inputs[0].output.as_nft()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) + .finish_output() + .unwrap() + .amount(); + + assert_eq!( + selected, + TransactionBuilderError::InsufficientAmount { + found: small_amount, + required: min_amount + }, + ); +} + +#[test] +fn auto_transition_nft_less_than_min_additional() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); + + let small_amount = 5; + + let inputs = build_inputs( + [ + ( + Nft { + amount: small_amount, + mana: 0, + nft_id: nft_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + sdruc: None, + expiration: None, + }, + None, + ), + ( + Basic { + amount: 1_000_000, + mana: 0, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + native_token: None, + sdruc: None, + timelock: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); + + let selected = TransactionBuilder::new( + inputs.clone(), + None, + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .with_required_inputs([*inputs[0].output_id()]) + .finish() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 2); + let min_amount = NftOutputBuilder::from(inputs[0].output.as_nft()) + .with_minimum_amount(protocol_parameters.storage_score_parameters()) + .finish_output() + .unwrap() + .amount(); + let nft_output = selected + .transaction + .outputs() + .iter() + .filter_map(Output::as_nft_opt) + .find(|o| o.nft_id() == &nft_id_1) + .unwrap(); + assert_eq!(nft_output.amount(), min_amount); } diff --git a/sdk/tests/client/input_selection/outputs.rs b/sdk/tests/client/transaction_builder/outputs.rs similarity index 62% rename from sdk/tests/client/input_selection/outputs.rs rename to sdk/tests/client/transaction_builder/outputs.rs index 1f15170983..24d8f778f6 100644 --- a/sdk/tests/client/input_selection/outputs.rs +++ b/sdk/tests/client/transaction_builder/outputs.rs @@ -5,31 +5,34 @@ use std::{collections::HashSet, str::FromStr}; use iota_sdk::{ client::{ - api::input_selection::{Burn, Error, InputSelection}, + api::transaction_builder::{Burn, TransactionBuilder, TransactionBuilderError}, secret::types::InputSigningData, }, types::block::{ address::Address, - output::{unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder}, - protocol::protocol_parameters, + output::{unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, NftId}, + payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, + protocol::iota_mainnet_protocol_parameters, rand::output::{rand_output_id_with_slot_index, rand_output_metadata_with_id}, }, }; use pretty_assertions::assert_eq; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, - Build::{Account, Basic}, - ACCOUNT_ID_1, ACCOUNT_ID_2, BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1, SLOT_COMMITMENT_ID, SLOT_INDEX, + assert_remainder_or_return, build_inputs, build_outputs, unsorted_eq, + Build::{Account, Basic, Nft}, + ACCOUNT_ID_1, ACCOUNT_ID_2, BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1, NFT_ID_1, SLOT_COMMITMENT_ID, + SLOT_INDEX, }; #[test] fn no_inputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = Vec::new(); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -38,7 +41,7 @@ fn no_inputs() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -46,19 +49,23 @@ fn no_inputs() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); - assert!(matches!(selected, Err(Error::NoAvailableInputsProvided))); + assert!(matches!( + selected, + Err(TransactionBuilderError::NoAvailableInputsProvided) + )); } #[test] fn no_outputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -72,7 +79,7 @@ fn no_outputs() { ); let outputs = Vec::new(); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -80,19 +87,20 @@ fn no_outputs() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); - assert!(matches!(selected, Err(Error::InvalidOutputCount(0)))); + assert!(matches!(selected, Err(TransactionBuilderError::InvalidOutputCount(0)))); } #[test] fn no_outputs_but_required_input() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -106,7 +114,7 @@ fn no_outputs_but_required_input() { ); let outputs = Vec::new(); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -115,29 +123,30 @@ fn no_outputs_but_required_input() { protocol_parameters, ) .with_required_inputs(HashSet::from([*inputs[0].output_id()])) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data, inputs); // Just a remainder assert_eq!(selected.transaction.outputs().len(), 1); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[0], 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } #[test] fn no_outputs_but_burn() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); let inputs = build_inputs( [( Account { amount: 2_000_000, + mana: 0, account_id: account_id_2, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -149,7 +158,7 @@ fn no_outputs_but_burn() { ); let outputs = Vec::new(); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -158,27 +167,32 @@ fn no_outputs_but_burn() { protocol_parameters, ) .with_burn(Burn::new().add_account(account_id_2)) - .select() + .finish() .unwrap(); + assert_eq!( + selected.transaction.capabilities(), + &TransactionCapabilities::from([TransactionCapabilityFlag::DestroyAccountOutputs]) + ); assert_eq!(selected.inputs_data, inputs); assert_eq!(selected.transaction.outputs().len(), 1); - assert!(is_remainder_or_return( + assert_remainder_or_return( &selected.transaction.outputs()[0], 2_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } #[test] fn no_address_provided() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -192,6 +206,7 @@ fn no_address_provided() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -200,7 +215,7 @@ fn no_address_provided() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, None, @@ -208,19 +223,23 @@ fn no_address_provided() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); - assert!(matches!(selected, Err(Error::NoAvailableInputsProvided))); + assert!(matches!( + selected, + Err(TransactionBuilderError::NoAvailableInputsProvided) + )); } #[test] fn no_matching_address_provided() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -234,6 +253,7 @@ fn no_matching_address_provided() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -242,7 +262,7 @@ fn no_matching_address_provided() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()], @@ -250,20 +270,24 @@ fn no_matching_address_provided() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); - assert!(matches!(selected, Err(Error::NoAvailableInputsProvided))); + assert!(matches!( + selected, + Err(TransactionBuilderError::NoAvailableInputsProvided) + )); } #[test] fn two_addresses_one_missing() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -276,6 +300,7 @@ fn two_addresses_one_missing() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -290,6 +315,7 @@ fn two_addresses_one_missing() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -298,7 +324,7 @@ fn two_addresses_one_missing() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -306,26 +332,28 @@ fn two_addresses_one_missing() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish() + .unwrap_err(); - assert!(matches!( + assert_eq!( selected, - Err(Error::InsufficientAmount { + TransactionBuilderError::InsufficientAmount { found: 1_000_000, required: 2_000_000, - }) - )); + } + ); } #[test] fn two_addresses() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -338,6 +366,7 @@ fn two_addresses() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -352,6 +381,7 @@ fn two_addresses() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -360,7 +390,7 @@ fn two_addresses() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -371,43 +401,43 @@ fn two_addresses() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn consolidate_with_min_allotment() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = [ BasicOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters()) - .with_mana(1000) + .with_mana(9860) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .finish_output() .unwrap(), BasicOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters()) - .with_mana(2000) + .with_mana(9860) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .finish_output() .unwrap(), BasicOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters()) - .with_mana(1000) + .with_mana(9860) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) .finish_output() .unwrap(), BasicOutputBuilder::new_with_minimum_amount(protocol_parameters.storage_score_parameters()) - .with_mana(1000) + .with_mana(9860) .add_unlock_condition(AddressUnlockCondition::new( Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), )) @@ -423,7 +453,7 @@ fn consolidate_with_min_allotment() { }) .collect::>(); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), None, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -433,11 +463,131 @@ fn consolidate_with_min_allotment() { ) .with_min_mana_allotment(account_id_1, 10) .with_required_inputs(inputs.iter().map(|i| *i.output_id())) - .select() + .finish() .unwrap(); assert_eq!(selected.transaction.outputs().len(), 1); assert_eq!(selected.transaction.allotments().len(), 1); - assert_eq!(selected.transaction.allotments()[0].mana(), 5000); + assert_eq!(selected.transaction.allotments()[0].mana(), 39440); assert_eq!(selected.transaction.outputs().iter().map(|o| o.mana()).sum::(), 0); } + +#[test] +fn transition_no_more_than_needed_for_account_amount() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); + let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); + + let inputs = build_inputs( + [ + ( + Account { + amount: 500_000, + mana: 0, + account_id: account_id_2, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }, + None, + ), + ( + Nft { + amount: 500_000, + mana: 0, + nft_id: nft_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + sdruc: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); + let outputs = build_outputs([Account { + amount: 500_000, + mana: 0, + account_id: account_id_2, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }]); + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .finish() + .unwrap(); + + assert_eq!(selected.inputs_data.len(), 1); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); +} + +#[test] +fn transition_no_more_than_needed_for_nft_amount() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_2 = AccountId::from_str(ACCOUNT_ID_2).unwrap(); + let nft_id_1 = NftId::from_str(NFT_ID_1).unwrap(); + + let inputs = build_inputs( + [ + ( + Account { + amount: 500_000, + mana: 0, + account_id: account_id_2, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + }, + None, + ), + ( + Nft { + amount: 500_000, + mana: 0, + nft_id: nft_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + sdruc: None, + expiration: None, + }, + None, + ), + ], + Some(SLOT_INDEX), + ); + let outputs = build_outputs([Nft { + amount: 500_000, + mana: 0, + nft_id: nft_id_1, + address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + sender: None, + issuer: None, + sdruc: None, + expiration: None, + }]); + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .finish() + .unwrap(); + + assert_eq!(selected.inputs_data.len(), 1); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); +} diff --git a/sdk/tests/client/input_selection/storage_deposit_return.rs b/sdk/tests/client/transaction_builder/storage_deposit_return.rs similarity index 84% rename from sdk/tests/client/input_selection/storage_deposit_return.rs rename to sdk/tests/client/transaction_builder/storage_deposit_return.rs index 52aeacc99b..50393f63bd 100644 --- a/sdk/tests/client/input_selection/storage_deposit_return.rs +++ b/sdk/tests/client/transaction_builder/storage_deposit_return.rs @@ -4,13 +4,13 @@ use std::str::FromStr; use iota_sdk::{ - client::api::input_selection::{Error, InputSelection}, - types::block::{address::Address, output::AccountId, protocol::protocol_parameters}, + client::api::transaction_builder::{TransactionBuilder, TransactionBuilderError}, + types::block::{address::Address, output::AccountId, protocol::iota_mainnet_protocol_parameters}, }; use pretty_assertions::assert_eq; use crate::client::{ - build_inputs, build_outputs, is_remainder_or_return, unsorted_eq, + assert_remainder_or_return, build_inputs, build_outputs, unsorted_eq, Build::{Account, Basic}, ACCOUNT_ID_1, BECH32_ADDRESS_ACCOUNT_1, BECH32_ADDRESS_ED25519_0, BECH32_ADDRESS_ED25519_1, BECH32_ADDRESS_ED25519_2, SLOT_COMMITMENT_ID, SLOT_INDEX, @@ -18,12 +18,13 @@ use crate::client::{ #[test] fn sdruc_output_not_provided_no_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -37,6 +38,7 @@ fn sdruc_output_not_provided_no_remainder() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -45,7 +47,7 @@ fn sdruc_output_not_provided_no_remainder() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -53,7 +55,7 @@ fn sdruc_output_not_provided_no_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -61,24 +63,25 @@ fn sdruc_output_not_provided_no_remainder() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), - None - )); + None, + ); } }); } #[test] fn sdruc_output_provided_no_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -93,6 +96,7 @@ fn sdruc_output_provided_no_remainder() { let outputs = build_outputs([ Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -102,6 +106,7 @@ fn sdruc_output_provided_no_remainder() { }, Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -111,7 +116,7 @@ fn sdruc_output_provided_no_remainder() { }, ]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -119,21 +124,22 @@ fn sdruc_output_provided_no_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn sdruc_output_provided_remainder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -147,6 +153,7 @@ fn sdruc_output_provided_remainder() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -155,7 +162,7 @@ fn sdruc_output_provided_remainder() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -163,7 +170,7 @@ fn sdruc_output_provided_remainder() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -171,25 +178,26 @@ fn sdruc_output_provided_remainder() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - None - )); + None, + ); } }); } #[test] fn two_sdrucs_to_the_same_address_both_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -202,6 +210,7 @@ fn two_sdrucs_to_the_same_address_both_needed() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -216,6 +225,7 @@ fn two_sdrucs_to_the_same_address_both_needed() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -224,7 +234,7 @@ fn two_sdrucs_to_the_same_address_both_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -232,7 +242,7 @@ fn two_sdrucs_to_the_same_address_both_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -240,25 +250,26 @@ fn two_sdrucs_to_the_same_address_both_needed() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 2_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), - None - )); + None, + ); } }); } #[test] fn two_sdrucs_to_the_same_address_one_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -271,6 +282,7 @@ fn two_sdrucs_to_the_same_address_one_needed() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -285,6 +297,7 @@ fn two_sdrucs_to_the_same_address_one_needed() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -293,7 +306,7 @@ fn two_sdrucs_to_the_same_address_one_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -301,7 +314,7 @@ fn two_sdrucs_to_the_same_address_one_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); @@ -310,25 +323,26 @@ fn two_sdrucs_to_the_same_address_one_needed() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), - None - )); + None, + ); } }); } #[test] fn two_sdrucs_to_different_addresses_both_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -341,6 +355,7 @@ fn two_sdrucs_to_different_addresses_both_needed() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -355,6 +370,7 @@ fn two_sdrucs_to_different_addresses_both_needed() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -363,7 +379,7 @@ fn two_sdrucs_to_different_addresses_both_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -371,39 +387,46 @@ fn two_sdrucs_to_different_addresses_both_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 3); assert!(selected.transaction.outputs().contains(&outputs[0])); - assert!(selected.transaction.outputs().iter().any(|output| { - is_remainder_or_return( - output, - 1_000_000, - Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), - None, - ) - })); - assert!(selected.transaction.outputs().iter().any(|output| { - is_remainder_or_return( - output, - 1_000_000, - Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap(), - None, - ) - })); + assert_remainder_or_return( + selected + .transaction + .outputs() + .iter() + .find(|o| o.as_basic().address() == &Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap()) + .unwrap(), + 1_000_000, + Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), + None, + ); + assert_remainder_or_return( + selected + .transaction + .outputs() + .iter() + .find(|o| o.as_basic().address() == &Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap()) + .unwrap(), + 1_000_000, + Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap(), + None, + ); } #[test] fn two_sdrucs_to_different_addresses_one_needed() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -416,6 +439,7 @@ fn two_sdrucs_to_different_addresses_one_needed() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -430,6 +454,7 @@ fn two_sdrucs_to_different_addresses_one_needed() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -438,7 +463,7 @@ fn two_sdrucs_to_different_addresses_one_needed() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -446,7 +471,7 @@ fn two_sdrucs_to_different_addresses_one_needed() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); @@ -455,24 +480,25 @@ fn two_sdrucs_to_different_addresses_one_needed() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), - None - )); + None, + ); } }); } #[test] fn insufficient_amount_because_of_sdruc() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -486,6 +512,7 @@ fn insufficient_amount_because_of_sdruc() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -494,7 +521,7 @@ fn insufficient_amount_because_of_sdruc() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -502,11 +529,11 @@ fn insufficient_amount_because_of_sdruc() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select(); + .finish(); assert!(matches!( selected, - Err(Error::InsufficientAmount { + Err(TransactionBuilderError::InsufficientAmount { found: 2_000_000, required: 3_000_000, }) @@ -515,13 +542,14 @@ fn insufficient_amount_because_of_sdruc() { #[test] fn useless_sdruc_required_for_sender_feature() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -534,6 +562,7 @@ fn useless_sdruc_required_for_sender_feature() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -548,6 +577,7 @@ fn useless_sdruc_required_for_sender_feature() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()), @@ -556,7 +586,7 @@ fn useless_sdruc_required_for_sender_feature() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [ @@ -567,7 +597,7 @@ fn useless_sdruc_required_for_sender_feature() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -575,19 +605,19 @@ fn useless_sdruc_required_for_sender_feature() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap(), - None - )); + None, + ); } }); } #[test] fn sdruc_required_non_ed25519_in_address_unlock() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -595,6 +625,7 @@ fn sdruc_required_non_ed25519_in_address_unlock() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap(), native_token: None, sender: None, @@ -607,6 +638,7 @@ fn sdruc_required_non_ed25519_in_address_unlock() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -619,6 +651,7 @@ fn sdruc_required_non_ed25519_in_address_unlock() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap(), native_token: None, sender: Some(Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap()), @@ -627,7 +660,7 @@ fn sdruc_required_non_ed25519_in_address_unlock() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -635,7 +668,7 @@ fn sdruc_required_non_ed25519_in_address_unlock() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); @@ -643,19 +676,19 @@ fn sdruc_required_non_ed25519_in_address_unlock() { assert!(selected.transaction.outputs().contains(&outputs[0])); selected.transaction.outputs().iter().for_each(|output| { if !outputs.contains(output) && !output.is_account() { - assert!(is_remainder_or_return( + assert_remainder_or_return( output, 1_000_000, Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap(), - None - )); + None, + ); } }); } #[test] fn useless_sdruc_non_ed25519_in_address_unlock() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); let inputs = build_inputs( @@ -663,6 +696,7 @@ fn useless_sdruc_non_ed25519_in_address_unlock() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap(), native_token: None, sender: None, @@ -675,6 +709,7 @@ fn useless_sdruc_non_ed25519_in_address_unlock() { ( Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ACCOUNT_1).unwrap(), native_token: None, sender: None, @@ -687,6 +722,7 @@ fn useless_sdruc_non_ed25519_in_address_unlock() { ( Account { amount: 1_000_000, + mana: 0, account_id: account_id_1, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), sender: None, @@ -699,6 +735,7 @@ fn useless_sdruc_non_ed25519_in_address_unlock() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_2).unwrap(), native_token: None, sender: None, @@ -707,7 +744,7 @@ fn useless_sdruc_non_ed25519_in_address_unlock() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -715,7 +752,7 @@ fn useless_sdruc_non_ed25519_in_address_unlock() { SLOT_COMMITMENT_ID, protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 2); diff --git a/sdk/tests/client/input_selection/timelock.rs b/sdk/tests/client/transaction_builder/timelock.rs similarity index 81% rename from sdk/tests/client/input_selection/timelock.rs rename to sdk/tests/client/transaction_builder/timelock.rs index 974e40accb..2a63c2b8c3 100644 --- a/sdk/tests/client/input_selection/timelock.rs +++ b/sdk/tests/client/transaction_builder/timelock.rs @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::{ - client::api::input_selection::{Error, InputSelection}, + client::api::transaction_builder::{TransactionBuilder, TransactionBuilderError}, types::block::{ address::Address, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, slot::{SlotCommitmentHash, SlotIndex}, }, }; @@ -17,12 +17,13 @@ use crate::client::{ #[test] fn one_output_timelock_not_expired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -36,6 +37,7 @@ fn one_output_timelock_not_expired() { ); let outputs = build_outputs([Basic { amount: 1_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -44,7 +46,7 @@ fn one_output_timelock_not_expired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs, outputs, [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -52,19 +54,23 @@ fn one_output_timelock_not_expired() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select(); + .finish(); - assert!(matches!(selected, Err(Error::NoAvailableInputsProvided))); + assert!(matches!( + selected, + Err(TransactionBuilderError::NoAvailableInputsProvided) + )); } #[test] fn timelock_equal_timestamp() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -78,6 +84,7 @@ fn timelock_equal_timestamp() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -86,7 +93,7 @@ fn timelock_equal_timestamp() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -94,22 +101,23 @@ fn timelock_equal_timestamp() { SlotCommitmentHash::null().into_slot_commitment_id(199), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_outputs_one_timelock_expired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -122,6 +130,7 @@ fn two_outputs_one_timelock_expired() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -136,6 +145,7 @@ fn two_outputs_one_timelock_expired() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -144,7 +154,7 @@ fn two_outputs_one_timelock_expired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -152,23 +162,24 @@ fn two_outputs_one_timelock_expired() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[1]); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn two_outputs_one_timelocked_one_missing() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [ ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -181,6 +192,7 @@ fn two_outputs_one_timelocked_one_missing() { ( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -195,6 +207,7 @@ fn two_outputs_one_timelocked_one_missing() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -203,7 +216,7 @@ fn two_outputs_one_timelocked_one_missing() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -211,22 +224,23 @@ fn two_outputs_one_timelocked_one_missing() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert_eq!(selected.inputs_data.len(), 1); assert_eq!(selected.inputs_data[0], inputs[1]); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } #[test] fn one_output_timelock_expired() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let inputs = build_inputs( [( Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), native_token: None, sender: None, @@ -240,6 +254,7 @@ fn one_output_timelock_expired() { ); let outputs = build_outputs([Basic { amount: 2_000_000, + mana: 0, address: Address::try_from_bech32(BECH32_ADDRESS_ED25519_1).unwrap(), native_token: None, sender: None, @@ -248,7 +263,7 @@ fn one_output_timelock_expired() { expiration: None, }]); - let selected = InputSelection::new( + let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], @@ -256,9 +271,9 @@ fn one_output_timelock_expired() { SlotCommitmentHash::null().into_slot_commitment_id(99), protocol_parameters, ) - .select() + .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert!(unsorted_eq(&selected.transaction.outputs(), &outputs)); + assert!(unsorted_eq(selected.transaction.outputs(), &outputs)); } diff --git a/sdk/tests/mod.rs b/sdk/tests/mod.rs index 3007e18930..f13f5139e1 100644 --- a/sdk/tests/mod.rs +++ b/sdk/tests/mod.rs @@ -1,9 +1,9 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "client")] +#[cfg(all(feature = "protocol_parameters_samples", feature = "client"))] mod client; mod types; mod utils; -#[cfg(feature = "wallet")] +#[cfg(all(feature = "protocol_parameters_samples", feature = "wallet"))] mod wallet; diff --git a/sdk/tests/types/address/account.rs b/sdk/tests/types/address/account.rs index 916a065f60..4179cb4e5e 100644 --- a/sdk/tests/types/address/account.rs +++ b/sdk/tests/types/address/account.rs @@ -141,10 +141,12 @@ fn serde_invalid_account_id() { "accountId": ACCOUNT_ID_INVALID, }); - assert!(matches!( - serde_json::from_value::(account_address_ser), - Err(e) if e.to_string() == "hex error: Invalid hex string length for slice: expected 64 got 61" - )); + assert_eq!( + serde_json::from_value::(account_address_ser) + .unwrap_err() + .to_string(), + "Invalid hex string length for slice: expected 64 got 61" + ); } #[test] @@ -167,7 +169,7 @@ fn pack_unpack() { assert_eq!( address, - PackableExt::unpack_verified(packed_address.as_slice(), &()).unwrap() + PackableExt::unpack_bytes_verified(packed_address.as_slice(), &()).unwrap() ); let address = Address::from(AccountAddress::from_str(ACCOUNT_ID).unwrap()); @@ -175,6 +177,6 @@ fn pack_unpack() { assert_eq!( address, - PackableExt::unpack_verified(packed_address.as_slice(), &()).unwrap() + PackableExt::unpack_bytes_verified(packed_address.as_slice(), &()).unwrap() ); } diff --git a/sdk/tests/types/address/bech32.rs b/sdk/tests/types/address/bech32.rs index 55ae7eb4ea..5fa80dea88 100644 --- a/sdk/tests/types/address/bech32.rs +++ b/sdk/tests/types/address/bech32.rs @@ -3,10 +3,7 @@ use core::str::FromStr; -use iota_sdk::types::block::{ - address::{Address, Bech32Address, Ed25519Address, Hrp}, - Error, -}; +use iota_sdk::types::block::address::{Address, AddressError, Bech32Address, Ed25519Address, Hrp}; use packable::PackableExt; use pretty_assertions::assert_eq; @@ -40,7 +37,7 @@ fn ctors() { fn hrp_from_str() { Hrp::from_str("rms").unwrap(); - assert!(matches!(Hrp::from_str("中國"), Err(Error::InvalidBech32Hrp(_)))); + assert!(matches!(Hrp::from_str("中國"), Err(AddressError::Bech32Hrp(_)))); } #[test] @@ -56,14 +53,14 @@ fn hrp_pack_unpack() { let hrp = Hrp::from_str("rms").unwrap(); let packed_hrp = hrp.pack_to_vec(); - assert_eq!(hrp, Hrp::unpack_verified(packed_hrp.as_slice(), &()).unwrap()); + assert_eq!(hrp, Hrp::unpack_bytes_verified(packed_hrp.as_slice(), &()).unwrap()); } #[test] fn invalid_hrp_unpack() { let packed_hrp = vec![32, 32, 32]; // invalid HRP: " " - assert!(Hrp::unpack_verified(packed_hrp.as_slice(), &()).is_err()); + assert!(Hrp::unpack_bytes_verified(packed_hrp.as_slice(), &()).is_err()); } #[test] diff --git a/sdk/tests/types/address/ed25519.rs b/sdk/tests/types/address/ed25519.rs index 6ed989f224..f18d7e1022 100644 --- a/sdk/tests/types/address/ed25519.rs +++ b/sdk/tests/types/address/ed25519.rs @@ -130,10 +130,12 @@ fn serde_invalid_ed25519_address() { "pubKeyHash": ED25519_ADDRESS_INVALID, }); - assert!(matches!( - serde_json::from_value::(ed25519_address_ser), - Err(e) if e.to_string() == "hex error: Invalid hex string length for slice: expected 64 got 63" - )); + assert_eq!( + serde_json::from_value::(ed25519_address_ser) + .unwrap_err() + .to_string(), + "Invalid hex string length for slice: expected 64 got 63" + ); } #[test] @@ -156,7 +158,7 @@ fn pack_unpack() { assert_eq!( address, - Ed25519Address::unpack_verified(packed_address.as_slice(), &()).unwrap() + Ed25519Address::unpack_bytes_verified(packed_address.as_slice(), &()).unwrap() ); let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS).unwrap()); @@ -164,6 +166,6 @@ fn pack_unpack() { assert_eq!( address, - Address::unpack_verified(packed_address.as_slice(), &()).unwrap() + Address::unpack_bytes_verified(packed_address.as_slice(), &()).unwrap() ); } diff --git a/sdk/tests/types/address/mod.rs b/sdk/tests/types/address/mod.rs index a98bc07f09..8e8ad184fd 100644 --- a/sdk/tests/types/address/mod.rs +++ b/sdk/tests/types/address/mod.rs @@ -11,12 +11,11 @@ mod restricted; use core::str::FromStr; use iota_sdk::types::block::{ - address::{AccountAddress, Address, Ed25519Address, NftAddress}, + address::{AccountAddress, Address, AddressError, Ed25519Address, NftAddress}, rand::address::{ rand_account_address, rand_anchor_address, rand_ed25519_address, rand_implicit_address, rand_multi_address, rand_nft_address, rand_restricted_address, }, - Error, }; use pretty_assertions::assert_eq; @@ -27,9 +26,9 @@ const ED25519_ADDRESS_INVALID: &str = "0x52fdfc072182654f163f5f0f9a621d729566c74 #[test] fn invalid_bech32() { - let address = Address::try_from_bech32(ED25519_ADDRESS_INVALID); + let address = Address::try_from_bech32(ED25519_ADDRESS_INVALID).unwrap_err(); - assert!(matches!(address, Err(Error::InvalidAddress))); + assert!(matches!(address, AddressError::Bech32Encoding(_))); } #[test] diff --git a/sdk/tests/types/address/multi.rs b/sdk/tests/types/address/multi.rs index c63a63633a..23978d04a7 100644 --- a/sdk/tests/types/address/multi.rs +++ b/sdk/tests/types/address/multi.rs @@ -21,7 +21,7 @@ fn ordered_by_packed_bytes() { let multi_1 = MultiAddress::new([weighted_1, weighted_2], 2).unwrap(); let bytes = multi_1.pack_to_vec(); - let multi_2 = MultiAddress::unpack_verified(bytes, &()).unwrap(); + let multi_2 = MultiAddress::unpack_bytes_verified(bytes, &()).unwrap(); assert!(multi_2.addresses()[0].address().is_ed25519()); assert!(multi_2.addresses()[1].address().is_account()); @@ -74,7 +74,7 @@ fn json_packable_bech32() { }); let multi_address = serde_json::from_value::
(multi_address_json).unwrap(); let multi_address_bytes = multi_address.pack_to_vec(); - let multi_address_unpacked = Address::unpack_verified(multi_address_bytes, &()).unwrap(); + let multi_address_unpacked = Address::unpack_bytes_verified(multi_address_bytes, &()).unwrap(); assert_eq!(multi_address, multi_address_unpacked); assert_eq!( diff --git a/sdk/tests/types/address/nft.rs b/sdk/tests/types/address/nft.rs index 45f5243a16..af7e40ae9b 100644 --- a/sdk/tests/types/address/nft.rs +++ b/sdk/tests/types/address/nft.rs @@ -135,16 +135,18 @@ fn serde_roundtrip() { } #[test] -fn serde_invalid_account_id() { +fn serde_invalid_nft_id() { let nft_address_ser = json!({ "type": NftAddress::KIND, "nftId": NFT_ID_INVALID, }); - assert!(matches!( - serde_json::from_value::(nft_address_ser), - Err(e) if e.to_string() == "hex error: Invalid hex string length for slice: expected 64 got 61" - )); + assert_eq!( + serde_json::from_value::(nft_address_ser) + .unwrap_err() + .to_string(), + "Invalid hex string length for slice: expected 64 got 61" + ); } #[test] @@ -167,7 +169,7 @@ fn pack_unpack() { assert_eq!( address, - PackableExt::unpack_verified(packed_address.as_slice(), &()).unwrap() + PackableExt::unpack_bytes_verified(packed_address.as_slice(), &()).unwrap() ); let address = Address::from(NftAddress::from_str(NFT_ID).unwrap()); @@ -175,6 +177,6 @@ fn pack_unpack() { assert_eq!( address, - PackableExt::unpack_verified(packed_address.as_slice(), &()).unwrap() + PackableExt::unpack_bytes_verified(packed_address.as_slice(), &()).unwrap() ); } diff --git a/sdk/tests/types/address/restricted.rs b/sdk/tests/types/address/restricted.rs index dc50f4c880..a28199a101 100644 --- a/sdk/tests/types/address/restricted.rs +++ b/sdk/tests/types/address/restricted.rs @@ -43,7 +43,7 @@ fn restricted_ed25519() { // Test from https://github.com/iotaledger/tips/blob/tip50/tips/TIP-0050/tip-0050.md#bech32-strings // Ed25519 Address (Plain) - let address = Address::unpack_verified( + let address = Address::unpack_bytes_verified( prefix_hex::decode::>("0x00efdc112efe262b304bcf379b26c31bad029f616ee3ec4aa6345a366e4c9e43a3").unwrap(), &(), ) @@ -103,7 +103,7 @@ fn restricted_account() { // Test from https://github.com/iotaledger/tips/blob/tip50/tips/TIP-0050/tip-0050.md#bech32-strings // Account Address (Plain) - let address = Address::unpack_verified( + let address = Address::unpack_bytes_verified( prefix_hex::decode::>("0x0860441c013b400f402c317833366f48730610296a09243636343e7b1b7e115409").unwrap(), &(), ) @@ -163,7 +163,7 @@ fn restricted_nft() { // Test from https://github.com/iotaledger/tips/blob/tip50/tips/TIP-0050/tip-0050.md#bech32-strings // NFT Address (Plain) - let address = Address::unpack_verified( + let address = Address::unpack_bytes_verified( prefix_hex::decode::>("0x10140f39267a343f0d650a751250445e40600d133522085d210a2b5f3f69445139").unwrap(), &(), ) diff --git a/sdk/tests/types/api/core.rs b/sdk/tests/types/api/core.rs index a9489a8761..70e5ac525d 100644 --- a/sdk/tests/types/api/core.rs +++ b/sdk/tests/types/api/core.rs @@ -4,15 +4,11 @@ use iota_sdk::types::{ api::core::{ BlockMetadataResponse, BlockWithMetadataResponse, CommitteeResponse, CongestionResponse, InfoResponse, - IssuanceBlockHeaderResponse, ManaRewardsResponse, OutputResponse, RoutesResponse, SubmitBlockResponse, - TransactionMetadataResponse, UtxoChangesFullResponse, UtxoChangesResponse, ValidatorResponse, - ValidatorsResponse, - }, - block::{ - output::{OutputMetadata, OutputWithMetadata}, - slot::SlotCommitment, - BlockDto, + IssuanceBlockHeaderResponse, ManaRewardsResponse, OutputResponse, OutputWithMetadataResponse, RoutesResponse, + SubmitBlockResponse, TransactionMetadataResponse, UtxoChangesFullResponse, UtxoChangesResponse, + ValidatorResponse, ValidatorsResponse, }, + block::{output::OutputMetadata, slot::SlotCommitment, BlockDto}, }; use packable::{ error::{UnexpectedEOF, UnpackError}, @@ -44,9 +40,9 @@ fn binary_response( let file = std::fs::read_to_string(format!("./tests/types/api/fixtures/{path}")).unwrap(); let bytes = hex::decode(file).unwrap(); let mut unpacker = SliceUnpacker::new(bytes.as_slice()); - let res = T::unpack::<_, true>(&mut unpacker, visitor); + let res = T::unpack_verified(&mut unpacker, visitor); - assert!(u8::unpack::<_, true>(&mut unpacker, &()).is_err()); + assert!(u8::unpack_verified(&mut unpacker, &()).is_err()); res } @@ -56,6 +52,7 @@ fn responses() { // GET /api/routes json_response::("get-routes-response-example.json").unwrap(); // GET /api/core/v3/info + // TODO reenable when Metrics are split out of Info // json_response::("get-info-response-example.json").unwrap(); // GET /api/core/v3/accounts/{bech32Address}/congestion json_response::("get-congestion-estimate-response-example.json").unwrap(); @@ -90,7 +87,7 @@ fn responses() { json_response::("get-output-metadata-by-id-response-unspent-example.json").unwrap(); json_response::("get-output-metadata-by-id-response-spent-example.json").unwrap(); // GET /api/core/v3/outputs/{outputId}/full - json_response::("get-full-output-metadata-example.json").unwrap(); + json_response::("get-full-output-metadata-example.json").unwrap(); // GET /api/core/v3/transactions/{transactionId}/metadata json_response::("get-transaction-metadata-by-id-response-example.json").unwrap(); // GET /api/core/v3/commitments/{commitmentId} diff --git a/sdk/tests/types/api/fixtures/get-info-response-example.json b/sdk/tests/types/api/fixtures/get-info-response-example.json index 2c84f7cc7e..683af6d394 100644 --- a/sdk/tests/types/api/fixtures/get-info-response-example.json +++ b/sdk/tests/types/api/fixtures/get-info-response-example.json @@ -24,27 +24,27 @@ "parameters": { "type": 0, "version": 3, - "networkName": "TestJungle", - "bech32Hrp": "tgl", + "networkName": "testnet", + "bech32Hrp": "rms", "storageScoreParameters": { - "storageCost": "0", - "factorData": 0, - "offsetOutputOverhead": "0", - "offsetEd25519BlockIssuerKey": "0", - "offsetStakingFeature": "0", - "offsetDelegation": "0" + "storageCost": "100", + "factorData": 1, + "offsetOutputOverhead": "10", + "offsetEd25519BlockIssuerKey": "100", + "offsetStakingFeature": "100", + "offsetDelegation": "100" }, "workScoreParameters": { - "dataByte": 0, - "block": 1, - "input": 0, - "contextInput": 0, - "output": 0, - "nativeToken": 0, - "staking": 0, - "blockIssuer": 0, - "allotment": 0, - "signatureEd25519": 0 + "dataByte": 1, + "block": 2, + "input": 3, + "contextInput": 4, + "output": 5, + "nativeToken": 6, + "staking": 7, + "blockIssuer": 8, + "allotment": 9, + "signatureEd25519": 10 }, "manaParameters": { "bitsCount": 63, @@ -55,11 +55,11 @@ 20 ], "decayFactorsExponent": 32, - "decayFactorEpochsSum": 2420916375, + "decayFactorEpochsSum": 2262417561, "decayFactorEpochsSumExponent": 21, - "annualDecayFactorPercentage": 50 + "annualDecayFactorPercentage": 70 }, - "tokenSupply": "2779530283277761", + "tokenSupply": "1813620509061365", "genesisSlot": 0, "genesisUnixTimestamp": "1695275822", "slotDurationInSeconds": 10, @@ -71,7 +71,7 @@ "livenessThresholdUpperBound": 30, "minCommittableAge": 10, "maxCommittableAge": 20, - "epochNearingThreshold": 24, + "epochNearingThreshold": 60, "congestionControlParameters": { "minReferenceManaCost": "1", "increase": "0", @@ -89,15 +89,15 @@ }, "rewardsParameters": { "profitMarginExponent": 8, - "bootstrappingDuration": 1154, - "manaShareCoefficient": "2", - "decayBalancingConstantExponent": 8, - "decayBalancingConstant": "1", + "bootstrappingDuration": 1079, + "rewardToGenerationRatio": 5, + "initialTargetRewardsRate": "10", + "finalTargetRewardsRate": "20", "poolCoefficientExponent": 11, - "retentionPeriod": 684 + "retentionPeriod": 384 }, "targetCommitteeSize": 32, - "chainSwitchingThreshold": 2 + "chainSwitchingThreshold": 3 } } ], diff --git a/sdk/tests/types/api/fixtures/get-validators-example.json b/sdk/tests/types/api/fixtures/get-validators-example.json index 542562af81..c1d8dca89e 100644 --- a/sdk/tests/types/api/fixtures/get-validators-example.json +++ b/sdk/tests/types/api/fixtures/get-validators-example.json @@ -1,5 +1,5 @@ { - "stakers": [ + "validators": [ { "address": "rms1pp4wuuz0y42caz48vv876qfpmffswsvg40zz8v79sy8cp0jfxm4kunflcgt", "stakingEndEpoch": 100, diff --git a/sdk/tests/types/block.rs b/sdk/tests/types/block.rs index 47d536265f..819b7b3398 100644 --- a/sdk/tests/types/block.rs +++ b/sdk/tests/types/block.rs @@ -5,13 +5,13 @@ use iota_sdk::types::{ block::{ helper::network_name_to_id, payload::Payload, - protocol::{protocol_parameters, ProtocolParameters}, + protocol::iota_mainnet_protocol_parameters, rand::{ block::{rand_basic_block_body_builder_with_strong_parents, rand_block, rand_block_with_block_body}, parents::rand_strong_parents, payload::rand_tagged_data_payload, }, - Block, BlockDto, + Block, BlockDto, BlockError, }, TryFromDto, }; @@ -92,20 +92,20 @@ use pretty_assertions::assert_eq; // Validate that a `unpack` ∘ `pack` round-trip results in the original block. #[test] fn pack_unpack_valid() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let block = rand_block(protocol_parameters.clone()); let packed_block = block.pack_to_vec(); assert_eq!(packed_block.len(), block.packed_len()); assert_eq!( block, - PackableExt::unpack_verified(packed_block.as_slice(), &protocol_parameters).unwrap() + PackableExt::unpack_bytes_verified(packed_block.as_slice(), protocol_parameters).unwrap() ); } #[test] fn getters() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let parents = rand_strong_parents(); let payload = Payload::from(rand_tagged_data_payload()); @@ -122,7 +122,7 @@ fn getters() { #[test] fn dto_mismatch_version() { - let protocol_parameters = ProtocolParameters::default(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let protocol_parameters_hash = protocol_parameters.hash(); let slot_index = 11_u64; let issuing_time = protocol_parameters.genesis_unix_timestamp() @@ -153,11 +153,11 @@ fn dto_mismatch_version() { } }); let block_dto = serde_json::from_value::(block_dto_json).unwrap(); - let block_res = Block::try_from_dto_with_params(block_dto, &protocol_parameters); + let block_res = Block::try_from_dto_with_params(block_dto, protocol_parameters); assert_eq!( block_res, - Err(iota_sdk::types::block::Error::ProtocolVersionMismatch { + Err(BlockError::ProtocolVersionMismatch { expected: protocol_parameters.version(), actual: protocol_version }) @@ -166,7 +166,7 @@ fn dto_mismatch_version() { #[test] fn dto_mismatch_network_id() { - let protocol_parameters = ProtocolParameters::default(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let protocol_parameters_hash = protocol_parameters.hash(); let slot_index = 11_u64; let issuing_time = protocol_parameters.genesis_unix_timestamp() @@ -196,11 +196,11 @@ fn dto_mismatch_network_id() { } }); let block_dto = serde_json::from_value::(block_dto_json).unwrap(); - let block_res = Block::try_from_dto_with_params(block_dto, &protocol_parameters); + let block_res = Block::try_from_dto_with_params(block_dto, protocol_parameters); assert_eq!( block_res, - Err(iota_sdk::types::block::Error::NetworkIdMismatch { + Err(BlockError::NetworkIdMismatch { expected: protocol_parameters.network_id(), actual: network_id }) diff --git a/sdk/tests/types/block_id.rs b/sdk/tests/types/block_id.rs index 60e9dc6e3f..13d53c95c4 100644 --- a/sdk/tests/types/block_id.rs +++ b/sdk/tests/types/block_id.rs @@ -53,7 +53,7 @@ fn pack_unpack_valid() { assert_eq!(packed_block_id.len(), block_id.packed_len()); assert_eq!( block_id, - PackableExt::unpack_verified(packed_block_id.as_slice(), &()).unwrap() + PackableExt::unpack_bytes_verified(packed_block_id.as_slice(), &()).unwrap() ); } @@ -77,53 +77,53 @@ fn protocol_parameters() -> ProtocolParameters { serde_json::from_value::(params_json.clone()).unwrap() } -// #[test] -// fn basic_block_tagged_data_payload_id() { -// // Test vector from https://github.com/iotaledger/tips/blob/tip46/tips/TIP-0046/tip-0046.md#basic-block-id-tagged-data-payload -// let protocol_parameters = protocol_parameters(); -// let file = std::fs::read_to_string("./tests/types/fixtures/basic_block_tagged_data_payload.json").unwrap(); -// let json = serde_json::from_str::(&file).unwrap(); -// let block_json = &json["block"]; -// let block_dto = serde_json::from_value::(block_json.clone()).unwrap(); -// let block = Block::try_from_dto(block_dto).unwrap(); -// let block_bytes = block.pack_to_vec(); -// let block_work_score = block.as_basic().work_score(protocol_parameters.work_score_parameters()); - -// assert_eq!(prefix_hex::encode(&block_bytes), json["bytes"]); -// assert_eq!(block, Block::unpack_unverified(block_bytes).unwrap()); -// assert_eq!(block.id(&protocol_parameters).to_string(), json["id"]); -// assert_eq!(block_work_score, json["workScore"]); -// } - -// #[test] -// fn basic_block_transaction_payload_id() { -// // Test vector from https://github.com/iotaledger/tips/blob/tip46/tips/TIP-0046/tip-0046.md#basic-block-id-transaction-payload -// let protocol_parameters = protocol_parameters(); -// let file = std::fs::read_to_string("./tests/types/fixtures/basic_block_transaction_payload.json").unwrap(); -// let json = serde_json::from_str::(&file).unwrap(); -// let block_json = &json["block"]; -// let block_dto = serde_json::from_value::(block_json.clone()).unwrap(); -// let block = Block::try_from_dto(block_dto).unwrap(); -// let block_bytes = block.pack_to_vec(); -// let block_work_score = block.as_basic().work_score(protocol_parameters.work_score_parameters()); - -// assert_eq!(prefix_hex::encode(&block_bytes), json["bytes"]); -// assert_eq!(block, Block::unpack_unverified(block_bytes).unwrap()); -// assert_eq!(block.id(&protocol_parameters).to_string(), json["id"]); -// assert_eq!(block_work_score, json["workScore"]); -// } - -// #[test] -// fn validation_block_id() { -// // Test vector from https://github.com/iotaledger/tips/blob/tip46/tips/TIP-0046/tip-0046.md#validation-block-id -// let file = std::fs::read_to_string("./tests/types/fixtures/validation_block.json").unwrap(); -// let json = serde_json::from_str::(&file).unwrap(); -// let block_json = &json["block"]; -// let block_dto = serde_json::from_value::(block_json.clone()).unwrap(); -// let block = Block::try_from_dto(block_dto).unwrap(); -// let block_bytes = block.pack_to_vec(); - -// assert_eq!(prefix_hex::encode(&block_bytes), json["bytes"]); -// assert_eq!(block, Block::unpack_unverified(block_bytes).unwrap()); -// assert_eq!(block.id(&protocol_parameters()).to_string(), json["id"]); -// } +#[test] +fn basic_block_tagged_data_payload_id() { + // Test vector from https://github.com/iotaledger/tips/blob/tip46/tips/TIP-0046/tip-0046.md#basic-block-id-tagged-data-payload + let protocol_parameters = protocol_parameters(); + let file = std::fs::read_to_string("./tests/types/fixtures/basic_block_tagged_data_payload.json").unwrap(); + let json = serde_json::from_str::(&file).unwrap(); + let block_json = &json["block"]; + let block_dto = serde_json::from_value::(block_json.clone()).unwrap(); + let block = Block::try_from_dto(block_dto).unwrap(); + let block_bytes = block.pack_to_vec(); + let block_work_score = block.as_basic().work_score(protocol_parameters.work_score_parameters()); + + assert_eq!(prefix_hex::encode(&block_bytes), json["bytes"]); + assert_eq!(block, Block::unpack_bytes_unverified(block_bytes).unwrap()); + assert_eq!(block.id(&protocol_parameters).to_string(), json["id"]); + assert_eq!(block_work_score, json["workScore"]); +} + +#[test] +fn basic_block_transaction_payload_id() { + // Test vector from https://github.com/iotaledger/tips/blob/tip46/tips/TIP-0046/tip-0046.md#basic-block-id-transaction-payload + let protocol_parameters = protocol_parameters(); + let file = std::fs::read_to_string("./tests/types/fixtures/basic_block_transaction_payload.json").unwrap(); + let json = serde_json::from_str::(&file).unwrap(); + let block_json = &json["block"]; + let block_dto = serde_json::from_value::(block_json.clone()).unwrap(); + let block = Block::try_from_dto(block_dto).unwrap(); + let block_bytes = block.pack_to_vec(); + let block_work_score = block.as_basic().work_score(protocol_parameters.work_score_parameters()); + + assert_eq!(prefix_hex::encode(&block_bytes), json["bytes"]); + assert_eq!(block, Block::unpack_bytes_unverified(block_bytes).unwrap()); + assert_eq!(block.id(&protocol_parameters).to_string(), json["id"]); + assert_eq!(block_work_score, json["workScore"]); +} + +#[test] +fn validation_block_id() { + // Test vector from https://github.com/iotaledger/tips/blob/tip46/tips/TIP-0046/tip-0046.md#validation-block-id + let file = std::fs::read_to_string("./tests/types/fixtures/validation_block.json").unwrap(); + let json = serde_json::from_str::(&file).unwrap(); + let block_json = &json["block"]; + let block_dto = serde_json::from_value::(block_json.clone()).unwrap(); + let block = Block::try_from_dto(block_dto).unwrap(); + let block_bytes = block.pack_to_vec(); + + assert_eq!(prefix_hex::encode(&block_bytes), json["bytes"]); + assert_eq!(block, Block::unpack_bytes_unverified(block_bytes).unwrap()); + assert_eq!(block.id(&protocol_parameters()).to_string(), json["id"]); +} diff --git a/sdk/tests/types/ed25519_signature.rs b/sdk/tests/types/ed25519_signature.rs index 4adc27d077..7d4212f79b 100644 --- a/sdk/tests/types/ed25519_signature.rs +++ b/sdk/tests/types/ed25519_signature.rs @@ -30,5 +30,8 @@ fn pack_unpack_valid() { let sig = Ed25519Signature::from_bytes(pub_key_bytes, sig_bytes); let sig_packed = sig.pack_to_vec(); - assert_eq!(sig, PackableExt::unpack_verified(sig_packed.as_slice(), &()).unwrap()); + assert_eq!( + sig, + PackableExt::unpack_bytes_verified(sig_packed.as_slice(), &()).unwrap() + ); } diff --git a/sdk/tests/types/fixtures/protocol_parameters.json b/sdk/tests/types/fixtures/protocol_parameters.json index bbbf63a0c7..35e57b20bd 100644 --- a/sdk/tests/types/fixtures/protocol_parameters.json +++ b/sdk/tests/types/fixtures/protocol_parameters.json @@ -1,144 +1,464 @@ { - "params": { - "type": 0, - "version": 3, - "networkName": "testnet", - "bech32Hrp": "rms", - "storageScoreParameters": { - "storageCost": "100", - "factorData": 1, - "offsetOutputOverhead": "10", - "offsetEd25519BlockIssuerKey": "100", - "offsetStakingFeature": "100", - "offsetDelegation": "100" - }, - "workScoreParameters": { - "dataByte": 1, - "block": 2, - "input": 3, - "contextInput": 4, - "output": 5, - "nativeToken": 6, - "staking": 7, - "blockIssuer": 8, - "allotment": 9, - "signatureEd25519": 10 - }, - "manaParameters": { - "bitsCount": 63, - "generationRate": 1, - "generationRateExponent": 17, - "decayFactors": [ - 4290989755, 4287015898, 4283045721, 4279079221, 4275116394, 4271157237, - 4267201747, 4263249920, 4259301752, 4255357241, 4251416383, 4247479175, - 4243545613, 4239615693, 4235689414, 4231766770, 4227847759, 4223932377, - 4220020622, 4216112489, 4212207975, 4208307077, 4204409792, 4200516116, - 4196626046, 4192739579, 4188856710, 4184977438, 4181101758, 4177229668, - 4173361163, 4169496241, 4165634898, 4161777132, 4157922938, 4154072313, - 4150225254, 4146381758, 4142541822, 4138705441, 4134872614, 4131043336, - 4127217604, 4123395415, 4119576766, 4115761654, 4111950074, 4108142024, - 4104337501, 4100536502, 4096739022, 4092945060, 4089154610, 4085367672, - 4081584240, 4077804312, 4074027884, 4070254954, 4066485518, 4062719573, - 4058957115, 4055198142, 4051442650, 4047690636, 4043942097, 4040197029, - 4036455429, 4032717295, 4028982622, 4025251408, 4021523650, 4017799344, - 4014078486, 4010361075, 4006647106, 4002936577, 3999229484, 3995525824, - 3991825594, 3988128791, 3984435412, 3980745453, 3977058911, 3973375783, - 3969696066, 3966019757, 3962346853, 3958677350, 3955011245, 3951348535, - 3947689218, 3944033289, 3940380746, 3936731586, 3933085805, 3929443400, - 3925804369, 3922168708, 3918536413, 3914907483, 3911281913, 3907659701, - 3904040843, 3900425337, 3896813179, 3893204366, 3889598896, 3885996764, - 3882397968, 3878802505, 3875210372, 3871621566, 3868036083, 3864453920, - 3860875075, 3857299544, 3853727325, 3850158414, 3846592808, 3843030504, - 3839471499, 3835915790, 3832363374, 3828814248, 3825268408, 3821725853, - 3818186578, 3814650580, 3811117858, 3807588407, 3804062225, 3800539308, - 3797019654, 3793503259, 3789990121, 3786480237, 3782973602, 3779470216, - 3775970074, 3772473173, 3768979511, 3765489084, 3762001889, 3758517924, - 3755037186, 3751559671, 3748085377, 3744614300, 3741146437, 3737681787, - 3734220344, 3730762108, 3727307074, 3723855240, 3720406602, 3716961158, - 3713518905, 3710079840, 3706643960, 3703211262, 3699781742, 3696355399, - 3692932229, 3689512229, 3686095396, 3682681728, 3679271221, 3675863872, - 3672459679, 3669058639, 3665660748, 3662266004, 3658874404, 3655485944, - 3652100623, 3648718437, 3645339383, 3641963459, 3638590661, 3635220986, - 3631854432, 3628490996, 3625130675, 3621773465, 3618419365, 3615068371, - 3611720480, 3608375690, 3605033997, 3601695399, 3598359893, 3595027476, - 3591698145, 3588371897, 3585048730, 3581728640, 3578411625, 3575097682, - 3571786808, 3568479000, 3565174255, 3561872571, 3558573944, 3555278373, - 3551985853, 3548696383, 3545409959, 3542126578, 3538846238, 3535568936, - 3532294669, 3529023435, 3525755230, 3522490051, 3519227897, 3515968763, - 3512712648, 3509459548, 3506209461, 3502962384, 3499718314, 3496477248, - 3493239183, 3490004118, 3486772048, 3483542972, 3480316886, 3477093788, - 3473873674, 3470656543, 3467442391, 3464231216, 3461023014, 3457817784, - 3454615522, 3451416225, 3448219892, 3445026518, 3441836102, 3438648641, - 3435464131, 3432282571, 3429103957, 3425928286, 3422755557, 3419585766, - 3416418910, 3413254987, 3410093995, 3406935929, 3403780789, 3400628570, - 3397479270, 3394332887, 3391189418, 3388048860, 3384911211, 3381776467, - 3378644627, 3375515686, 3372389644, 3369266496, 3366146241, 3363028875, - 3359914396, 3356802802, 3353694089, 3350588256, 3347485298, 3344385214, - 3341288001, 3338193657, 3335102178, 3332013562, 3328927806, 3325844909, - 3322764866, 3319687675, 3316613335, 3313541841, 3310473192, 3307407385, - 3304344417, 3301284286, 3298226988, 3295172522, 3292120885, 3289072074, - 3286026086, 3282982919, 3279942570, 3276905037, 3273870317, 3270838408, - 3267809306, 3264783010, 3261759516, 3258738822, 3255720926, 3252705824, - 3249693515, 3246683996, 3243677263, 3240673315, 3237672149, 3234673763, - 3231678153, 3228685317, 3225695253, 3222707958, 3219723430, 3216741666, - 3213762662, 3210786418, 3207812930, 3204842196, 3201874213, 3198908979, - 3195946490, 3192986746, 3190029742, 3187075477, 3184123947, 3181175151, - 3178229086, 3175285749, 3172345138, 3169407251, 3166472084, 3163539635, - 3160609902, 3157682882, 3154758573, 3151836972, 3148918077, 3146001885, - 3143088393, 3140177600, 3137269503, 3134364098, 3131461384, 3128561359, - 3125664019, 3122769362, 3119877387, 3116988089, 3114101467, 3111217518, - 3108336240, 3105457631, 3102581687, 3099708407, 3096837788, 3093969827, - 3091104522, 3088241871, 3085381870, 3082524519, 3079669813, 3076817752, - 3073968331, 3071121550, 3068277404, 3065435893, 3062597013, 3059760763, - 3056927139, 3054096139, 3051267761, 3048442002, 3045618860, 3042798333, - 3039980417, 3037165112, 3034352413, 3031542320, 3028734829, 3025929938, - 3023127644, 3020327946, 3017530840, 3014736325, 3011944398, 3009155056 - ], - "decayFactorsExponent": 32, - "decayFactorEpochsSum": 2262417561, - "decayFactorEpochsSumExponent": 21, - "annualDecayFactorPercentage": 70 - }, - "tokenSupply": "1813620509061365", - "genesisSlot": 0, - "genesisUnixTimestamp": "1695275822", - "slotDurationInSeconds": 10, - "slotsPerEpochExponent": 13, - "stakingUnbondingPeriod": 10, - "validationBlocksPerSlot": 10, - "punishmentEpochs": 10, - "livenessThresholdLowerBound": 15, - "livenessThresholdUpperBound": 30, - "minCommittableAge": 10, - "maxCommittableAge": 20, - "epochNearingThreshold": 60, - "congestionControlParameters": { - "minReferenceManaCost": "1", - "increase": "0", - "decrease": "0", - "increaseThreshold": 800000, - "decreaseThreshold": 500000, - "schedulerRate": 100000, - "maxBufferSize": 1000, - "maxValidationBufferSize": 100 - }, - "versionSignalingParameters": { - "windowSize": 7, - "windowTargetRatio": 5, - "activationOffset": 7 - }, - "rewardsParameters": { - "profitMarginExponent": 8, - "bootstrappingDuration": 1079, - "manaShareCoefficient": "2", - "decayBalancingConstantExponent": 8, - "decayBalancingConstant": "1", - "poolCoefficientExponent": 11, - "retentionPeriod": 384 - }, - "targetCommitteeSize": 32, - "chainSwitchingThreshold": 3 + "params":{ + "type":0, + "version":3, + "networkName":"testnet", + "bech32Hrp":"rms", + "storageScoreParameters":{ + "storageCost":"100", + "factorData":1, + "offsetOutputOverhead":"10", + "offsetEd25519BlockIssuerKey":"100", + "offsetStakingFeature":"100", + "offsetDelegation":"100" + }, + "workScoreParameters":{ + "dataByte":1, + "block":2, + "input":3, + "contextInput":4, + "output":5, + "nativeToken":6, + "staking":7, + "blockIssuer":8, + "allotment":9, + "signatureEd25519":10 + }, + "manaParameters":{ + "bitsCount":63, + "generationRate":1, + "generationRateExponent":17, + "decayFactors":[ + 4290989755, + 4287015898, + 4283045721, + 4279079221, + 4275116394, + 4271157237, + 4267201747, + 4263249920, + 4259301752, + 4255357241, + 4251416383, + 4247479175, + 4243545613, + 4239615693, + 4235689414, + 4231766770, + 4227847759, + 4223932377, + 4220020622, + 4216112489, + 4212207975, + 4208307077, + 4204409792, + 4200516116, + 4196626046, + 4192739579, + 4188856710, + 4184977438, + 4181101758, + 4177229668, + 4173361163, + 4169496241, + 4165634898, + 4161777132, + 4157922938, + 4154072313, + 4150225254, + 4146381758, + 4142541822, + 4138705441, + 4134872614, + 4131043336, + 4127217604, + 4123395415, + 4119576766, + 4115761654, + 4111950074, + 4108142024, + 4104337501, + 4100536502, + 4096739022, + 4092945060, + 4089154610, + 4085367672, + 4081584240, + 4077804312, + 4074027884, + 4070254954, + 4066485518, + 4062719573, + 4058957115, + 4055198142, + 4051442650, + 4047690636, + 4043942097, + 4040197029, + 4036455429, + 4032717295, + 4028982622, + 4025251408, + 4021523650, + 4017799344, + 4014078486, + 4010361075, + 4006647106, + 4002936577, + 3999229484, + 3995525824, + 3991825594, + 3988128791, + 3984435412, + 3980745453, + 3977058911, + 3973375783, + 3969696066, + 3966019757, + 3962346853, + 3958677350, + 3955011245, + 3951348535, + 3947689218, + 3944033289, + 3940380746, + 3936731586, + 3933085805, + 3929443400, + 3925804369, + 3922168708, + 3918536413, + 3914907483, + 3911281913, + 3907659701, + 3904040843, + 3900425337, + 3896813179, + 3893204366, + 3889598896, + 3885996764, + 3882397968, + 3878802505, + 3875210372, + 3871621566, + 3868036083, + 3864453920, + 3860875075, + 3857299544, + 3853727325, + 3850158414, + 3846592808, + 3843030504, + 3839471499, + 3835915790, + 3832363374, + 3828814248, + 3825268408, + 3821725853, + 3818186578, + 3814650580, + 3811117858, + 3807588407, + 3804062225, + 3800539308, + 3797019654, + 3793503259, + 3789990121, + 3786480237, + 3782973602, + 3779470216, + 3775970074, + 3772473173, + 3768979511, + 3765489084, + 3762001889, + 3758517924, + 3755037186, + 3751559671, + 3748085377, + 3744614300, + 3741146437, + 3737681787, + 3734220344, + 3730762108, + 3727307074, + 3723855240, + 3720406602, + 3716961158, + 3713518905, + 3710079840, + 3706643960, + 3703211262, + 3699781742, + 3696355399, + 3692932229, + 3689512229, + 3686095396, + 3682681728, + 3679271221, + 3675863872, + 3672459679, + 3669058639, + 3665660748, + 3662266004, + 3658874404, + 3655485944, + 3652100623, + 3648718437, + 3645339383, + 3641963459, + 3638590661, + 3635220986, + 3631854432, + 3628490996, + 3625130675, + 3621773465, + 3618419365, + 3615068371, + 3611720480, + 3608375690, + 3605033997, + 3601695399, + 3598359893, + 3595027476, + 3591698145, + 3588371897, + 3585048730, + 3581728640, + 3578411625, + 3575097682, + 3571786808, + 3568479000, + 3565174255, + 3561872571, + 3558573944, + 3555278373, + 3551985853, + 3548696383, + 3545409959, + 3542126578, + 3538846238, + 3535568936, + 3532294669, + 3529023435, + 3525755230, + 3522490051, + 3519227897, + 3515968763, + 3512712648, + 3509459548, + 3506209461, + 3502962384, + 3499718314, + 3496477248, + 3493239183, + 3490004118, + 3486772048, + 3483542972, + 3480316886, + 3477093788, + 3473873674, + 3470656543, + 3467442391, + 3464231216, + 3461023014, + 3457817784, + 3454615522, + 3451416225, + 3448219892, + 3445026518, + 3441836102, + 3438648641, + 3435464131, + 3432282571, + 3429103957, + 3425928286, + 3422755557, + 3419585766, + 3416418910, + 3413254987, + 3410093995, + 3406935929, + 3403780789, + 3400628570, + 3397479270, + 3394332887, + 3391189418, + 3388048860, + 3384911211, + 3381776467, + 3378644627, + 3375515686, + 3372389644, + 3369266496, + 3366146241, + 3363028875, + 3359914396, + 3356802802, + 3353694089, + 3350588256, + 3347485298, + 3344385214, + 3341288001, + 3338193657, + 3335102178, + 3332013562, + 3328927806, + 3325844909, + 3322764866, + 3319687675, + 3316613335, + 3313541841, + 3310473192, + 3307407385, + 3304344417, + 3301284286, + 3298226988, + 3295172522, + 3292120885, + 3289072074, + 3286026086, + 3282982919, + 3279942570, + 3276905037, + 3273870317, + 3270838408, + 3267809306, + 3264783010, + 3261759516, + 3258738822, + 3255720926, + 3252705824, + 3249693515, + 3246683996, + 3243677263, + 3240673315, + 3237672149, + 3234673763, + 3231678153, + 3228685317, + 3225695253, + 3222707958, + 3219723430, + 3216741666, + 3213762662, + 3210786418, + 3207812930, + 3204842196, + 3201874213, + 3198908979, + 3195946490, + 3192986746, + 3190029742, + 3187075477, + 3184123947, + 3181175151, + 3178229086, + 3175285749, + 3172345138, + 3169407251, + 3166472084, + 3163539635, + 3160609902, + 3157682882, + 3154758573, + 3151836972, + 3148918077, + 3146001885, + 3143088393, + 3140177600, + 3137269503, + 3134364098, + 3131461384, + 3128561359, + 3125664019, + 3122769362, + 3119877387, + 3116988089, + 3114101467, + 3111217518, + 3108336240, + 3105457631, + 3102581687, + 3099708407, + 3096837788, + 3093969827, + 3091104522, + 3088241871, + 3085381870, + 3082524519, + 3079669813, + 3076817752, + 3073968331, + 3071121550, + 3068277404, + 3065435893, + 3062597013, + 3059760763, + 3056927139, + 3054096139, + 3051267761, + 3048442002, + 3045618860, + 3042798333, + 3039980417, + 3037165112, + 3034352413, + 3031542320, + 3028734829, + 3025929938, + 3023127644, + 3020327946, + 3017530840, + 3014736325, + 3011944398, + 3009155056 + ], + "decayFactorsExponent":32, + "decayFactorEpochsSum":2262417561, + "decayFactorEpochsSumExponent":21, + "annualDecayFactorPercentage":70 + }, + "tokenSupply":"1813620509061365", + "genesisSlot":0, + "genesisUnixTimestamp":"1695275822", + "slotDurationInSeconds":10, + "slotsPerEpochExponent":13, + "stakingUnbondingPeriod":10, + "validationBlocksPerSlot":10, + "punishmentEpochs":10, + "livenessThresholdLowerBound":15, + "livenessThresholdUpperBound":30, + "minCommittableAge":10, + "maxCommittableAge":20, + "epochNearingThreshold":60, + "congestionControlParameters":{ + "minReferenceManaCost":"1", + "increase":"0", + "decrease":"0", + "increaseThreshold":800000, + "decreaseThreshold":500000, + "schedulerRate":100000, + "maxBufferSize":1000, + "maxValidationBufferSize":100 + }, + "versionSignalingParameters":{ + "windowSize":7, + "windowTargetRatio":5, + "activationOffset":7 + }, + "rewardsParameters":{ + "profitMarginExponent":8, + "bootstrappingDuration":1079, + "rewardToGenerationRatio":2, + "initialTargetRewardsRate":"8", + "finalTargetRewardsRate":"1", + "poolCoefficientExponent":11, + "retentionPeriod":384 + }, + "targetCommitteeSize":32, + "chainSwitchingThreshold":3 }, - "bytes": "0x000307746573746e657403726d736400000000000000010a000000000000006400000000000000640000000000000064000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000003f01118001bb4ec3ffdaab86ff59174aff35910dff6a19d1fef5af94fed35458fe00081cfe78c9dffd3999a3fd3f7767fd87632bfd0d5eeffccd66b3fcc67d77fcf2a23bfc4fd6fffbd917c4fb8e6788fb69c54cfb673111fb85abd5fac0339afa14ca5efa7e6e23fafb20e8f986e1acf91eb071f9be8c36f96477fbf80b70c0f8b17685f8528b4af8ecad0ff87aded4f7f91c9af766695ff7bec324f7fe2beaf621a2aff6262675f608b83af6c45700f65705c6f5bec08bf5f68951f5fa6017f5c845ddf45d38a3f4b63869f4ce462ff4a462f5f3328cbbf378c381f3700848f3185b0ef36cbbd4f26a299bf20ea561f2552e28f23bc5eef1be69b5f1da1b7cf18cdb42f1d1a809f1a583d0f0056c97f0ef615ef05e6525f05076ecefc294b3efb0c07aef16fa41eff34009ef4295d0ee01f797ee2c665feec0e226eeba6ceeed1704b6edd4a87deded5a45ed5f1a0ded27e7d4ec42c19cecada864ec659d2cec669ff4ebadaebceb37cb84eb02f54ceb092c15eb4a70ddeac2c1a5ea6d206eea488c36ea5105ffe9848bc7e9dd1e90e95bbf58e9f96c21e9b527eae88befb2e879c47be87ba644e88e950de8b091d6e7dc9a9fe710b168e749d431e78404fbe6be41c4e6f38b8de620e356e6434720e658b8e9e55d36b3e54ec17ce5285946e5e8fd0fe58bafd9e40e6ea3e46e396de4a81137e4b8f600e49de8cae352e794e3d4f25ee3220b29e33730f3e21162bde2aca087e206ec51e21b441ce2e9a8e6e16d1ab1e1a2987be1882346e11abb10e1555fdbe03710a6e0bccd70e0e1973be0a46e06e00252d1dff7419cdf813e67df9c4732df455dfdde7b7fc8de38ae93de7ce95ede42312ade8885f5dd4ae6c0dd86538cdd39cd57dd605323ddf8e5eedcfe84badc6e3086dc47e851dc85ac1ddc257de9db245ab5db804381db35394ddb403b19db9f49e5da4f64b1da4c8b7dda94be49da24fe15daf849e2d90fa2aed965067bd9f77647d9c3f313d9c57ce0d8fa11add860b379d8f46046d8b31a13d899e0dfd7a5b2acd7d39079d7207b46d78a7113d70d74e0d6a782add6559d7ad614c447d6e1f614d6b935e2d59a80afd580d77cd5693a4ad552a917d53824e5d418abb2d4ef3d80d4bbdc4dd478871bd4253ee9d3bd00b7d33fcf84d3a7a952d3f28f20d31e82eed22880bcd20d8a8ad2cb9f58d25ec126d2c3eef4d1f927c3d1fb6c91d1c8bd5fd15c1a2ed1b582fcd0d0f6cad0aa7699d0400268d08f9936d0963c05d050ebd3cfbca5a2cfd66b71cf9c3d40cf0a1b0fcf1f04deced7f8acce30f97bce26054bceb81c1acee23fe9cda16eb8cdf4a887cdd6ee56cd464026cd419df5ccc305c5cccb7994cc55f963cc5e8433cce51a03cce6bcd2cb5e6aa2cb4b2372cbabe741cb79b711cbb592e1ca5a79b1ca666b81cad76851caaa7121cadc85f1c96ba5c1c953d091c9930662c9264832c90c9502c940edd2c8c150a3c88bbf73c89c3944c8f2be14c8894fe5c760ebb5c7729286c7be4457c7410228c7f9caf8c6e29ec9c6fa7d9ac63e686bc6ad5d3cc6425e0dc6fb69dec5d780afc5d1a280c5e8cf51c5190823c5614bf4c4be99c5c42cf396c4aa5768c435c739c4ca410bc466c7dcc30758aec3aaf37fc34d9a51c3ed4b23c38808f5c21ad0c6c2a2a298c21c806ac286683cc2de5b0ec2205ae0c14b63b2c15c7784c14f9656c123c028c1d5f4fac06334cdc0c97e9fc005d471c0153444c0f69e16c0a614e9bf2295bbbf66208ebf72b660bf425733bfd40206bf25b9d8be337aabbefa457ebe7a1c51beaefd23be95e9f6bd2be0c9bd6fe19cbd5eed6fbdf50343bd322516bd1351e9bc9487bcbcb3c88fbc6e1463bcc26a36bcadcb09bc2c37ddbb3dadb0bbdd2d84bb09b957bbc04e2bbbffeefebac299d2ba084fa6bacf0e7aba13d94dbad2ad21ba0b8df5b9b976c9b9db6a9db96e6971b9707245b9df8519b9b7a3edb8f7cbc1b89cfe95b8a33b6ab80a833eb8cfd412b8ee30e7b76797bbb7350890b7588364b7cb0839b78e980db79c32e2b6f5d6b6b695858bb67b3e60b6a30135b60bcf09b6b1a6deb59288b3b5ac7488b5fd6a5db5816b32b5387607b51d8bdcb430aab1b46dd386b4d2065cb45c4431b40a8c06b4d8dddbb3c539b1b3ce9f86b3f00f5cb32099c0d9861546f530336e7a710600000000002edb0b65000000000a0d0a0000000a0a0000000f001e000a000000140000003c00000001000000000000000000000000000000000000000000000000350c0020a10700a0860100e803000064000000070507083704000002000000000000000801000000000000000b80012003", - "hash": "0x28ccbc633e0d22e19752f5e65c0d22055a7d59756bfa754b8839088e18a6a5a6" -} + "bytes":"0x000307746573746e657403726d736400000000000000010a000000000000006400000000000000640000000000000064000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000003f01118001bb4ec3ffdaab86ff59174aff35910dff6a19d1fef5af94fed35458fe00081cfe78c9dffd3999a3fd3f7767fd87632bfd0d5eeffccd66b3fcc67d77fcf2a23bfc4fd6fffbd917c4fb8e6788fb69c54cfb673111fb85abd5fac0339afa14ca5efa7e6e23fafb20e8f986e1acf91eb071f9be8c36f96477fbf80b70c0f8b17685f8528b4af8ecad0ff87aded4f7f91c9af766695ff7bec324f7fe2beaf621a2aff6262675f608b83af6c45700f65705c6f5bec08bf5f68951f5fa6017f5c845ddf45d38a3f4b63869f4ce462ff4a462f5f3328cbbf378c381f3700848f3185b0ef36cbbd4f26a299bf20ea561f2552e28f23bc5eef1be69b5f1da1b7cf18cdb42f1d1a809f1a583d0f0056c97f0ef615ef05e6525f05076ecefc294b3efb0c07aef16fa41eff34009ef4295d0ee01f797ee2c665feec0e226eeba6ceeed1704b6edd4a87deded5a45ed5f1a0ded27e7d4ec42c19cecada864ec659d2cec669ff4ebadaebceb37cb84eb02f54ceb092c15eb4a70ddeac2c1a5ea6d206eea488c36ea5105ffe9848bc7e9dd1e90e95bbf58e9f96c21e9b527eae88befb2e879c47be87ba644e88e950de8b091d6e7dc9a9fe710b168e749d431e78404fbe6be41c4e6f38b8de620e356e6434720e658b8e9e55d36b3e54ec17ce5285946e5e8fd0fe58bafd9e40e6ea3e46e396de4a81137e4b8f600e49de8cae352e794e3d4f25ee3220b29e33730f3e21162bde2aca087e206ec51e21b441ce2e9a8e6e16d1ab1e1a2987be1882346e11abb10e1555fdbe03710a6e0bccd70e0e1973be0a46e06e00252d1dff7419cdf813e67df9c4732df455dfdde7b7fc8de38ae93de7ce95ede42312ade8885f5dd4ae6c0dd86538cdd39cd57dd605323ddf8e5eedcfe84badc6e3086dc47e851dc85ac1ddc257de9db245ab5db804381db35394ddb403b19db9f49e5da4f64b1da4c8b7dda94be49da24fe15daf849e2d90fa2aed965067bd9f77647d9c3f313d9c57ce0d8fa11add860b379d8f46046d8b31a13d899e0dfd7a5b2acd7d39079d7207b46d78a7113d70d74e0d6a782add6559d7ad614c447d6e1f614d6b935e2d59a80afd580d77cd5693a4ad552a917d53824e5d418abb2d4ef3d80d4bbdc4dd478871bd4253ee9d3bd00b7d33fcf84d3a7a952d3f28f20d31e82eed22880bcd20d8a8ad2cb9f58d25ec126d2c3eef4d1f927c3d1fb6c91d1c8bd5fd15c1a2ed1b582fcd0d0f6cad0aa7699d0400268d08f9936d0963c05d050ebd3cfbca5a2cfd66b71cf9c3d40cf0a1b0fcf1f04deced7f8acce30f97bce26054bceb81c1acee23fe9cda16eb8cdf4a887cdd6ee56cd464026cd419df5ccc305c5cccb7994cc55f963cc5e8433cce51a03cce6bcd2cb5e6aa2cb4b2372cbabe741cb79b711cbb592e1ca5a79b1ca666b81cad76851caaa7121cadc85f1c96ba5c1c953d091c9930662c9264832c90c9502c940edd2c8c150a3c88bbf73c89c3944c8f2be14c8894fe5c760ebb5c7729286c7be4457c7410228c7f9caf8c6e29ec9c6fa7d9ac63e686bc6ad5d3cc6425e0dc6fb69dec5d780afc5d1a280c5e8cf51c5190823c5614bf4c4be99c5c42cf396c4aa5768c435c739c4ca410bc466c7dcc30758aec3aaf37fc34d9a51c3ed4b23c38808f5c21ad0c6c2a2a298c21c806ac286683cc2de5b0ec2205ae0c14b63b2c15c7784c14f9656c123c028c1d5f4fac06334cdc0c97e9fc005d471c0153444c0f69e16c0a614e9bf2295bbbf66208ebf72b660bf425733bfd40206bf25b9d8be337aabbefa457ebe7a1c51beaefd23be95e9f6bd2be0c9bd6fe19cbd5eed6fbdf50343bd322516bd1351e9bc9487bcbcb3c88fbc6e1463bcc26a36bcadcb09bc2c37ddbb3dadb0bbdd2d84bb09b957bbc04e2bbbffeefebac299d2ba084fa6bacf0e7aba13d94dbad2ad21ba0b8df5b9b976c9b9db6a9db96e6971b9707245b9df8519b9b7a3edb8f7cbc1b89cfe95b8a33b6ab80a833eb8cfd412b8ee30e7b76797bbb7350890b7588364b7cb0839b78e980db79c32e2b6f5d6b6b695858bb67b3e60b6a30135b60bcf09b6b1a6deb59288b3b5ac7488b5fd6a5db5816b32b5387607b51d8bdcb430aab1b46dd386b4d2065cb45c4431b40a8c06b4d8dddbb3c539b1b3ce9f86b3f00f5cb32099c0d9861546f530336e7a710600000000002edb0b65000000000a0d0a0000000a0a0000000f001e000a000000140000003c00000001000000000000000000000000000000000000000000000000350c0020a10700a0860100e803000064000000070507083704000002080000000000000001000000000000000b80012003", + "hash":"0x21e0f6e8607b04fa34d54a8a776adfe7e0e5a8931005ce8a66c5990fa1c2f960" +} \ No newline at end of file diff --git a/sdk/tests/types/fixtures/tip45_32_output_proof_idx0.json b/sdk/tests/types/fixtures/tip45_32_output_proof_idx0.json new file mode 100644 index 0000000000..02f28d5bb8 --- /dev/null +++ b/sdk/tests/types/fixtures/tip45_32_output_proof_idx0.json @@ -0,0 +1,55 @@ +{ + "transaction_bytes": "0x01fb5c44ef0d3ac8730b0000000000010000f09d3cd648a7246c7c1b2ba2f9182465ae5742b78c592392b4b455ab8ed719520000000000000000013f0000000021000040420f0000000000000000000000000001000041b5fc0f730776b9d25dc9ffba236ce34bd9433f8a4bc8e286612e42654e0138000040420f00000000000000000000000000010000c5797137d27ceeb1d7a7f712b2dbbd2cb6b9e3fa39fe98a7751ea5913f00495d000040420f00000000000000000000000000010000c3d789866209202d21895e9a5a69ceb4b1fba08f53237c697e32cf6780a89a71000040420f000000000000000000000000000100008b1697f39c48f841e78e9e3fb2eae08d6af3463fc26fdfbf8ae23ad86ef5353d000040420f00000000000000000000000000010000145a6400ae4ce60e2192b6e538bb790a774db03070afaff1f0c83beedd15fa65000040420f00000000000000000000000000010000f25fbe26abe7ddc7b1ba9fc94a8f6c5a1ac025b0f8027fb7c5ec6047b7715271000040420f00000000000000000000000000010000ea2e3aa40282125d37373a7a51b94e806156f3d4d5a3e4a7679e9dac36ae5daa000040420f000000000000000000000000000100003475a41594b179a940fe311fddb2b8f0f68ff86fc88d50b085ed0af6dc916879000040420f00000000000000000000000000010000e7f9581f4105471c428dcba08f274986fbc084cbebbe77e0121f6486d7543dab000040420f00000000000000000000000000010000b2313d9e1e500c0105496d43459c33692af0d72c5412a6aa896c2d968aa09d0b000040420f0000000000000000000000000001000056db5b539ddeda7e1c81602669e26506d1a5b3119713d30090ba674cd837e567000040420f000000000000000000000000000100000f29bab850895afefbc2fcc2db1d60f1d86e275ef406b8d98c4e7ed6112c74e5000040420f00000000000000000000000000010000bda720e6d8bf203933dbf1dc4bd00e87e39e93fe514f6e64c9288ae8ce0ca84a000040420f000000000000000000000000000100009d3fd662ba96b4df02d578526090c1f9a580e1d93376380b302bc153b08c9bca000040420f00000000000000000000000000010000613fc72ee7c78035e0cc469a1f49629a98e5ad9c87ce441b9605baa249c42744000040420f000000000000000000000000000100004400a5ed5664a913bb87cb0af6b81bbe01b6a34824f92822cacf6d28f24c235e000040420f000000000000000000000000000100000b4cca1c8d620d650c8bf0f6b3575278732bb79b0156080557a72ffca84e4049000040420f00000000000000000000000000010000f8d2c1579c46b28987a0ed69f1d751b166c4dfd8c1701066aa9d3ad16761785a000040420f00000000000000000000000000010000cd682775e623f1b3f4a99f4d10509aae49a9e4fb2bb959c604d95b7f053fbd38000040420f000000000000000000000000000100006de2cd6d6b75cdf3ecdddcab903e6df6ca288d8ae3ea66c4281b7f86cf063e36000040420f000000000000000000000000000100002a207ba664da0a73f7722868af1396e35e94c96ceb32ddba28dfe862258ac8cb000040420f000000000000000000000000000100004e9807d3cfd5b26ad015b3a8c746d602234a8aeeb09a4f0b814ccf092170086c000040420f000000000000000000000000000100008c2150b4207ba52c8b75ebf13bcb9db776ca103c3ccd6fd25c065a4c91878751000040420f000000000000000000000000000100003293f372d83017fb62eddadf4dfe963c92b5aaa9a4b955da9e8c165318fe8e05000040420f00000000000000000000000000010000c439c102d9b01aab0c7dd33763fb9d376defb5b0d3238122835b9b0eb2def7c6000040420f00000000000000000000000000010000757a899de49f0b2a00dc90ef5bd901c3e24e4998d939cff0f0cf55e8e208e41d000040420f0000000000000000000000000001000032a40408819bfce14fc7101a42d4fc6cf02780c385fc2c3c07e38ddab307f57e000040420f000000000000000000000000000100003f010f28a30d3a5caf57b682ee1b29a89fe13847d761f751a2efef3079d20047000040420f00000000000000000000000000010000c1c3d93b0a50cae27cbfc53cf0195430cda86ce34a63471a59120a61cbc3f5ea000040420f0000000000000000000000000001000009ee1ed9707cec374bdc4977ed77fa237e9f788f6bd47ecbe65d5e3788ccc9b1000040420f00000000000000000000000000010000df40b4f6fe643a1ab1a4ecc7f2f1d2feb24aea9b8cfedb09d21d4dd1870f784e000040420f00000000000000000000000000010000f77ec745cf5fe977cd02905d866cb2ecce4631d02910f96ac49945db418966cb000040420f000000000000000000000000000100009d1879ca302277d00c6a00aa70a23caae1aadbc1232cd3026875ba9291cce11600010000002daefbcbadd044da470acd2f7fcf6fcb04b873cc801e7ee408018e1dfa0257accae4a03e1357e6ce71deb6f0de8ec9cf2c0f62cbcc0a54a7ea8db5c05257f2c538139739cc41aebd32ffee5f45d8b2c5004079d4d675189d3aa092cc673de704", + "proof": { + "slot": 11, + "outputIndex": 0, + "transactionCommitment": "0xe5cd573b65e2e10f8811a5a5df5af36b385dc2d75bf13b7c731aa4660125f470", + "outputCommitmentProof": { + "type": 0, + "l": { + "type": 0, + "l": { + "type": 0, + "l": { + "type": 0, + "l": { + "type": 0, + "l": { + "type": 0, + "l": { + "type": 2, + "hash": "0xb750f19d60bdd576e8eb617d101ae54146b5511b76d41294b8b9a869f12ab13b" + }, + "r": { + "type": 1, + "hash": "0x6b9c6672c54f7dc2012fea833416a8425a3dc40a4855a63908e741568843ab72" + } + }, + "r": { + "type": 1, + "hash": "0xb125aad50138d88d8c43459345cbab04523c7e8fc4ea00a2c63bf9c792734775" + } + }, + "r": { + "type": 1, + "hash": "0x91ce0e100a30fed59e0ef34bc4fae37aa46811d48cc18f95b2cc02371fb7e7b8" + } + }, + "r": { + "type": 1, + "hash": "0x8211ee89080681552986e3321b231ff1cfdc114a952c1e3d3fc934283603df1f" + } + }, + "r": { + "type": 1, + "hash": "0x84fc02511dfc0326c9c82aa7081430cbfac7dc9d4e9144527374f66399e5c790" + } + }, + "r": { + "type": 1, + "hash": "0x4ad094e37e0ec880bb6a24f03f3bc3ca5fdb60af734606ba639bd413eb1a1e39" + } + } + }, + "proof_bytes": "0x0b0000000000e5cd573b65e2e10f8811a5a5df5af36b385dc2d75bf13b7c731aa4660125f4700000000000000220b750f19d60bdd576e8eb617d101ae54146b5511b76d41294b8b9a869f12ab13b01206b9c6672c54f7dc2012fea833416a8425a3dc40a4855a63908e741568843ab720120b125aad50138d88d8c43459345cbab04523c7e8fc4ea00a2c63bf9c792734775012091ce0e100a30fed59e0ef34bc4fae37aa46811d48cc18f95b2cc02371fb7e7b801208211ee89080681552986e3321b231ff1cfdc114a952c1e3d3fc934283603df1f012084fc02511dfc0326c9c82aa7081430cbfac7dc9d4e9144527374f66399e5c79001204ad094e37e0ec880bb6a24f03f3bc3ca5fdb60af734606ba639bd413eb1a1e39" +} diff --git a/sdk/tests/types/fixtures/tip45_32_output_proof_idx28.json b/sdk/tests/types/fixtures/tip45_32_output_proof_idx28.json new file mode 100644 index 0000000000..d57b824b45 --- /dev/null +++ b/sdk/tests/types/fixtures/tip45_32_output_proof_idx28.json @@ -0,0 +1,55 @@ +{ + "transaction_bytes": "0x01fb5c44ef0d3ac8730b0000000000010000f09d3cd648a7246c7c1b2ba2f9182465ae5742b78c592392b4b455ab8ed719520000000000000000013f0000000021000040420f0000000000000000000000000001000041b5fc0f730776b9d25dc9ffba236ce34bd9433f8a4bc8e286612e42654e0138000040420f00000000000000000000000000010000c5797137d27ceeb1d7a7f712b2dbbd2cb6b9e3fa39fe98a7751ea5913f00495d000040420f00000000000000000000000000010000c3d789866209202d21895e9a5a69ceb4b1fba08f53237c697e32cf6780a89a71000040420f000000000000000000000000000100008b1697f39c48f841e78e9e3fb2eae08d6af3463fc26fdfbf8ae23ad86ef5353d000040420f00000000000000000000000000010000145a6400ae4ce60e2192b6e538bb790a774db03070afaff1f0c83beedd15fa65000040420f00000000000000000000000000010000f25fbe26abe7ddc7b1ba9fc94a8f6c5a1ac025b0f8027fb7c5ec6047b7715271000040420f00000000000000000000000000010000ea2e3aa40282125d37373a7a51b94e806156f3d4d5a3e4a7679e9dac36ae5daa000040420f000000000000000000000000000100003475a41594b179a940fe311fddb2b8f0f68ff86fc88d50b085ed0af6dc916879000040420f00000000000000000000000000010000e7f9581f4105471c428dcba08f274986fbc084cbebbe77e0121f6486d7543dab000040420f00000000000000000000000000010000b2313d9e1e500c0105496d43459c33692af0d72c5412a6aa896c2d968aa09d0b000040420f0000000000000000000000000001000056db5b539ddeda7e1c81602669e26506d1a5b3119713d30090ba674cd837e567000040420f000000000000000000000000000100000f29bab850895afefbc2fcc2db1d60f1d86e275ef406b8d98c4e7ed6112c74e5000040420f00000000000000000000000000010000bda720e6d8bf203933dbf1dc4bd00e87e39e93fe514f6e64c9288ae8ce0ca84a000040420f000000000000000000000000000100009d3fd662ba96b4df02d578526090c1f9a580e1d93376380b302bc153b08c9bca000040420f00000000000000000000000000010000613fc72ee7c78035e0cc469a1f49629a98e5ad9c87ce441b9605baa249c42744000040420f000000000000000000000000000100004400a5ed5664a913bb87cb0af6b81bbe01b6a34824f92822cacf6d28f24c235e000040420f000000000000000000000000000100000b4cca1c8d620d650c8bf0f6b3575278732bb79b0156080557a72ffca84e4049000040420f00000000000000000000000000010000f8d2c1579c46b28987a0ed69f1d751b166c4dfd8c1701066aa9d3ad16761785a000040420f00000000000000000000000000010000cd682775e623f1b3f4a99f4d10509aae49a9e4fb2bb959c604d95b7f053fbd38000040420f000000000000000000000000000100006de2cd6d6b75cdf3ecdddcab903e6df6ca288d8ae3ea66c4281b7f86cf063e36000040420f000000000000000000000000000100002a207ba664da0a73f7722868af1396e35e94c96ceb32ddba28dfe862258ac8cb000040420f000000000000000000000000000100004e9807d3cfd5b26ad015b3a8c746d602234a8aeeb09a4f0b814ccf092170086c000040420f000000000000000000000000000100008c2150b4207ba52c8b75ebf13bcb9db776ca103c3ccd6fd25c065a4c91878751000040420f000000000000000000000000000100003293f372d83017fb62eddadf4dfe963c92b5aaa9a4b955da9e8c165318fe8e05000040420f00000000000000000000000000010000c439c102d9b01aab0c7dd33763fb9d376defb5b0d3238122835b9b0eb2def7c6000040420f00000000000000000000000000010000757a899de49f0b2a00dc90ef5bd901c3e24e4998d939cff0f0cf55e8e208e41d000040420f0000000000000000000000000001000032a40408819bfce14fc7101a42d4fc6cf02780c385fc2c3c07e38ddab307f57e000040420f000000000000000000000000000100003f010f28a30d3a5caf57b682ee1b29a89fe13847d761f751a2efef3079d20047000040420f00000000000000000000000000010000c1c3d93b0a50cae27cbfc53cf0195430cda86ce34a63471a59120a61cbc3f5ea000040420f0000000000000000000000000001000009ee1ed9707cec374bdc4977ed77fa237e9f788f6bd47ecbe65d5e3788ccc9b1000040420f00000000000000000000000000010000df40b4f6fe643a1ab1a4ecc7f2f1d2feb24aea9b8cfedb09d21d4dd1870f784e000040420f00000000000000000000000000010000f77ec745cf5fe977cd02905d866cb2ecce4631d02910f96ac49945db418966cb000040420f000000000000000000000000000100009d1879ca302277d00c6a00aa70a23caae1aadbc1232cd3026875ba9291cce11600010000002daefbcbadd044da470acd2f7fcf6fcb04b873cc801e7ee408018e1dfa0257accae4a03e1357e6ce71deb6f0de8ec9cf2c0f62cbcc0a54a7ea8db5c05257f2c538139739cc41aebd32ffee5f45d8b2c5004079d4d675189d3aa092cc673de704", + "proof": { + "slot": 11, + "outputIndex": 28, + "transactionCommitment": "0xe5cd573b65e2e10f8811a5a5df5af36b385dc2d75bf13b7c731aa4660125f470", + "outputCommitmentProof": { + "type": 0, + "l": { + "type": 0, + "l": { + "type": 1, + "hash": "0x7f564883c631d43a902ef144a674e6ac11ed7db1c9e88ecf7a664c19885de497" + }, + "r": { + "type": 0, + "l": { + "type": 1, + "hash": "0xaa70d7aab4560bcafda057bcffca504f4d6ca89bc5652d4cf60a33e85a39bc5f" + }, + "r": { + "type": 0, + "l": { + "type": 1, + "hash": "0xc6e9e11d1ae2b9479e778fed77d273ced27cfee60b8d9c2cb0fce4efe5b1d179" + }, + "r": { + "type": 0, + "l": { + "type": 0, + "l": { + "type": 2, + "hash": "0x56cf9617298303d80e0b0bc7a38e9807db5e6b5c1dbe32338ec54efa830f1d93" + }, + "r": { + "type": 1, + "hash": "0x45d50fb24eecc01fed1254b75e0ddc97c3d8825f02e68b75da3f16f14573cc53" + } + }, + "r": { + "type": 1, + "hash": "0x2e88c7335dcc28ef7cf618d77336b62f49c1d0c05d956e516eb9a37f556373e2" + } + } + } + } + }, + "r": { + "type": 1, + "hash": "0x4ad094e37e0ec880bb6a24f03f3bc3ca5fdb60af734606ba639bd413eb1a1e39" + } + } + }, + "proof_bytes": "0x0b0000001c00e5cd573b65e2e10f8811a5a5df5af36b385dc2d75bf13b7c731aa4660125f470000001207f564883c631d43a902ef144a674e6ac11ed7db1c9e88ecf7a664c19885de497000120aa70d7aab4560bcafda057bcffca504f4d6ca89bc5652d4cf60a33e85a39bc5f000120c6e9e11d1ae2b9479e778fed77d273ced27cfee60b8d9c2cb0fce4efe5b1d1790000022056cf9617298303d80e0b0bc7a38e9807db5e6b5c1dbe32338ec54efa830f1d93012045d50fb24eecc01fed1254b75e0ddc97c3d8825f02e68b75da3f16f14573cc5301202e88c7335dcc28ef7cf618d77336b62f49c1d0c05d956e516eb9a37f556373e201204ad094e37e0ec880bb6a24f03f3bc3ca5fdb60af734606ba639bd413eb1a1e39" +} diff --git a/sdk/tests/types/fixtures/tip45_five_output_proof.json b/sdk/tests/types/fixtures/tip45_five_output_proof.json new file mode 100644 index 0000000000..69f05bb498 --- /dev/null +++ b/sdk/tests/types/fixtures/tip45_five_output_proof.json @@ -0,0 +1,34 @@ +{ + "transaction_bytes": "0x01fb5c44ef0d3ac8730b0000000000010000f09d3cd648a7246c7c1b2ba2f9182465ae5742b78c592392b4b455ab8ed719520000000000000000013f0000000006000040420f0000000000000000000000000001000041b5fc0f730776b9d25dc9ffba236ce34bd9433f8a4bc8e286612e42654e0138000040420f00000000000000000000000000010000c5797137d27ceeb1d7a7f712b2dbbd2cb6b9e3fa39fe98a7751ea5913f00495d000040420f00000000000000000000000000010000c3d789866209202d21895e9a5a69ceb4b1fba08f53237c697e32cf6780a89a71000040420f000000000000000000000000000100008b1697f39c48f841e78e9e3fb2eae08d6af3463fc26fdfbf8ae23ad86ef5353d000040420f00000000000000000000000000010000145a6400ae4ce60e2192b6e538bb790a774db03070afaff1f0c83beedd15fa65000040420f00000000000000000000000000010000f25fbe26abe7ddc7b1ba9fc94a8f6c5a1ac025b0f8027fb7c5ec6047b771527100010000002daefbcbadd044da470acd2f7fcf6fcb04b873cc801e7ee408018e1dfa0257ac75b951fbf7c55b41b6323cd9b00fe5c444f3145d7bc75da7ae366756f01ee8a18a8e15c6321eb0972084d05422b6eb257869f6f5333ae17b763c0f111d65510e", + "proof": { + "slot": 11, + "outputIndex": 2, + "transactionCommitment": "0xe5cd573b65e2e10f8811a5a5df5af36b385dc2d75bf13b7c731aa4660125f470", + "outputCommitmentProof": { + "type": 0, + "l": { + "type": 0, + "l": { + "type": 1, + "hash": "0xdb9fc09b2bdea181fd4f9775116a2ec3c27c93a7ce0ce8f5835fda73bfd09b49" + }, + "r": { + "type": 0, + "l": { + "type": 2, + "hash": "0xe6fc3cdc50433760d43adfe52426648597c092d8973a30529ad0058d6a03b55d" + }, + "r": { + "type": 1, + "hash": "0x7392fe9c9e518a0bc1b0f032e003dbf73096fb95a8bfdbfe05e05f67e0894038" + } + } + }, + "r": { + "type": 1, + "hash": "0xc00b8503cb3aa5473f1a4b4d68cdad468c52e628fdf8dc175caae2d8bec75297" + } + } + }, + "proof_bytes": "0x0b0000000200e5cd573b65e2e10f8811a5a5df5af36b385dc2d75bf13b7c731aa4660125f47000000120db9fc09b2bdea181fd4f9775116a2ec3c27c93a7ce0ce8f5835fda73bfd09b49000220e6fc3cdc50433760d43adfe52426648597c092d8973a30529ad0058d6a03b55d01207392fe9c9e518a0bc1b0f032e003dbf73096fb95a8bfdbfe05e05f67e08940380120c00b8503cb3aa5473f1a4b4d68cdad468c52e628fdf8dc175caae2d8bec75297" +} diff --git a/sdk/tests/types/fixtures/tip45_single_output_proof.json b/sdk/tests/types/fixtures/tip45_single_output_proof.json new file mode 100644 index 0000000000..4b8dc93491 --- /dev/null +++ b/sdk/tests/types/fixtures/tip45_single_output_proof.json @@ -0,0 +1,20 @@ +{ + "transaction_bytes": "0x01fb5c44ef0d3ac8730b0000000000010000f09d3cd648a7246c7c1b2ba2f9182465ae5742b78c592392b4b455ab8ed719520000000000000000013f0000000002000040420f0000000000000000000000000001000041b5fc0f730776b9d25dc9ffba236ce34bd9433f8a4bc8e286612e42654e0138000040420f00000000000000000000000000010000c5797137d27ceeb1d7a7f712b2dbbd2cb6b9e3fa39fe98a7751ea5913f00495d00010000002daefbcbadd044da470acd2f7fcf6fcb04b873cc801e7ee408018e1dfa0257acea95793c5a8aef7eeb0daa2a1632f0abf6eeb4c7ca19808f20f6101a6d22ea8f8b4d85dbbbd21d0ac521991a57536fbd37bccd85ab29254a3b5d2e534fda5506", + "proof": { + "slot": 11, + "outputIndex": 0, + "transactionCommitment": "0xe5cd573b65e2e10f8811a5a5df5af36b385dc2d75bf13b7c731aa4660125f470", + "outputCommitmentProof": { + "type": 0, + "l": { + "type": 2, + "hash": "0xb750f19d60bdd576e8eb617d101ae54146b5511b76d41294b8b9a869f12ab13b" + }, + "r": { + "type": 1, + "hash": "0x6b9c6672c54f7dc2012fea833416a8425a3dc40a4855a63908e741568843ab72" + } + } + }, + "proof_bytes": "0x0b0000000000e5cd573b65e2e10f8811a5a5df5af36b385dc2d75bf13b7c731aa4660125f470000220b750f19d60bdd576e8eb617d101ae54146b5511b76d41294b8b9a869f12ab13b01206b9c6672c54f7dc2012fea833416a8425a3dc40a4855a63908e741568843ab72" +} diff --git a/sdk/tests/types/input/utxo.rs b/sdk/tests/types/input/utxo.rs index e097db2bb0..0cd2b4e238 100644 --- a/sdk/tests/types/input/utxo.rs +++ b/sdk/tests/types/input/utxo.rs @@ -96,11 +96,14 @@ fn pack_unpack() { assert_eq!( utxo_input, - UtxoInput::unpack_verified(packed_input.as_slice(), &()).unwrap() + UtxoInput::unpack_bytes_verified(packed_input.as_slice(), &()).unwrap() ); let input = Input::from(utxo_input); let packed_input = input.pack_to_vec(); - assert_eq!(input, Input::unpack_verified(packed_input.as_slice(), &()).unwrap()); + assert_eq!( + input, + Input::unpack_bytes_verified(packed_input.as_slice(), &()).unwrap() + ); } diff --git a/sdk/tests/types/mod.rs b/sdk/tests/types/mod.rs index fd58dfba92..e4a8c1a5bd 100644 --- a/sdk/tests/types/mod.rs +++ b/sdk/tests/types/mod.rs @@ -3,20 +3,25 @@ mod address; mod api; +#[cfg(feature = "protocol_parameters_samples")] mod block; mod block_id; mod ed25519_signature; mod foundry_id; mod input; +#[cfg(feature = "protocol_parameters_samples")] mod output; mod output_id; mod parents; +#[cfg(feature = "protocol_parameters_samples")] mod payload; mod protocol_parameters; +#[cfg(feature = "protocol_parameters_samples")] mod signed_transaction_payload; mod slot; mod storage_score; mod tagged_data_payload; +#[cfg(feature = "protocol_parameters_samples")] mod transaction; mod transaction_id; mod unlock; diff --git a/sdk/tests/types/output/account.rs b/sdk/tests/types/output/account.rs index 57f5b2474c..7d441107b9 100644 --- a/sdk/tests/types/output/account.rs +++ b/sdk/tests/types/output/account.rs @@ -3,7 +3,7 @@ use iota_sdk::types::block::{ output::{AccountOutput, Feature, MinimumOutputAmount}, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, rand::output::{ feature::{rand_issuer_feature, rand_metadata_feature, rand_sender_feature}, rand_account_id, rand_account_output, @@ -15,7 +15,7 @@ use pretty_assertions::assert_eq; #[test] fn builder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let account_id = rand_account_id(); let address_1 = rand_address_unlock_condition_different_from_account_id(&account_id); let address_2 = rand_address_unlock_condition_different_from_account_id(&account_id); @@ -70,10 +70,10 @@ fn builder() { #[test] fn pack_unpack() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let output = rand_account_output(protocol_parameters.token_supply()); let bytes = output.pack_to_vec(); - let output_unpacked = AccountOutput::unpack_verified(bytes, &protocol_parameters).unwrap(); + let output_unpacked = AccountOutput::unpack_bytes_verified(bytes, protocol_parameters).unwrap(); assert_eq!(output, output_unpacked); } diff --git a/sdk/tests/types/output/basic.rs b/sdk/tests/types/output/basic.rs index 42268b7afe..f24b66de14 100644 --- a/sdk/tests/types/output/basic.rs +++ b/sdk/tests/types/output/basic.rs @@ -3,7 +3,7 @@ use iota_sdk::types::block::{ output::{BasicOutput, Feature, FoundryId, MinimumOutputAmount, NativeToken, SimpleTokenScheme, TokenId}, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, rand::{ address::rand_account_address, output::{ @@ -18,7 +18,7 @@ use pretty_assertions::assert_eq; #[test] fn builder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let foundry_id = FoundryId::build(&rand_account_address(), 0, SimpleTokenScheme::KIND); let address_1 = rand_address_unlock_condition(); let address_2 = rand_address_unlock_condition(); @@ -64,10 +64,10 @@ fn builder() { #[test] fn pack_unpack() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let output = rand_basic_output(protocol_parameters.token_supply()); let bytes = output.pack_to_vec(); - let output_unpacked = BasicOutput::unpack_verified(bytes, &protocol_parameters).unwrap(); + let output_unpacked = BasicOutput::unpack_bytes_verified(bytes, protocol_parameters).unwrap(); assert_eq!(output, output_unpacked); } diff --git a/sdk/tests/types/output/feature/metadata.rs b/sdk/tests/types/output/feature/metadata.rs index 491e33b506..40bcf46f17 100644 --- a/sdk/tests/types/output/feature/metadata.rs +++ b/sdk/tests/types/output/feature/metadata.rs @@ -1,7 +1,7 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::block::{output::feature::MetadataFeature, Error}; +use iota_sdk::types::block::output::feature::{FeatureError, MetadataFeature}; use packable::{error::UnpackError, PackableExt}; #[test] @@ -46,15 +46,15 @@ fn serde_roundtrip() { #[test] fn unpack_invalid_order() { assert!(matches!( - MetadataFeature::unpack_verified([3, 1, 99, 0, 0, 1, 98, 0, 0, 1, 97, 0, 0], &()), - Err(UnpackError::Packable(Error::InvalidMetadataFeature(error_msg))) if &error_msg == "unordered map" + MetadataFeature::unpack_bytes_verified([3, 1, 99, 0, 0, 1, 98, 0, 0, 1, 97, 0, 0], &()), + Err(UnpackError::Packable(FeatureError::MetadataFeature(error_msg))) if &error_msg == "unordered map" )); } #[test] fn unpack_invalid_length() { assert!(matches!( - MetadataFeature::unpack_verified([vec![1, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()), - Err(UnpackError::Packable(Error::InvalidMetadataFeature(len))) if &len == "Out of bounds byte length: 8197" + MetadataFeature::unpack_bytes_verified([vec![1, 1, 33, 0, 32], vec![0u8; 8192]].concat(), &()), + Err(UnpackError::Packable(FeatureError::MetadataFeature(len))) if &len == "Out of bounds byte length: 8197" )); } diff --git a/sdk/tests/types/output/foundry.rs b/sdk/tests/types/output/foundry.rs index 8a6b39822c..ef5b56b494 100644 --- a/sdk/tests/types/output/foundry.rs +++ b/sdk/tests/types/output/foundry.rs @@ -6,7 +6,7 @@ use iota_sdk::types::block::{ unlock_condition::ImmutableAccountAddressUnlockCondition, FoundryId, FoundryOutput, MinimumOutputAmount, NativeToken, SimpleTokenScheme, TokenId, }, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, rand::{ address::rand_account_address, output::{feature::rand_metadata_feature, rand_foundry_output, rand_token_scheme}, @@ -17,7 +17,7 @@ use pretty_assertions::assert_eq; #[test] fn builder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let foundry_id = FoundryId::build(&rand_account_address(), 0, SimpleTokenScheme::KIND); let account_1 = ImmutableAccountAddressUnlockCondition::new(rand_account_address()); let account_2 = ImmutableAccountAddressUnlockCondition::new(rand_account_address()); @@ -65,10 +65,10 @@ fn builder() { #[test] fn pack_unpack() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let output = rand_foundry_output(protocol_parameters.token_supply()); let bytes = output.pack_to_vec(); - let output_unpacked = FoundryOutput::unpack_verified(bytes, &protocol_parameters).unwrap(); + let output_unpacked = FoundryOutput::unpack_bytes_verified(bytes, protocol_parameters).unwrap(); assert_eq!(output, output_unpacked); } diff --git a/sdk/tests/types/output/mod.rs b/sdk/tests/types/output/mod.rs index 986986ff7b..03173a9454 100644 --- a/sdk/tests/types/output/mod.rs +++ b/sdk/tests/types/output/mod.rs @@ -7,3 +7,4 @@ mod foundry; mod nft; mod feature; +mod proof; diff --git a/sdk/tests/types/output/nft.rs b/sdk/tests/types/output/nft.rs index 5eb4322ecf..abff7af9d3 100644 --- a/sdk/tests/types/output/nft.rs +++ b/sdk/tests/types/output/nft.rs @@ -3,7 +3,7 @@ use iota_sdk::types::block::{ output::{MinimumOutputAmount, NftId, NftOutput}, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, rand::output::{ feature::{rand_issuer_feature, rand_sender_feature}, rand_nft_output, @@ -15,7 +15,7 @@ use pretty_assertions::assert_eq; #[test] fn builder() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let address_1 = rand_address_unlock_condition(); let address_2 = rand_address_unlock_condition(); let sender_1 = rand_sender_feature(); @@ -61,10 +61,10 @@ fn builder() { #[test] fn pack_unpack() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let output = rand_nft_output(protocol_parameters.token_supply()); let bytes = output.pack_to_vec(); - let output_unpacked = NftOutput::unpack_verified(bytes, &protocol_parameters).unwrap(); + let output_unpacked = NftOutput::unpack_bytes_verified(bytes, protocol_parameters).unwrap(); assert_eq!(output, output_unpacked); } diff --git a/sdk/tests/types/output/proof.rs b/sdk/tests/types/output/proof.rs new file mode 100644 index 0000000000..35b6b465ec --- /dev/null +++ b/sdk/tests/types/output/proof.rs @@ -0,0 +1,55 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use iota_sdk::{ + types::block::{output::OutputIdProof, payload::Payload}, + utils::serde::prefix_hex_bytes, +}; +use packable::PackableExt; +use pretty_assertions::assert_eq; +use serde::Deserialize; + +#[derive(Deserialize)] +struct ProofFixture { + #[serde(with = "prefix_hex_bytes")] + transaction_bytes: Vec, + proof: OutputIdProof, + proof_bytes: String, +} + +fn proof_fixture(filename: &str) -> Result> { + Ok(serde_json::from_value(serde_json::from_reader(std::fs::File::open( + format!("./tests/types/fixtures/{filename}"), + )?)?)?) +} + +#[test] +fn output_proofs() { + for filename in [ + // https://github.com/iotaledger/tips/blob/tip45/tips/TIP-0045/tip-0045.md#single-output + "tip45_single_output_proof.json", + // https://github.com/iotaledger/tips/blob/tip45/tips/TIP-0045/tip-0045.md#five-outputs + "tip45_five_output_proof.json", + // https://github.com/iotaledger/tips/blob/tip45/tips/TIP-0045/tip-0045.md#32-outputs + "tip45_32_output_proof_idx0.json", + "tip45_32_output_proof_idx28.json", + ] { + let fixture = proof_fixture(filename).unwrap_or_else(|e| panic!("failed to deserialize {filename}: {e}")); + + let payload = Payload::unpack_bytes_unverified(&fixture.transaction_bytes).unwrap(); + let transaction = payload.as_signed_transaction().transaction(); + assert_eq!( + transaction + .output_id_proof(fixture.proof.output_index) + .map(|p| serde_json::to_string_pretty(&p).unwrap()) + .unwrap(), + serde_json::to_string_pretty(&fixture.proof).unwrap(), + "proof mismatch for {filename}" + ); + assert_eq!( + prefix_hex::encode(fixture.proof.pack_to_vec()), + fixture.proof_bytes, + "byte mismatch for {filename}" + ); + } +} diff --git a/sdk/tests/types/output_id.rs b/sdk/tests/types/output_id.rs index 38394a6fab..862aff5ec2 100644 --- a/sdk/tests/types/output_id.rs +++ b/sdk/tests/types/output_id.rs @@ -98,7 +98,7 @@ fn packed_len() { #[test] fn pack_unpack() { let output_id_1 = OutputId::from_str(OUTPUT_ID).unwrap(); - let output_id_2 = OutputId::unpack_verified(output_id_1.pack_to_vec().as_slice(), &()).unwrap(); + let output_id_2 = OutputId::unpack_bytes_verified(output_id_1.pack_to_vec().as_slice(), &()).unwrap(); assert_eq!(output_id_1, output_id_2); } diff --git a/sdk/tests/types/parents.rs b/sdk/tests/types/parents.rs index 8e229e7d2f..0b1448bde7 100644 --- a/sdk/tests/types/parents.rs +++ b/sdk/tests/types/parents.rs @@ -7,7 +7,7 @@ use std::collections::BTreeSet; use iota_sdk::types::block::{ core::basic, rand::block::{rand_block_id, rand_block_ids}, - BlockId, Error, + BlockError, BlockId, }; use packable::{error::UnpackError, prefix::VecPrefix, PackableExt}; use pretty_assertions::assert_eq; @@ -62,7 +62,7 @@ fn new_invalid_more_than_max() { // )); assert!(matches!( basic::StrongParents::from_vec(inner), - Err(Error::InvalidParentCount) + Err(BlockError::InvalidParentCount) )); } @@ -99,7 +99,7 @@ fn packed_len() { #[test] fn pack_unpack_valid() { let parents_1 = basic::StrongParents::from_vec(rand_block_ids(8)).unwrap(); - let parents_2 = basic::StrongParents::unpack_verified(parents_1.pack_to_vec().as_slice(), &()).unwrap(); + let parents_2 = basic::StrongParents::unpack_bytes_verified(parents_1.pack_to_vec().as_slice(), &()).unwrap(); assert_eq!(parents_1, parents_2); } @@ -120,8 +120,8 @@ fn pack_unpack_invalid_less_than_min() { // ))) // )); assert!(matches!( - basic::StrongParents::unpack_verified(bytes.as_slice(), &()), - Err(UnpackError::Packable(Error::InvalidParentCount)) + basic::StrongParents::unpack_bytes_verified(bytes.as_slice(), &()), + Err(UnpackError::Packable(BlockError::InvalidParentCount)) )); } @@ -141,8 +141,8 @@ fn pack_unpack_invalid_more_than_max() { // ))) // )); assert!(matches!( - basic::StrongParents::unpack_verified(bytes.as_slice(), &()), - Err(UnpackError::Packable(Error::InvalidParentCount)) + basic::StrongParents::unpack_bytes_verified(bytes.as_slice(), &()), + Err(UnpackError::Packable(BlockError::InvalidParentCount)) )); } @@ -153,11 +153,11 @@ fn unpack_invalid_not_sorted() { let inner = VecPrefix::<_, u8>::try_from(inner).unwrap(); let packed = inner.pack_to_vec(); - let parents = basic::StrongParents::unpack_verified(packed.as_slice(), &()); + let parents = basic::StrongParents::unpack_bytes_verified(packed.as_slice(), &()); assert!(matches!( parents, - Err(UnpackError::Packable(Error::ParentsNotUniqueSorted)) + Err(UnpackError::Packable(BlockError::ParentsNotUniqueSorted)) ),); } @@ -168,10 +168,10 @@ fn unpack_invalid_not_unique() { let inner = VecPrefix::<_, u8>::try_from(inner).unwrap(); let packed = inner.pack_to_vec(); - let parents = basic::StrongParents::unpack_verified(packed.as_slice(), &()); + let parents = basic::StrongParents::unpack_bytes_verified(packed.as_slice(), &()); assert!(matches!( parents, - Err(UnpackError::Packable(Error::ParentsNotUniqueSorted)) + Err(UnpackError::Packable(BlockError::ParentsNotUniqueSorted)) ),); } diff --git a/sdk/tests/types/payload.rs b/sdk/tests/types/payload.rs index 5e1929ce11..a61a3c5ff2 100644 --- a/sdk/tests/types/payload.rs +++ b/sdk/tests/types/payload.rs @@ -9,7 +9,7 @@ use iota_sdk::types::block::{ signed_transaction::{SignedTransactionPayload, Transaction, TransactionId}, Payload, TaggedDataPayload, }, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, rand::{bytes::rand_bytes, mana::rand_mana_allotment}, signature::{Ed25519Signature, Signature}, unlock::{ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, @@ -24,7 +24,7 @@ const ED25519_SIGNATURE: &str = "0xc6a40edf9a089f42c18f4ebccb35fe4b578d93b879e99 #[test] fn transaction() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -40,8 +40,8 @@ fn transaction() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs(vec![input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); let pub_key_bytes = prefix_hex::decode(ED25519_PUBLIC_KEY).unwrap(); @@ -61,7 +61,7 @@ fn transaction() { assert!(matches!(payload, Payload::SignedTransaction(_))); assert_eq!( payload, - PackableExt::unpack_verified(packed.as_slice(), &protocol_parameters).unwrap() + PackableExt::unpack_bytes_verified(packed.as_slice(), protocol_parameters).unwrap() ); } diff --git a/sdk/tests/types/protocol_parameters.rs b/sdk/tests/types/protocol_parameters.rs index 84c6c201be..3159695c6b 100644 --- a/sdk/tests/types/protocol_parameters.rs +++ b/sdk/tests/types/protocol_parameters.rs @@ -5,16 +5,19 @@ use iota_sdk::types::block::protocol::ProtocolParameters; use packable::PackableExt; use pretty_assertions::assert_eq; -// // Test from https://github.com/iotaledger/tips/blob/tip49/tips/TIP-0049/tip-0049.md#protocol-parameter-hash -// #[test] -// fn serde_packable_hash() { -// let file = std::fs::read_to_string("./tests/types/fixtures/protocol_parameters.json").unwrap(); -// let json = serde_json::from_str::(&file).unwrap(); -// let params_json = &json["params"]; -// let params = serde_json::from_value::(params_json.clone()).unwrap(); -// let params_bytes = params.pack_to_vec(); +// Test from https://github.com/iotaledger/tips/blob/tip49/tips/TIP-0049/tip-0049.md#protocol-parameter-hash +#[test] +fn serde_packable_hash() { + let file = std::fs::read_to_string("./tests/types/fixtures/protocol_parameters.json").unwrap(); + let json = serde_json::from_str::(&file).unwrap(); + let params_json = &json["params"]; + let params = serde_json::from_value::(params_json.clone()).unwrap(); + let params_bytes = params.pack_to_vec(); -// assert_eq!(prefix_hex::encode(¶ms_bytes), json["bytes"]); -// assert_eq!(params, ProtocolParameters::unpack_verified(params_bytes, &()).unwrap()); -// assert_eq!(params.hash().to_string(), json["hash"]); -// } + assert_eq!(prefix_hex::encode(¶ms_bytes), json["bytes"]); + assert_eq!( + params, + ProtocolParameters::unpack_bytes_verified(params_bytes, &()).unwrap() + ); + assert_eq!(params.hash().to_string(), json["hash"]); +} diff --git a/sdk/tests/types/signed_transaction_payload.rs b/sdk/tests/types/signed_transaction_payload.rs index f624285eca..d2e700183a 100644 --- a/sdk/tests/types/signed_transaction_payload.rs +++ b/sdk/tests/types/signed_transaction_payload.rs @@ -5,12 +5,14 @@ use iota_sdk::types::block::{ address::{Address, Ed25519Address}, input::{Input, UtxoInput}, output::{unlock_condition::AddressUnlockCondition, BasicOutput, Output}, - payload::signed_transaction::{SignedTransactionPayload, Transaction, TransactionId}, - protocol::protocol_parameters, + payload::{ + signed_transaction::{SignedTransactionPayload, Transaction, TransactionId}, + PayloadError, + }, + protocol::iota_mainnet_protocol_parameters, rand::mana::rand_mana_allotment, signature::{Ed25519Signature, Signature}, unlock::{ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, - Error, }; use packable::PackableExt; use pretty_assertions::assert_eq; @@ -28,7 +30,7 @@ fn kind() { // Validate that attempting to construct a `SignedTransactionPayload` with too few unlocks is an error. #[test] fn builder_too_few_unlocks() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); // Construct a transaction with two inputs and one output. let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); @@ -45,8 +47,8 @@ fn builder_too_few_unlocks() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); // Construct a list with a single unlock, whereas we have 2 tx inputs. @@ -58,14 +60,14 @@ fn builder_too_few_unlocks() { assert!(matches!( SignedTransactionPayload::new(transaction, unlocks), - Err(Error::InputUnlockCountMismatch{input_count, unlock_count}) + Err(PayloadError::InputUnlockCountMismatch{input_count, unlock_count}) if input_count == 2 && unlock_count == 1)); } // Validate that attempting to construct a `SignedTransactionPayload` with too many unlocks is an error. #[test] fn builder_too_many_unlocks() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); // Construct a transaction with one input and one output. let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); @@ -81,8 +83,8 @@ fn builder_too_many_unlocks() { let transaction = Transaction::builder(protocol_parameters.network_id()) .add_input(input1) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); // Construct a list of two unlocks, whereas we only have 1 tx input. @@ -96,7 +98,7 @@ fn builder_too_many_unlocks() { assert!(matches!( SignedTransactionPayload::new(transaction, unlocks), - Err(Error::InputUnlockCountMismatch{input_count, unlock_count}) + Err(PayloadError::InputUnlockCountMismatch{input_count, unlock_count}) if input_count == 1 && unlock_count == 2)); } @@ -104,7 +106,7 @@ fn builder_too_many_unlocks() { #[test] fn pack_unpack_valid() { // Construct a transaction with two inputs and one output. - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -120,8 +122,8 @@ fn pack_unpack_valid() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); // Construct a list of two unlocks, whereas we only have 1 tx input. @@ -138,13 +140,13 @@ fn pack_unpack_valid() { assert_eq!(packed_tx_payload.len(), tx_payload.packed_len()); assert_eq!( tx_payload, - PackableExt::unpack_verified(packed_tx_payload.as_slice(), &protocol_parameters).unwrap() + PackableExt::unpack_bytes_verified(packed_tx_payload.as_slice(), protocol_parameters).unwrap() ); } #[test] fn getters() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); // Construct a transaction with two inputs and one output. let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); @@ -161,8 +163,8 @@ fn getters() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); // Construct a list of two unlocks, whereas we only have 1 tx input. diff --git a/sdk/tests/types/slot.rs b/sdk/tests/types/slot.rs index 711824baea..da2f009c42 100644 --- a/sdk/tests/types/slot.rs +++ b/sdk/tests/types/slot.rs @@ -26,7 +26,7 @@ fn slot_commitment_id_index() { assert_eq!(prefix_hex::encode(&commitment_bytes), fixture.bytes); assert_eq!( fixture.commitment, - SlotCommitment::unpack_verified(commitment_bytes, &()).unwrap() + SlotCommitment::unpack_bytes_verified(commitment_bytes, &()).unwrap() ); assert_eq!(commitment_id, fixture.id); assert_eq!(commitment_id.slot_index(), fixture.commitment.slot()); diff --git a/sdk/tests/types/tagged_data_payload.rs b/sdk/tests/types/tagged_data_payload.rs index e02934f949..ccaca36750 100644 --- a/sdk/tests/types/tagged_data_payload.rs +++ b/sdk/tests/types/tagged_data_payload.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::types::block::{ - payload::tagged_data::TaggedDataPayload, + payload::{tagged_data::TaggedDataPayload, PayloadError}, rand::bytes::{rand_bytes, rand_bytes_array}, - Block, Error, + Block, }; use packable::{ bounded::{TryIntoBoundedU32Error, TryIntoBoundedU8Error}, @@ -59,7 +59,7 @@ fn new_valid_tag_length_min() { fn new_invalid_tag_length_more_than_max() { assert!(matches!( TaggedDataPayload::new(rand_bytes(65), [0x42, 0xff, 0x84, 0xa2, 0x42, 0xff, 0x84, 0xa2]), - Err(Error::InvalidTagLength(TryIntoBoundedU8Error::Invalid(65))) + Err(PayloadError::TagLength(TryIntoBoundedU8Error::Invalid(65))) )); } @@ -68,7 +68,7 @@ fn new_invalid_data_length_more_than_max() { assert!(matches!( // TODO https://github.com/iotaledger/iota-sdk/issues/1226 TaggedDataPayload::new(rand_bytes(32), [0u8; Block::LENGTH_MAX + 42]), - Err(Error::InvalidTaggedDataLength(TryIntoBoundedU32Error::Invalid(l))) if l == Block::LENGTH_MAX as u32 + 42 + Err(PayloadError::TaggedDataLength(TryIntoBoundedU32Error::Invalid(l))) if l == Block::LENGTH_MAX as u32 + 42 )); } @@ -84,7 +84,7 @@ fn packed_len() { fn pack_unpack_valid() { let tagged_data_1 = TaggedDataPayload::new(rand_bytes(32), [0x42, 0xff, 0x84, 0xa2, 0x42, 0xff, 0x84, 0xa2]).unwrap(); - let tagged_data_2 = TaggedDataPayload::unpack_verified(tagged_data_1.pack_to_vec().as_slice(), &()).unwrap(); + let tagged_data_2 = TaggedDataPayload::unpack_bytes_verified(tagged_data_1.pack_to_vec().as_slice(), &()).unwrap(); assert_eq!(tagged_data_1.tag(), tagged_data_2.tag()); assert_eq!(tagged_data_1.data(), tagged_data_2.data()); @@ -92,7 +92,8 @@ fn pack_unpack_valid() { #[test] fn unpack_valid_tag_length_min() { - let payload = TaggedDataPayload::unpack_verified([0x00, 0x00, 0x00, 0x00, 0x00, 0x00].as_slice(), &()).unwrap(); + let payload = + TaggedDataPayload::unpack_bytes_verified([0x00, 0x00, 0x00, 0x00, 0x00, 0x00].as_slice(), &()).unwrap(); assert!(payload.tag().is_empty()); } @@ -100,7 +101,7 @@ fn unpack_valid_tag_length_min() { #[test] fn unpack_invalid_tag_length_more_than_max() { assert!(matches!( - TaggedDataPayload::unpack_verified( + TaggedDataPayload::unpack_bytes_verified( [ 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -110,7 +111,7 @@ fn unpack_invalid_tag_length_more_than_max() { ], &() ), - Err(UnpackError::Packable(Error::InvalidTagLength( + Err(UnpackError::Packable(PayloadError::TagLength( TryIntoBoundedU8Error::Invalid(65) ))) )); @@ -119,8 +120,8 @@ fn unpack_invalid_tag_length_more_than_max() { #[test] fn unpack_invalid_data_length_more_than_max() { assert!(matches!( - TaggedDataPayload::unpack_verified([0x02, 0x00, 0x00, 0x35, 0x82, 0x00, 0x00], &()), - Err(UnpackError::Packable(Error::InvalidTaggedDataLength( + TaggedDataPayload::unpack_bytes_verified([0x02, 0x00, 0x00, 0x35, 0x82, 0x00, 0x00], &()), + Err(UnpackError::Packable(PayloadError::TaggedDataLength( TryIntoBoundedU32Error::Invalid(33333) ))) )); diff --git a/sdk/tests/types/transaction.rs b/sdk/tests/types/transaction.rs index e32da6a7a2..f564ff1bf9 100644 --- a/sdk/tests/types/transaction.rs +++ b/sdk/tests/types/transaction.rs @@ -15,13 +15,12 @@ use iota_sdk::types::block::{ signed_transaction::{ SignedTransactionPayload, Transaction, TransactionCapabilities, TransactionCapabilityFlag, TransactionId, }, - Payload, + Payload, PayloadError, }, - protocol::protocol_parameters, + protocol::iota_mainnet_protocol_parameters, rand::{mana::rand_mana_allotment, payload::rand_tagged_data_payload}, signature::{Ed25519Signature, Signature}, unlock::{ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, - Error, }; use packable::bounded::TryIntoBoundedU16Error; use pretty_assertions::assert_eq; @@ -34,7 +33,7 @@ const ED25519_SIGNATURE: &str = "0xc6a40edf9a089f42c18f4ebccb35fe4b578d93b879e99 #[test] fn build_valid() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -50,15 +49,15 @@ fn build_valid() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(transaction.is_ok()); } #[test] fn build_valid_with_payload() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -75,15 +74,15 @@ fn build_valid_with_payload() { .with_inputs([input1, input2]) .add_output(output) .with_payload(rand_tagged_data_payload()) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(transaction.is_ok()); } #[test] fn build_valid_add_inputs_outputs() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -99,15 +98,15 @@ fn build_valid_add_inputs_outputs() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(transaction.is_ok()); } #[test] fn build_invalid_payload_kind() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); // Construct a transaction with two inputs and one output. let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); @@ -123,8 +122,8 @@ fn build_invalid_payload_kind() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1.clone(), input2.clone()]) .add_output(output.clone()) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); // Construct a list of two unlocks, whereas we only have 1 tx input. @@ -141,15 +140,15 @@ fn build_invalid_payload_kind() { .with_inputs(vec![input1, input2]) .add_output(output) .with_payload(tx_payload) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); - assert!(matches!(transaction, Err(Error::InvalidPayloadKind(1)))); + assert!(matches!(transaction, Err(PayloadError::Kind(1)))); } #[test] fn build_invalid_input_count_low() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); let amount = 1_000_000; let output = Output::Basic( @@ -161,18 +160,18 @@ fn build_invalid_input_count_low() { let transaction = Transaction::builder(protocol_parameters.network_id()) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(matches!( transaction, - Err(Error::InvalidInputCount(TryIntoBoundedU16Error::Invalid(0))) + Err(PayloadError::InputCount(TryIntoBoundedU16Error::Invalid(0))) )); } #[test] fn build_invalid_input_count_high() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input = Input::Utxo(UtxoInput::new(transaction_id, 0)); let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); @@ -187,35 +186,35 @@ fn build_invalid_input_count_high() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs(vec![input; 129]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(matches!( transaction, - Err(Error::InvalidInputCount(TryIntoBoundedU16Error::Invalid(129))) + Err(PayloadError::InputCount(TryIntoBoundedU16Error::Invalid(129))) )); } #[test] fn build_invalid_output_count_low() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input = Input::Utxo(UtxoInput::new(transaction_id, 0)); let transaction = Transaction::builder(protocol_parameters.network_id()) .add_input(input) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(matches!( transaction, - Err(Error::InvalidOutputCount(TryIntoBoundedU16Error::Invalid(0))) + Err(PayloadError::OutputCount(TryIntoBoundedU16Error::Invalid(0))) )); } #[test] fn build_invalid_output_count_high() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input = Input::Utxo(UtxoInput::new(transaction_id, 0)); let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); @@ -230,18 +229,18 @@ fn build_invalid_output_count_high() { let transaction = Transaction::builder(protocol_parameters.network_id()) .add_input(input) .with_outputs(vec![output; 129]) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(matches!( transaction, - Err(Error::InvalidOutputCount(TryIntoBoundedU16Error::Invalid(129))) + Err(PayloadError::OutputCount(TryIntoBoundedU16Error::Invalid(129))) )); } #[test] fn build_invalid_duplicate_utxo() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input = Input::Utxo(UtxoInput::new(transaction_id, 0)); let address = Address::from(Ed25519Address::from_str(ED25519_ADDRESS_1).unwrap()); @@ -256,15 +255,15 @@ fn build_invalid_duplicate_utxo() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs(vec![input; 2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); - assert!(matches!(transaction, Err(Error::DuplicateUtxo(_)))); + assert!(matches!(transaction, Err(PayloadError::DuplicateUtxo(_)))); } #[test] fn build_invalid_accumulated_output() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input = Input::Utxo(UtxoInput::new(transaction_id, 0)); @@ -291,15 +290,15 @@ fn build_invalid_accumulated_output() { let transaction = Transaction::builder(protocol_parameters.network_id()) .add_input(input) .with_outputs([output1, output2]) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); - assert!(matches!(transaction, Err(Error::InvalidTransactionAmountSum(_)))); + assert!(matches!(transaction, Err(PayloadError::TransactionAmountSum(_)))); } #[test] fn getters() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -317,8 +316,8 @@ fn getters() { .with_inputs([input1, input2]) .with_outputs(outputs.clone()) .with_payload(payload.clone()) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); assert_eq!(transaction.outputs(), outputs.as_slice()); @@ -327,7 +326,7 @@ fn getters() { #[test] fn duplicate_output_nft() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -346,18 +345,18 @@ fn duplicate_output_nft() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .with_outputs([basic, nft.clone(), nft]) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(matches!( transaction, - Err(Error::DuplicateOutputChain(ChainId::Nft(nft_id_0))) if nft_id_0 == nft_id + Err(PayloadError::DuplicateOutputChain(ChainId::Nft(nft_id_0))) if nft_id_0 == nft_id )); } #[test] fn duplicate_output_nft_null() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -376,15 +375,15 @@ fn duplicate_output_nft_null() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .with_outputs([basic, nft.clone(), nft]) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(transaction.is_ok()); } #[test] fn duplicate_output_account() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -403,18 +402,18 @@ fn duplicate_output_account() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .with_outputs([basic, account.clone(), account]) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(matches!( transaction, - Err(Error::DuplicateOutputChain(ChainId::Account(account_id_0))) if account_id_0 == account_id + Err(PayloadError::DuplicateOutputChain(ChainId::Account(account_id_0))) if account_id_0 == account_id )); } #[test] fn duplicate_output_foundry() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -439,18 +438,18 @@ fn duplicate_output_foundry() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs([input1, input2]) .with_outputs([basic, foundry.clone(), foundry]) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters); + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters); assert!(matches!( transaction, - Err(Error::DuplicateOutputChain(ChainId::Foundry(foundry_id_0))) if foundry_id_0 == foundry_id + Err(PayloadError::DuplicateOutputChain(ChainId::Foundry(foundry_id_0))) if foundry_id_0 == foundry_id )); } #[test] fn transactions_capabilities() { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -465,8 +464,8 @@ fn transactions_capabilities() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs(vec![input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); let mut capabilities = transaction.capabilities().clone(); diff --git a/sdk/tests/types/transaction_id.rs b/sdk/tests/types/transaction_id.rs index a6b6190b64..87eb82784b 100644 --- a/sdk/tests/types/transaction_id.rs +++ b/sdk/tests/types/transaction_id.rs @@ -3,13 +3,7 @@ use core::str::FromStr; -use iota_sdk::types::{ - block::payload::{ - signed_transaction::{dto::SignedTransactionPayloadDto, SignedTransactionPayload, TransactionId}, - Payload, - }, - TryFromDto, -}; +use iota_sdk::types::block::payload::signed_transaction::TransactionId; use packable::PackableExt; use pretty_assertions::assert_eq; @@ -51,7 +45,7 @@ fn pack_unpack_valid() { assert_eq!( transaction_id, - PackableExt::unpack_verified(packed_transaction_id.as_slice(), &()).unwrap() + PackableExt::unpack_bytes_verified(packed_transaction_id.as_slice(), &()).unwrap() ); } diff --git a/sdk/tests/types/unlock/account.rs b/sdk/tests/types/unlock/account.rs index 083660c18a..47c9672175 100644 --- a/sdk/tests/types/unlock/account.rs +++ b/sdk/tests/types/unlock/account.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::block::{unlock::AccountUnlock, Error}; +use iota_sdk::types::block::unlock::{AccountUnlock, UnlockError}; use packable::{bounded::InvalidBoundedU16, PackableExt}; use pretty_assertions::assert_eq; @@ -24,7 +24,7 @@ fn new_valid_max_index() { fn new_invalid_more_than_max_index() { assert!(matches!( AccountUnlock::new(128), - Err(Error::InvalidAccountIndex(InvalidBoundedU16(128))) + Err(UnlockError::AccountIndex(InvalidBoundedU16(128))) )); } @@ -37,7 +37,7 @@ fn try_from_valid() { fn try_from_invalid() { assert!(matches!( AccountUnlock::try_from(128), - Err(Error::InvalidAccountIndex(InvalidBoundedU16(128))) + Err(UnlockError::AccountIndex(InvalidBoundedU16(128))) )); } @@ -52,7 +52,7 @@ fn packed_len() { #[test] fn pack_unpack_valid() { let unlock_1 = AccountUnlock::new(42).unwrap(); - let unlock_2 = AccountUnlock::unpack_verified(unlock_1.pack_to_vec().as_slice(), &()).unwrap(); + let unlock_2 = AccountUnlock::unpack_bytes_verified(unlock_1.pack_to_vec().as_slice(), &()).unwrap(); assert_eq!(unlock_1, unlock_2); } diff --git a/sdk/tests/types/unlock/mod.rs b/sdk/tests/types/unlock/mod.rs index cb9bde2c23..3f07a09896 100644 --- a/sdk/tests/types/unlock/mod.rs +++ b/sdk/tests/types/unlock/mod.rs @@ -8,8 +8,7 @@ mod signature; use iota_sdk::types::block::{ rand::signature::rand_signature, - unlock::{AccountUnlock, AnchorUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, Unlocks}, - Error, + unlock::{AccountUnlock, AnchorUnlock, NftUnlock, ReferenceUnlock, SignatureUnlock, Unlock, UnlockError, Unlocks}, }; use packable::bounded::TryIntoBoundedU16Error; use pretty_assertions::assert_eq; @@ -27,7 +26,7 @@ fn kind() { fn new_invalid_first_reference() { assert!(matches!( Unlocks::new([ReferenceUnlock::new(42).unwrap().into()]), - Err(Error::InvalidUnlockReference(0)), + Err(UnlockError::Reference(0)), )); } @@ -38,7 +37,7 @@ fn new_invalid_self_reference() { SignatureUnlock::from(rand_signature()).into(), ReferenceUnlock::new(1).unwrap().into() ]), - Err(Error::InvalidUnlockReference(1)), + Err(UnlockError::Reference(1)), )); } @@ -50,7 +49,7 @@ fn new_invalid_future_reference() { ReferenceUnlock::new(2).unwrap().into(), SignatureUnlock::from(rand_signature()).into(), ]), - Err(Error::InvalidUnlockReference(1)), + Err(UnlockError::Reference(1)), )); } @@ -62,7 +61,7 @@ fn new_invalid_reference_reference() { ReferenceUnlock::new(0).unwrap().into(), ReferenceUnlock::new(1).unwrap().into() ]), - Err(Error::InvalidUnlockReference(2)), + Err(UnlockError::Reference(2)), )); } @@ -80,7 +79,7 @@ fn new_invalid_duplicate_signature() { SignatureUnlock::from(rand_signature()).into(), ReferenceUnlock::new(3).unwrap().into() ]), - Err(Error::DuplicateSignatureUnlock(5)), + Err(UnlockError::DuplicateSignature(5)), )); } @@ -88,7 +87,7 @@ fn new_invalid_duplicate_signature() { fn new_invalid_too_many_blocks() { assert!(matches!( Unlocks::new(vec![ReferenceUnlock::new(0).unwrap().into(); 300]), - Err(Error::InvalidUnlockCount(TryIntoBoundedU16Error::Invalid(300))), + Err(UnlockError::Count(TryIntoBoundedU16Error::Invalid(300))), )); } @@ -151,7 +150,7 @@ fn invalid_account_0() { AccountUnlock::new(0).unwrap().into(), SignatureUnlock::from(rand_signature()).into(), ]), - Err(Error::InvalidUnlockAccount(0)), + Err(UnlockError::Account(0)), )); } @@ -162,7 +161,7 @@ fn invalid_account_index() { SignatureUnlock::from(rand_signature()).into(), AccountUnlock::new(2).unwrap().into(), ]), - Err(Error::InvalidUnlockAccount(1)), + Err(UnlockError::Account(1)), )); } @@ -173,7 +172,7 @@ fn invalid_nft_0() { NftUnlock::new(0).unwrap().into(), SignatureUnlock::from(rand_signature()).into(), ]), - Err(Error::InvalidUnlockNft(0)), + Err(UnlockError::Nft(0)), )); } @@ -184,6 +183,6 @@ fn invalid_nft_index() { SignatureUnlock::from(rand_signature()).into(), NftUnlock::new(2).unwrap().into(), ]), - Err(Error::InvalidUnlockNft(1)), + Err(UnlockError::Nft(1)), )); } diff --git a/sdk/tests/types/unlock/nft.rs b/sdk/tests/types/unlock/nft.rs index 70204ae4ca..1255cb405a 100644 --- a/sdk/tests/types/unlock/nft.rs +++ b/sdk/tests/types/unlock/nft.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::block::{unlock::NftUnlock, Error}; +use iota_sdk::types::block::unlock::{NftUnlock, UnlockError}; use packable::{bounded::InvalidBoundedU16, PackableExt}; use pretty_assertions::assert_eq; @@ -24,7 +24,7 @@ fn new_valid_max_index() { fn new_invalid_more_than_max_index() { assert!(matches!( NftUnlock::new(128), - Err(Error::InvalidNftIndex(InvalidBoundedU16(128))) + Err(UnlockError::NftIndex(InvalidBoundedU16(128))) )); } @@ -37,7 +37,7 @@ fn try_from_valid() { fn try_from_invalid() { assert!(matches!( NftUnlock::try_from(128), - Err(Error::InvalidNftIndex(InvalidBoundedU16(128))) + Err(UnlockError::NftIndex(InvalidBoundedU16(128))) )); } @@ -52,7 +52,7 @@ fn packed_len() { #[test] fn pack_unpack_valid() { let unlock_1 = NftUnlock::new(42).unwrap(); - let unlock_2 = NftUnlock::unpack_verified(unlock_1.pack_to_vec().as_slice(), &()).unwrap(); + let unlock_2 = NftUnlock::unpack_bytes_verified(unlock_1.pack_to_vec().as_slice(), &()).unwrap(); assert_eq!(unlock_1, unlock_2); } diff --git a/sdk/tests/types/unlock/reference.rs b/sdk/tests/types/unlock/reference.rs index 6938d60b12..3806e63250 100644 --- a/sdk/tests/types/unlock/reference.rs +++ b/sdk/tests/types/unlock/reference.rs @@ -1,7 +1,7 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::block::{unlock::ReferenceUnlock, Error}; +use iota_sdk::types::block::unlock::{ReferenceUnlock, UnlockError}; use packable::{bounded::InvalidBoundedU16, error::UnpackError, PackableExt}; use pretty_assertions::assert_eq; @@ -24,7 +24,7 @@ fn new_valid_max_index() { fn new_invalid_more_than_max_index() { assert!(matches!( ReferenceUnlock::new(128), - Err(Error::InvalidReferenceIndex(InvalidBoundedU16(128))) + Err(UnlockError::ReferenceIndex(InvalidBoundedU16(128))) )); } @@ -37,7 +37,7 @@ fn try_from_valid() { fn try_from_invalid() { assert!(matches!( ReferenceUnlock::try_from(128), - Err(Error::InvalidReferenceIndex(InvalidBoundedU16(128))) + Err(UnlockError::ReferenceIndex(InvalidBoundedU16(128))) )); } @@ -52,7 +52,7 @@ fn packed_len() { #[test] fn pack_unpack_valid() { let reference_1 = ReferenceUnlock::try_from(42).unwrap(); - let reference_2 = ReferenceUnlock::unpack_verified(reference_1.pack_to_vec().as_slice(), &()).unwrap(); + let reference_2 = ReferenceUnlock::unpack_bytes_verified(reference_1.pack_to_vec().as_slice(), &()).unwrap(); assert_eq!(reference_1, reference_2); } @@ -60,8 +60,8 @@ fn pack_unpack_valid() { #[test] fn pack_unpack_invalid_index() { assert!(matches!( - ReferenceUnlock::unpack_verified([0x2a, 0x2a], &()), - Err(UnpackError::Packable(Error::InvalidReferenceIndex(InvalidBoundedU16( + ReferenceUnlock::unpack_bytes_verified([0x2a, 0x2a], &()), + Err(UnpackError::Packable(UnlockError::ReferenceIndex(InvalidBoundedU16( 10794 )))) )); diff --git a/sdk/tests/types/unlock/signature.rs b/sdk/tests/types/unlock/signature.rs index 0833a13616..ccaa454ec4 100644 --- a/sdk/tests/types/unlock/signature.rs +++ b/sdk/tests/types/unlock/signature.rs @@ -1,7 +1,7 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::block::{rand::signature::rand_signature, unlock::SignatureUnlock, Error}; +use iota_sdk::types::block::{rand::signature::rand_signature, signature::SignatureError, unlock::SignatureUnlock}; use packable::{error::UnpackError, PackableExt}; use pretty_assertions::assert_eq; @@ -27,7 +27,7 @@ fn packed_len() { fn pack_unpack_valid_ed25519() { let signature_1 = SignatureUnlock::from(rand_signature()); let signature_bytes = signature_1.pack_to_vec(); - let signature_2 = SignatureUnlock::unpack_verified(signature_bytes.as_slice(), &()).unwrap(); + let signature_2 = SignatureUnlock::unpack_bytes_verified(signature_bytes.as_slice(), &()).unwrap(); assert_eq!(signature_bytes[0], 0); assert_eq!(signature_1, signature_2); @@ -36,7 +36,7 @@ fn pack_unpack_valid_ed25519() { #[test] fn pack_unpack_invalid_kind() { assert!(matches!( - SignatureUnlock::unpack_verified( + SignatureUnlock::unpack_bytes_verified( [ 1, 111, 225, 221, 28, 247, 253, 234, 110, 187, 52, 129, 153, 130, 84, 26, 7, 226, 27, 212, 145, 96, 151, 196, 124, 135, 176, 31, 48, 0, 213, 200, 82, 227, 169, 21, 179, 253, 115, 184, 209, 107, 138, 0, @@ -46,6 +46,6 @@ fn pack_unpack_invalid_kind() { ], &() ), - Err(UnpackError::Packable(Error::InvalidSignatureKind(1))) + Err(UnpackError::Packable(SignatureError::Kind(1))) )); } diff --git a/sdk/tests/wallet/address_generation.rs b/sdk/tests/wallet/address_generation.rs deleted file mode 100644 index eccd3aa430..0000000000 --- a/sdk/tests/wallet/address_generation.rs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "stronghold")] -use crypto::keys::bip39::Mnemonic; -use crypto::keys::bip44::Bip44; -#[cfg(feature = "stronghold")] -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -#[cfg(feature = "ledger_nano")] -use iota_sdk::client::secret::{ledger_nano::LedgerSecretManager, GenerateAddressOptions}; -#[cfg(feature = "events")] -use iota_sdk::wallet::events::{WalletEvent, WalletEventType}; -use iota_sdk::{ - client::{ - constants::IOTA_COIN_TYPE, - secret::{mnemonic::MnemonicSecretManager, SecretManager}, - Error as ClientError, - }, - types::block::address::ToBech32Ext, - wallet::{ClientOptions, Error, Result, Wallet}, -}; -use pretty_assertions::assert_eq; - -use crate::wallet::common::{setup, tear_down, DEFAULT_MNEMONIC, NODE_LOCAL}; - -#[tokio::test] -async fn wallet_address_generation_mnemonic() -> Result<()> { - let storage_path = "test-storage/wallet_address_generation_mnemonic"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - let secret_manager = MnemonicSecretManager::try_from_mnemonic(DEFAULT_MNEMONIC.to_owned())?; - - #[allow(unused_mut)] - let mut wallet_builder = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) - .with_client_options(client_options) - .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); - - #[cfg(feature = "storage")] - { - wallet_builder = wallet_builder.with_storage_path(storage_path); - } - let wallet = wallet_builder.finish().await?; - - let address = wallet.generate_ed25519_address(0, 0, None).await?; - - assert_eq!( - address.to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" - ); - - tear_down(storage_path) -} - -#[cfg(feature = "stronghold")] -#[tokio::test] -async fn wallet_address_generation_stronghold() -> Result<()> { - let storage_path = "test-storage/wallet_address_generation_stronghold"; - setup(storage_path)?; - - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let secret_manager = StrongholdSecretManager::builder() - .password("some_hopefully_secure_password".to_owned()) - .build("test-storage/wallet_address_generation_stronghold/test.stronghold")?; - secret_manager - .store_mnemonic(Mnemonic::from(DEFAULT_MNEMONIC.to_string())) - .await?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - #[allow(unused_mut)] - let mut wallet_builder = Wallet::builder() - .with_secret_manager(SecretManager::Stronghold(secret_manager)) - .with_client_options(client_options) - .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); - #[cfg(feature = "storage")] - { - wallet_builder = wallet_builder.with_storage_path(storage_path); - } - let wallet = wallet_builder.finish().await?; - - let address = wallet.generate_ed25519_address(0, 0, None).await?; - - assert_eq!( - address.to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" - ); - - tear_down(storage_path) -} - -#[tokio::test] -#[cfg(all(feature = "ledger_nano", feature = "events"))] -#[ignore = "requires ledger nano instance"] -async fn wallet_address_generation_ledger() -> Result<()> { - let storage_path = "test-storage/wallet_address_generation_ledger"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - let mut secret_manager = LedgerSecretManager::new(true); - secret_manager.non_interactive = true; - - #[allow(unused_mut)] - let mut wallet_builder = Wallet::builder() - .with_secret_manager(SecretManager::LedgerNano(secret_manager)) - .with_client_options(client_options) - .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); - - #[cfg(feature = "storage")] - { - wallet_builder = wallet_builder.with_storage_path(storage_path); - } - let wallet = wallet_builder.finish().await?; - - let address = wallet.generate_ed25519_address(0, 0, None).await?; - - assert_eq!( - address.to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - // This address was generated with a MnemonicSecretManager and the ledger simulator mnemonic. - // "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster - // seven myth punch hobby comfort wild raise skin". - "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" - ); - - let (sender, mut receiver) = tokio::sync::mpsc::channel(1); - - wallet - .listen([WalletEventType::LedgerAddressGeneration], move |event| { - if let WalletEvent::LedgerAddressGeneration(address) = event { - sender - .try_send(address.address.clone()) - .expect("too many LedgerAddressGeneration events"); - } else { - panic!("expected LedgerAddressGeneration event") - } - }) - .await; - - let address = wallet - .generate_ed25519_address( - 0, - 0, - Some(GenerateAddressOptions { - ledger_nano_prompt: true, - ..Default::default() - }), - ) - .await?; - - assert_eq!( - address.to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - // This address was generated with a MnemonicSecretManager and the ledger simulator mnemonic. - // "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster - // seven myth punch hobby comfort wild raise skin". - "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" - ); - - assert_eq!( - receiver - .recv() - .await - .expect("never received event") - .into_inner() - .to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - // This address was generated with a MnemonicSecretManager and the ledger simulator mnemonic. - // "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster - // seven myth punch hobby comfort wild raise skin". - "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" - ); - - tear_down(storage_path) -} - -// #[tokio::test] -// async fn wallet_address_generation_placeholder() -> Result<()> { -// let storage_path = "test-storage/wallet_address_generation_placeholder"; -// setup(storage_path)?; - -// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - -// #[allow(unused_mut)] -// let mut wallet_builder = Wallet::builder() -// .with_secret_manager(SecretManager::Placeholder) -// .with_client_options(client_options) -// .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); - -// #[cfg(feature = "storage")] -// { -// wallet_builder = wallet_builder.with_storage_path(storage_path); -// } -// let wallet = wallet_builder.finish().await?; - -// if let Err(Error::Client(error)) = wallet.generate_ed25519_address(0, 0, None).await { -// assert!(matches!(*error, ClientError::PlaceholderSecretManager)) -// } else { -// panic!("expected PlaceholderSecretManager") -// } - -// tear_down(storage_path) -// } diff --git a/sdk/tests/wallet/backup_restore.rs b/sdk/tests/wallet/backup_restore.rs index 7fd0ceec97..779c7a41d2 100644 --- a/sdk/tests/wallet/backup_restore.rs +++ b/sdk/tests/wallet/backup_restore.rs @@ -21,7 +21,7 @@ // // Backup and restore with Stronghold // #[tokio::test] -// async fn backup_and_restore() -> Result<()> { +// async fn backup_and_restore() -> Result<(), WalletError> { // iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); // let storage_path = "test-storage/backup_and_restore"; @@ -98,7 +98,7 @@ // let node_dto = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); // assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); -// assert_eq!(wallet.address().await, restored_wallet.address().await); +// assert_eq!(wallet.address().clone(), restored_wallet.address().clone()); // // secret manager is the same // assert_eq!( @@ -128,7 +128,7 @@ // // Backup and restore with Stronghold and MnemonicSecretManager // #[tokio::test] -// async fn backup_and_restore_mnemonic_secret_manager() -> Result<()> { +// async fn backup_and_restore_mnemonic_secret_manager() -> Result<(), WalletError> { // iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); // let storage_path = "test-storage/backup_and_restore_mnemonic_secret_manager"; @@ -196,7 +196,7 @@ // // Get wallet // let recovered_wallet = restore_wallet; -// assert_eq!(wallet.address().await, recovered_wallet.address().await); +// assert_eq!(wallet.address().clone(), recovered_wallet.address().clone()); // // secret manager is the same // assert_eq!( @@ -208,7 +208,7 @@ // // Backup and restore with Stronghold // #[tokio::test] -// async fn backup_and_restore_different_coin_type() -> Result<()> { +// async fn backup_and_restore_different_coin_type() -> Result<(), WalletError> { // iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); // let storage_path = "test-storage/backup_and_restore_different_coin_type"; @@ -269,14 +269,14 @@ // // Validate restored data // // No wallet restored, because the coin type was different -// assert!(restore_wallet.get_wallet_data().await?.is_empty()); +// assert!(restore_wallet.get_wallet_ledger().await?.is_empty()); // // Restored coin type is not used and it's still the same one // let new_wallet = restore_wallet; // assert_eq!(new_wallet.data().await.coin_type(), &IOTA_COIN_TYPE); // // secret manager is the same // assert_eq!( -// new_wallet.address().await, +// new_wallet.address().clone(), // "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" // ); @@ -290,7 +290,7 @@ // // Backup and restore with Stronghold // #[tokio::test] -// async fn backup_and_restore_same_coin_type() -> Result<()> { +// async fn backup_and_restore_same_coin_type() -> Result<(), WalletError> { // iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); // let storage_path = "test-storage/backup_and_restore_same_coin_type"; @@ -352,13 +352,13 @@ // // Validate restored data // // The wallet is restored, because the coin type is the same -// let restored_wallet = restore_wallet.get_wallet_data().await?; +// let restored_wallet = restore_wallet.get_wallet_ledger().await?; // assert!(restored_wallet.is_some()); // // addresses are still there // assert_eq!( -// restored_wallet.address().await, -// wallet_before_backup.address().await +// restored_wallet.address().clone(), +// wallet_before_backup.address().clone() // ); // // compare client options, they are not restored @@ -371,7 +371,7 @@ // // Backup and restore with Stronghold // #[tokio::test] -// async fn backup_and_restore_different_coin_type_dont_ignore() -> Result<()> { +// async fn backup_and_restore_different_coin_type_dont_ignore() -> Result<(), WalletError> { // iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); // let storage_path = "test-storage/backup_and_restore_different_coin_type_dont_ignore"; @@ -431,10 +431,10 @@ // // Validate restored data // // No wallet restored, because the coin type was different -// let restored_wallet = restore_wallet.get_wallet_data().await?; +// let restored_wallet = restore_wallet.get_wallet_ledger().await?; // assert_eq!( -// wallet.address().await, -// restored_wallet.address().await, +// wallet.address().clone(), +// restored_wallet.address().clone(), // ); // // TODO: Restored coin type is used @@ -442,7 +442,7 @@ // assert_eq!(new_wallet.data().await.coin_type(), &SHIMMER_COIN_TYPE); // // secret manager is restored // assert_eq!( -// new_wallet.address().await, +// new_wallet.address().clone(), // "smr1qzvjvjyqxgfx4f0m3xhn2rj24e03dwsmjz082735y3wx88v2gudu2afedhu" // ); @@ -455,7 +455,7 @@ // } // #[tokio::test] -// async fn backup_and_restore_bech32_hrp_mismatch() -> Result<()> { +// async fn backup_and_restore_bech32_hrp_mismatch() -> Result<(), WalletError> { // iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); // let storage_path = "test-storage/backup_and_restore_bech32_hrp_mismatch"; @@ -520,7 +520,7 @@ // assert!(client_options.node_manager_builder.nodes.contains(&node_dto)); // // No restored wallet because the bech32 hrp was different -// let restored_wallet = restore_wallet.get_wallet_data().await?; +// let restored_wallet = restore_wallet.get_wallet_ledger().await?; // assert!(restored_wallet.is_empty()); // // Restored coin type is used @@ -537,7 +537,7 @@ // // Restore a Stronghold snapshot without secret manager data // #[tokio::test] -// async fn restore_no_secret_manager_data() -> Result<()> { +// async fn restore_no_secret_manager_data() -> Result<(), WalletError> { // iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); // let storage_path = "test-storage/restore_no_secret_manager_data"; diff --git a/sdk/tests/wallet/balance.rs b/sdk/tests/wallet/balance.rs index 8e10ed9773..5a28905c90 100644 --- a/sdk/tests/wallet/balance.rs +++ b/sdk/tests/wallet/balance.rs @@ -7,12 +7,13 @@ use iota_sdk::{ unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, BasicOutputBuilder, UnlockCondition, }, - wallet::{types::Balance, Result}, + wallet::types::Balance, }; use pretty_assertions::assert_eq; use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; +#[cfg(all(feature = "rand", feature = "protocol_parameters_samples"))] #[test] fn rand_balance_add_assign() { use iota_sdk::U256; @@ -126,7 +127,7 @@ fn rand_balance_add_assign() { #[ignore] #[tokio::test] -async fn balance_expiration() -> Result<()> { +async fn balance_expiration() -> Result<(), Box> { let storage_path_0 = "test-storage/balance_expiration_0"; let storage_path_1 = "test-storage/balance_expiration_1"; let storage_path_2 = "test-storage/balance_expiration_2"; @@ -210,7 +211,7 @@ async fn balance_expiration() -> Result<()> { #[ignore] #[tokio::test] -async fn balance_transfer() -> Result<()> { +async fn balance_transfer() -> Result<(), Box> { let storage_path_0 = "test-storage/addresses_balance_0"; let storage_path_1 = "test-storage/addresses_balance_1"; setup(storage_path_0)?; @@ -259,7 +260,7 @@ async fn balance_transfer() -> Result<()> { // #[ignore] // #[tokio::test] // #[cfg(feature = "participation")] -// async fn balance_voting_power() -> Result<()> { +// async fn balance_voting_power() -> Result<(), WalletError> { // let storage_path = "test-storage/balance_voting_power"; // setup(storage_path)?; diff --git a/sdk/tests/wallet/bech32_hrp_validation.rs b/sdk/tests/wallet/bech32_hrp_validation.rs index 5271a0bbb3..91833186cb 100644 --- a/sdk/tests/wallet/bech32_hrp_validation.rs +++ b/sdk/tests/wallet/bech32_hrp_validation.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::{ - client::Error as ClientError, + client::ClientError, types::block::address::{Bech32Address, ToBech32Ext}, - wallet::{Error, OutputParams, Result, SendParams}, + wallet::{OutputParams, SendParams, WalletError}, }; use pretty_assertions::assert_eq; @@ -12,7 +12,7 @@ use crate::wallet::common::{make_wallet, setup, tear_down}; #[ignore] #[tokio::test] -async fn bech32_hrp_send_amount() -> Result<()> { +async fn bech32_hrp_send_amount() -> Result<(), Box> { let storage_path = "test-storage/bech32_hrp_send_amount"; setup(storage_path)?; @@ -32,7 +32,7 @@ async fn bech32_hrp_send_amount() -> Result<()> { let bech32_hrp = wallet.client().get_bech32_hrp().await?; match error { - Error::Client(error) => match *error { + WalletError::Client(error) => match error { ClientError::Bech32HrpMismatch { provided, expected } => { assert_eq!(provided, "wronghrp"); assert_eq!(expected, bech32_hrp.to_string()); @@ -47,7 +47,7 @@ async fn bech32_hrp_send_amount() -> Result<()> { #[ignore] #[tokio::test] -async fn bech32_hrp_prepare_output() -> Result<()> { +async fn bech32_hrp_prepare_output() -> Result<(), Box> { let storage_path = "test-storage/bech32_hrp_prepare_output"; setup(storage_path)?; @@ -71,7 +71,7 @@ async fn bech32_hrp_prepare_output() -> Result<()> { let bech32_hrp = wallet.client().get_bech32_hrp().await?; match error { - Error::Client(error) => match *error { + WalletError::Client(error) => match error { ClientError::Bech32HrpMismatch { provided, expected } => { assert_eq!(provided, "wronghrp"); assert_eq!(expected, bech32_hrp.to_string()); diff --git a/sdk/tests/wallet/burn_outputs.rs b/sdk/tests/wallet/burn_outputs.rs index 4676de5a62..7d7dd7209b 100644 --- a/sdk/tests/wallet/burn_outputs.rs +++ b/sdk/tests/wallet/burn_outputs.rs @@ -2,13 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::{ - client::api::input_selection::Burn, + client::api::transaction_builder::Burn, types::block::output::{ feature::MetadataFeature, unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, NativeToken, NftId, NftOutputBuilder, OutputId, UnlockCondition, }, - wallet::{CreateNativeTokenParams, MintNftParams, Result, Wallet}, + wallet::{CreateNativeTokenParams, MintNftParams, Wallet}, U256, }; use pretty_assertions::assert_eq; @@ -17,7 +17,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] -async fn mint_and_burn_nft() -> Result<()> { +async fn mint_and_burn_nft() -> Result<(), Box> { let storage_path = "test-storage/mint_and_burn_outputs"; setup(storage_path)?; @@ -59,7 +59,7 @@ async fn mint_and_burn_nft() -> Result<()> { #[ignore] #[tokio::test] -async fn mint_and_burn_expired_nft() -> Result<()> { +async fn mint_and_burn_expired_nft() -> Result<(), Box> { let storage_path = "test-storage/mint_and_burn_expired_nft"; setup(storage_path)?; @@ -98,7 +98,7 @@ async fn mint_and_burn_expired_nft() -> Result<()> { #[ignore] #[tokio::test] -async fn create_and_melt_native_token() -> Result<()> { +async fn create_and_melt_native_token() -> Result<(), Box> { let storage_path = "test-storage/create_and_melt_native_token"; setup(storage_path)?; @@ -108,7 +108,6 @@ async fn create_and_melt_native_token() -> Result<()> { // First create an account output, this needs to be done only once, because an account can have many foundry outputs let transaction = wallet.create_account_output(None, None).await?; - // Wait for transaction to get accepted wallet .wait_for_transaction_acceptance(&transaction.transaction_id, None, None) .await?; @@ -178,7 +177,7 @@ async fn create_and_melt_native_token() -> Result<()> { tear_down(storage_path) } -async fn destroy_foundry(wallet: &Wallet) -> Result<()> { +async fn destroy_foundry(wallet: &Wallet) -> Result<(), Box> { let balance = wallet.sync(None).await?; println!("wallet balance -> {}", serde_json::to_string(&balance).unwrap()); @@ -201,7 +200,7 @@ async fn destroy_foundry(wallet: &Wallet) -> Result<()> { Ok(()) } -async fn destroy_account(wallet: &Wallet) -> Result<()> { +async fn destroy_account(wallet: &Wallet) -> Result<(), Box> { let balance = wallet.sync(None).await.unwrap(); println!("account balance -> {}", serde_json::to_string(&balance).unwrap()); @@ -225,7 +224,7 @@ async fn destroy_account(wallet: &Wallet) -> Result<()> { #[ignore] #[tokio::test] -async fn create_and_burn_native_tokens() -> Result<()> { +async fn create_and_burn_native_tokens() -> Result<(), Box> { let storage_path = "test-storage/create_and_burn_native_tokens"; setup(storage_path)?; @@ -272,7 +271,7 @@ async fn create_and_burn_native_tokens() -> Result<()> { #[ignore] #[tokio::test] -async fn mint_and_burn_nft_with_account() -> Result<()> { +async fn mint_and_burn_nft_with_account() -> Result<(), Box> { let storage_path = "test-storage/mint_and_burn_nft_with_account"; setup(storage_path)?; diff --git a/sdk/tests/wallet/claim_outputs.rs b/sdk/tests/wallet/claim_outputs.rs index 946db76f53..b30cad0626 100644 --- a/sdk/tests/wallet/claim_outputs.rs +++ b/sdk/tests/wallet/claim_outputs.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::{ + client::api::options::TransactionOptions, types::block::output::{ unlock_condition::{AddressUnlockCondition, ExpirationUnlockCondition}, BasicOutputBuilder, NativeToken, NftId, NftOutputBuilder, UnlockCondition, }, - wallet::{CreateNativeTokenParams, OutputsToClaim, Result, SendNativeTokenParams, SendParams, TransactionOptions}, + wallet::{CreateNativeTokenParams, OutputsToClaim, SendNativeTokenParams, SendParams}, U256, }; use pretty_assertions::assert_eq; @@ -15,7 +16,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] -async fn claim_2_basic_micro_outputs() -> Result<()> { +async fn claim_2_basic_micro_outputs() -> Result<(), Box> { let storage_path_0 = "test-storage/claim_2_basic_micro_outputs_0"; let storage_path_1 = "test-storage/claim_2_basic_micro_outputs_1"; setup(storage_path_0)?; @@ -72,7 +73,7 @@ async fn claim_2_basic_micro_outputs() -> Result<()> { #[ignore] #[tokio::test] -async fn claim_1_of_2_basic_outputs() -> Result<()> { +async fn claim_1_of_2_basic_outputs() -> Result<(), Box> { let storage_path_0 = "test-storage/claim_1_of_2_basic_outputs_0"; let storage_path_1 = "test-storage/claim_1_of_2_basic_outputs_1"; setup(storage_path_0)?; @@ -129,7 +130,7 @@ async fn claim_1_of_2_basic_outputs() -> Result<()> { #[ignore] #[tokio::test] -async fn claim_2_basic_outputs_no_outputs_in_claim_account() -> Result<()> { +async fn claim_2_basic_outputs_no_outputs_in_claim_account() -> Result<(), Box> { let storage_path_0 = "test-storage/claim_2_basic_outputs_no_outputs_in_claim_account_0"; let storage_path_1 = "test-storage/claim_2_basic_outputs_no_outputs_in_claim_account_1"; setup(storage_path_0)?; @@ -185,7 +186,7 @@ async fn claim_2_basic_outputs_no_outputs_in_claim_account() -> Result<()> { #[ignore] #[tokio::test] -async fn claim_2_native_tokens() -> Result<()> { +async fn claim_2_native_tokens() -> Result<(), Box> { let storage_path_0 = "test-storage/claim_2_native_tokens_0"; let storage_path_1 = "test-storage/claim_2_native_tokens_1"; setup(storage_path_0)?; @@ -277,7 +278,7 @@ async fn claim_2_native_tokens() -> Result<()> { #[ignore] #[tokio::test] -async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<()> { +async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<(), Box> { let storage_path_0 = "test-storage/claim_2_native_tokens_no_outputs_in_claim_account_0"; let storage_path_1 = "test-storage/claim_2_native_tokens_no_outputs_in_claim_account_1"; setup(storage_path_0)?; @@ -384,7 +385,7 @@ async fn claim_2_native_tokens_no_outputs_in_claim_account() -> Result<()> { #[ignore] #[tokio::test] -async fn claim_2_nft_outputs() -> Result<()> { +async fn claim_2_nft_outputs() -> Result<(), Box> { let storage_path_0 = "test-storage/claim_2_nft_outputs_0"; let storage_path_1 = "test-storage/claim_2_nft_outputs_1"; setup(storage_path_0)?; @@ -446,7 +447,7 @@ async fn claim_2_nft_outputs() -> Result<()> { #[ignore] #[tokio::test] -async fn claim_2_nft_outputs_no_outputs_in_claim_account() -> Result<()> { +async fn claim_2_nft_outputs_no_outputs_in_claim_account() -> Result<(), Box> { let storage_path_0 = "test-storage/claim_2_nft_outputs_no_outputs_in_claim_wallet_0"; let storage_path_1 = "test-storage/claim_2_nft_outputs_no_outputs_in_claim_wallet_1"; setup(storage_path_0)?; @@ -507,7 +508,7 @@ async fn claim_2_nft_outputs_no_outputs_in_claim_account() -> Result<()> { #[ignore] #[tokio::test] -async fn claim_basic_micro_output_error() -> Result<()> { +async fn claim_basic_micro_output_error() -> Result<(), Box> { let storage_path_0 = "test-storage/claim_basic_micro_output_error_0"; let storage_path_1 = "test-storage/claim_basic_micro_output_error_1"; setup(storage_path_0)?; @@ -540,7 +541,10 @@ async fn claim_basic_micro_output_error() -> Result<()> { let result = wallet_1 .claim_outputs(wallet_1.claimable_outputs(OutputsToClaim::MicroTransactions).await?) .await; - assert!(matches!(result, Err(iota_sdk::wallet::Error::InsufficientFunds { .. }))); + assert!(matches!( + result, + Err(iota_sdk::wallet::WalletError::InsufficientFunds { .. }) + )); tear_down(storage_path_0)?; tear_down(storage_path_1)?; diff --git a/sdk/tests/wallet/common/mod.rs b/sdk/tests/wallet/common/mod.rs index e73a4b2174..045ca06ce3 100644 --- a/sdk/tests/wallet/common/mod.rs +++ b/sdk/tests/wallet/common/mod.rs @@ -11,10 +11,11 @@ use iota_sdk::{ secret::{mnemonic::MnemonicSecretManager, SecretManager}, Client, }, - wallet::{ClientOptions, Result, Wallet}, + types::block::protocol::iota_mainnet_protocol_parameters, + wallet::{ClientOptions, Wallet}, }; -pub use self::constants::*; +pub use self::constants::{DEFAULT_MNEMONIC, FAUCET_URL, NODE_LOCAL, NODE_OTHER}; /// It creates a new wallet with a mnemonic secret manager, a client options object, /// SHIMMER_COIN_TYPE, and a storage path @@ -29,10 +30,17 @@ pub use self::constants::*; /// /// A Wallet #[allow(dead_code, unused_variables)] -pub(crate) async fn make_wallet(storage_path: &str, mnemonic: Option, node: Option<&str>) -> Result { - let client_options = ClientOptions::new().with_node(node.unwrap_or(NODE_LOCAL))?; - let secret_manager = - MnemonicSecretManager::try_from_mnemonic(mnemonic.unwrap_or_else(|| Client::generate_mnemonic().unwrap()))?; +pub(crate) async fn make_wallet( + storage_path: &str, + mnemonic: impl Into>, + node: Option<&str>, +) -> Result> { + let client_options = ClientOptions::new() + .with_node(node.unwrap_or(NODE_LOCAL))? + .with_protocol_parameters(iota_mainnet_protocol_parameters().clone()); + let secret_manager = MnemonicSecretManager::try_from_mnemonic( + mnemonic.into().unwrap_or_else(|| Client::generate_mnemonic().unwrap()), + )?; #[allow(unused_mut)] let mut wallet_builder = Wallet::builder() @@ -45,12 +53,15 @@ pub(crate) async fn make_wallet(storage_path: &str, mnemonic: Option, wallet_builder = wallet_builder.with_storage_path(storage_path); } - wallet_builder.finish().await + Ok(wallet_builder.finish().await?) } #[allow(dead_code, unused_variables)] #[cfg(feature = "ledger_nano")] -pub(crate) async fn make_ledger_nano_wallet(storage_path: &str, node: Option<&str>) -> Result { +pub(crate) async fn make_ledger_nano_wallet( + storage_path: &str, + node: Option<&str>, +) -> Result> { let client_options = ClientOptions::new().with_node(node.unwrap_or(NODE_LOCAL))?; let mut secret_manager = iota_sdk::client::secret::ledger_nano::LedgerSecretManager::new(true); secret_manager.non_interactive = true; @@ -65,12 +76,12 @@ pub(crate) async fn make_ledger_nano_wallet(storage_path: &str, node: Option<&st wallet_builder = wallet_builder.with_storage_path(storage_path); } - wallet_builder.finish().await + Ok(wallet_builder.finish().await?) } /// Request funds from the faucet and sync the wallet. #[allow(dead_code)] -pub(crate) async fn request_funds(wallet: &Wallet) -> Result<()> { +pub(crate) async fn request_funds(wallet: &Wallet) -> Result<(), Box> { request_funds_from_faucet(FAUCET_URL, &wallet.address().await).await?; // Continue only after funds are received @@ -85,13 +96,13 @@ pub(crate) async fn request_funds(wallet: &Wallet) -> Result<()> { } #[allow(dead_code)] -pub(crate) fn setup(path: &str) -> Result<()> { +pub(crate) fn setup(path: &str) -> Result<(), Box> { std::fs::remove_dir_all(path).ok(); Ok(()) } #[allow(dead_code)] -pub(crate) fn tear_down(path: &str) -> Result<()> { +pub(crate) fn tear_down(path: &str) -> Result<(), Box> { std::fs::remove_dir_all(path).ok(); Ok(()) } diff --git a/sdk/tests/wallet/consolidation.rs b/sdk/tests/wallet/consolidation.rs index cb0341dcd6..87cb3d3d67 100644 --- a/sdk/tests/wallet/consolidation.rs +++ b/sdk/tests/wallet/consolidation.rs @@ -1,14 +1,14 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::wallet::{ConsolidationParams, Result, SendParams}; +use iota_sdk::wallet::{ConsolidationParams, SendParams}; use pretty_assertions::assert_eq; use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] -async fn consolidation() -> Result<()> { +async fn consolidation() -> Result<(), Box> { let storage_path_0 = "test-storage/consolidation_0"; let storage_path_1 = "test-storage/consolidation_1"; setup(storage_path_0)?; @@ -31,7 +31,7 @@ async fn consolidation() -> Result<()> { let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.base_coin().available(), 10 * amount); - assert_eq!(wallet_1.data().await.unspent_outputs().len(), 10); + assert_eq!(wallet_1.ledger().await.unspent_outputs().len(), 10); let tx = wallet_1 .consolidate_outputs(ConsolidationParams::new().with_force(true)) @@ -44,7 +44,7 @@ async fn consolidation() -> Result<()> { // Balance still the same assert_eq!(balance.base_coin().available(), 10 * amount); // Only one unspent output - assert_eq!(wallet_1.data().await.unspent_outputs().len(), 1); + assert_eq!(wallet_1.ledger().await.unspent_outputs().len(), 1); tear_down(storage_path_0)?; tear_down(storage_path_1)?; diff --git a/sdk/tests/wallet/core.rs b/sdk/tests/wallet/core.rs index 8723afe641..3b05f8ef27 100644 --- a/sdk/tests/wallet/core.rs +++ b/sdk/tests/wallet/core.rs @@ -6,7 +6,7 @@ use crypto::keys::bip39::Mnemonic; use iota_sdk::{ client::constants::SHIMMER_COIN_TYPE, client::node_manager::node::{Node, NodeDto}, - wallet::Error, + wallet::WalletError, }; use iota_sdk::{ client::{ @@ -14,10 +14,11 @@ use iota_sdk::{ secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, crypto::keys::bip44::Bip44, - types::block::address::Bech32Address, - wallet::{ClientOptions, Result, Wallet}, + types::block::{address::Bech32Address, protocol::iota_mainnet_protocol_parameters}, + wallet::{ClientOptions, Wallet}, }; use pretty_assertions::assert_eq; +#[cfg(feature = "storage")] use url::Url; #[cfg(feature = "storage")] @@ -26,11 +27,12 @@ use crate::wallet::common::{make_wallet, setup, tear_down, DEFAULT_MNEMONIC, NOD #[cfg(feature = "storage")] #[tokio::test] -async fn update_client_options() -> Result<()> { +async fn update_client_options() -> Result<(), Box> { let storage_path = "test-storage/update_client_options"; setup(storage_path)?; - let wallet = make_wallet(storage_path, None, Some(NODE_OTHER)).await?; + let mnemonic = Mnemonic::from(DEFAULT_MNEMONIC.to_owned()); + let wallet = make_wallet(storage_path, mnemonic.clone(), Some(NODE_OTHER)).await?; let node_dto_old = NodeDto::Node(Node::from(Url::parse(NODE_OTHER).unwrap())); let node_dto_new = NodeDto::Node(Node::from(Url::parse(NODE_LOCAL).unwrap())); @@ -49,7 +51,7 @@ async fn update_client_options() -> Result<()> { // The client options are also updated in the database and available the next time drop(wallet); - let wallet = make_wallet(storage_path, None, None).await?; + let wallet = make_wallet(storage_path, mnemonic, None).await?; let client_options = wallet.client_options().await; assert!(client_options.node_manager_builder.nodes.contains(&node_dto_new)); assert!(!client_options.node_manager_builder.nodes.contains(&node_dto_old)); @@ -59,7 +61,7 @@ async fn update_client_options() -> Result<()> { // #[cfg(feature = "storage")] // #[tokio::test] -// async fn different_seed() -> Result<()> { +// async fn different_seed() -> Result<(), Box> { // let storage_path = "test-storage/different_seed"; // setup(storage_path)?; @@ -76,19 +78,18 @@ async fn update_client_options() -> Result<()> { #[cfg(feature = "storage")] #[tokio::test] -async fn changed_bip_path() -> Result<()> { +async fn changed_bip_path() -> Result<(), Box> { use iota_sdk::crypto::keys::bip44::Bip44; let storage_path = "test-storage/changed_coin_type"; setup(storage_path)?; let mnemonic = Mnemonic::from(DEFAULT_MNEMONIC.to_owned()); - - let wallet = make_wallet(storage_path, Some(mnemonic.clone()), None).await?; + let wallet = make_wallet(storage_path, mnemonic.clone(), None).await?; drop(wallet); - let err = Wallet::builder() + let result = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( mnemonic.clone(), )?)) @@ -99,11 +100,10 @@ async fn changed_bip_path() -> Result<()> { // Building the wallet with another coin type needs to return an error, because a different coin type was used in // the existing account - let mismatch_err: Result = Err(Error::BipPathMismatch { - new_bip_path: Some(Bip44::new(IOTA_COIN_TYPE)), - old_bip_path: Some(Bip44::new(SHIMMER_COIN_TYPE)), - }); - assert!(matches!(err, mismatch_err)); + assert!(matches!(result, Err(WalletError::BipPathMismatch { + new_bip_path: Some(new_bip_path), + old_bip_path: Some(old_bip_path), + }) if new_bip_path == Bip44::new(IOTA_COIN_TYPE) && old_bip_path == Bip44::new(SHIMMER_COIN_TYPE))); // Building the wallet with the same coin type still works assert!( @@ -121,7 +121,7 @@ async fn changed_bip_path() -> Result<()> { } #[tokio::test] -async fn shimmer_coin_type() -> Result<()> { +async fn shimmer_coin_type() -> Result<(), Box> { let storage_path = "test-storage/shimmer_coin_type"; setup(storage_path)?; @@ -138,11 +138,13 @@ async fn shimmer_coin_type() -> Result<()> { } #[tokio::test] -async fn iota_coin_type() -> Result<()> { +async fn iota_coin_type() -> Result<(), Box> { let storage_path = "test-storage/iota_coin_type"; setup(storage_path)?; - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; + let client_options = ClientOptions::new() + .with_node(NODE_LOCAL)? + .with_protocol_parameters(iota_mainnet_protocol_parameters().clone()); let secret_manager = MnemonicSecretManager::try_from_mnemonic(DEFAULT_MNEMONIC.to_owned())?; #[allow(unused_mut)] @@ -169,7 +171,7 @@ async fn iota_coin_type() -> Result<()> { #[cfg(feature = "storage")] #[tokio::test] -async fn update_node_auth() -> Result<()> { +async fn update_node_auth() -> Result<(), Box> { let storage_path = "test-storage/update_node_auth"; setup(storage_path)?; diff --git a/sdk/tests/wallet/error.rs b/sdk/tests/wallet/error.rs index 037ab03582..f81b088a87 100644 --- a/sdk/tests/wallet/error.rs +++ b/sdk/tests/wallet/error.rs @@ -1,27 +1,27 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::wallet::Error; +use iota_sdk::wallet::WalletError; use pretty_assertions::assert_eq; #[test] fn stringified_error() { // testing a unit-type-like error - let error = Error::MissingBipPath; + let error = WalletError::MissingBipPath; assert_eq!( &serde_json::to_string(&error).unwrap(), "{\"type\":\"missingBipPath\",\"error\":\"missing BIP path\"}" ); // testing a tuple-like error - let error = Error::InvalidMnemonic("nilly willy".to_string()); + let error = WalletError::InvalidMnemonic("nilly willy".to_string()); assert_eq!( serde_json::to_string(&error).unwrap(), "{\"type\":\"invalidMnemonic\",\"error\":\"invalid mnemonic: nilly willy\"}" ); // testing a struct-like error - let error = Error::NoOutputsToConsolidate { + let error = WalletError::NoOutputsToConsolidate { available_outputs: 0, consolidation_threshold: 0, }; diff --git a/sdk/tests/wallet/events.rs b/sdk/tests/wallet/events.rs index 30ddb04ee7..34219b52d0 100644 --- a/sdk/tests/wallet/events.rs +++ b/sdk/tests/wallet/events.rs @@ -5,18 +5,27 @@ use iota_sdk::{ client::api::PreparedTransactionDataDto, types::block::{ address::{Address, Bech32Address, Ed25519Address}, + core::{ + basic::{MaxBurnedManaAmount, StrongParents}, + BlockHeader, + }, input::{Input, UtxoInput}, output::{ - unlock_condition::AddressUnlockCondition, BasicOutput, LeafHash, Output, OutputCommitmentProof, + unlock_condition::AddressUnlockCondition, AccountId, BasicOutput, LeafHash, Output, OutputCommitmentProof, OutputIdProof, }, - payload::signed_transaction::{Transaction, TransactionHash, TransactionId}, - protocol::protocol_parameters, + payload::{ + signed_transaction::{Transaction, TransactionHash, TransactionId}, + Payload, SignedTransactionPayload, + }, + protocol::iota_mainnet_protocol_parameters, rand::{ mana::rand_mana_allotment, output::{rand_basic_output, rand_output_metadata}, }, - slot::SlotIndex, + slot::{SlotCommitmentId, SlotIndex}, + unlock::{EmptyUnlock, Unlock, Unlocks}, + BlockBody, BlockId, UnsignedBlock, }, wallet::{ events::types::{ @@ -53,11 +62,8 @@ fn wallet_events_serde() { output_id_proof: OutputIdProof { slot: SlotIndex(1), output_index: 0, - transaction_commitment: "0x".to_string(), - output_commitment_proof: OutputCommitmentProof::LeafHash(LeafHash { - kind: 1, - hash: [0u8; 32], - }), + transaction_commitment: [0u8; 32], + output_commitment_proof: OutputCommitmentProof::Leaf(LeafHash([0u8; 32])), }, network_id: 42, remainder: true, @@ -79,7 +85,7 @@ fn wallet_events_serde() { })); assert_serde_eq(WalletEvent::TransactionProgress( - TransactionProgressEvent::SelectingInputs, + TransactionProgressEvent::BuildingTransaction, )); assert_serde_eq(WalletEvent::TransactionProgress( @@ -90,7 +96,7 @@ fn wallet_events_serde() { )); { - let protocol_parameters = protocol_parameters(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let transaction_id = TransactionId::new(prefix_hex::decode(TRANSACTION_ID).unwrap()); let input1 = Input::Utxo(UtxoInput::new(transaction_id, 0)); let input2 = Input::Utxo(UtxoInput::new(transaction_id, 1)); @@ -106,8 +112,8 @@ fn wallet_events_serde() { let transaction = Transaction::builder(protocol_parameters.network_id()) .with_inputs(vec![input1, input2]) .add_output(output) - .add_mana_allotment(rand_mana_allotment(&protocol_parameters)) - .finish_with_params(&protocol_parameters) + .add_mana_allotment(rand_mana_allotment(protocol_parameters)) + .finish_with_params(protocol_parameters) .unwrap(); assert_serde_eq(WalletEvent::TransactionProgress( @@ -118,14 +124,42 @@ fn wallet_events_serde() { mana_rewards: Default::default(), })), )); + + let block_payload = SignedTransactionPayload::new( + transaction, + Unlocks::new([Unlock::Empty(EmptyUnlock), Unlock::Empty(EmptyUnlock)]).unwrap(), + ) + .unwrap(); + let payload = Payload::from(block_payload); + let block = UnsignedBlock::new( + BlockHeader::new( + protocol_parameters.version(), + protocol_parameters.network_id(), + 0u64, + SlotCommitmentId::new([0; 36]), + SlotIndex(0), + AccountId::new([0; 32]), + ), + BlockBody::build_basic( + StrongParents::from_vec(vec![BlockId::new([0; 36])]).unwrap(), + MaxBurnedManaAmount::Amount(0), + ) + .with_payload(payload) + .finish_block_body() + .unwrap(), + ); + + assert_serde_eq(WalletEvent::TransactionProgress( + TransactionProgressEvent::PreparedBlockSigningInput(prefix_hex::encode(block.signing_input())), + )); } assert_serde_eq(WalletEvent::TransactionProgress( - TransactionProgressEvent::PreparedTransactionSigningHash(ED25519_ADDRESS.to_string()), + TransactionProgressEvent::SigningTransaction, )); assert_serde_eq(WalletEvent::TransactionProgress( - TransactionProgressEvent::SigningTransaction, + TransactionProgressEvent::PreparedTransactionSigningHash(ED25519_ADDRESS.to_string()), )); assert_serde_eq(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting)); diff --git a/sdk/tests/wallet/migrate_stronghold_snapshot_v2_to_v3.rs b/sdk/tests/wallet/migrate_stronghold_snapshot_v2_to_v3.rs index 1bd1747ecb..ae11b2cd07 100644 --- a/sdk/tests/wallet/migrate_stronghold_snapshot_v2_to_v3.rs +++ b/sdk/tests/wallet/migrate_stronghold_snapshot_v2_to_v3.rs @@ -10,10 +10,11 @@ use iota_sdk::{ secret::{stronghold::StrongholdSecretManager, SecretManager}, storage::StorageAdapter, stronghold::{Error as StrongholdError, StrongholdAdapter}, - Error as ClientError, + ClientError, }, crypto::keys::bip44::Bip44, - wallet::{ClientOptions, Error as WalletError, Wallet}, + types::block::protocol::iota_mainnet_protocol_parameters, + wallet::{ClientOptions, Wallet, WalletError}, }; use pretty_assertions::assert_eq; @@ -25,6 +26,7 @@ const PBKDF_ITER: u32 = 100; #[tokio::test] async fn stronghold_snapshot_v2_v3_migration() { iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + let protocol_parameters = iota_mainnet_protocol_parameters(); let storage_path = "test-storage/stronghold_snapshot_v2_v3_migration"; setup(storage_path).unwrap(); @@ -87,7 +89,12 @@ async fn stronghold_snapshot_v2_v3_migration() { let restore_manager = Wallet::builder() .with_storage_path("test-storage/stronghold_snapshot_v2_v3_migration") .with_secret_manager(stronghold_secret_manager) - .with_client_options(ClientOptions::new().with_node(NODE_LOCAL).unwrap()) + .with_client_options( + ClientOptions::new() + .with_node(NODE_LOCAL) + .unwrap() + .with_protocol_parameters(protocol_parameters.clone()), + ) // Build with a different coin type, to check if it gets replaced by the one from the backup .with_bip_path(Bip44::new(IOTA_COIN_TYPE)) .finish() @@ -96,7 +103,7 @@ async fn stronghold_snapshot_v2_v3_migration() { // restore with ignore_if_coin_type_mismatch: Some(true) to not overwrite the coin type let error = restore_manager - .restore_backup( + .restore_from_stronghold_snapshot( PathBuf::from("./tests/wallet/fixtures/v3.stronghold"), "wrong_password".to_owned(), Some(false), @@ -106,10 +113,7 @@ async fn stronghold_snapshot_v2_v3_migration() { match error { Err(WalletError::Client(err)) => { - assert!(matches!( - *err, - ClientError::Stronghold(StrongholdError::InvalidPassword) - )); + assert!(matches!(err, ClientError::Stronghold(StrongholdError::InvalidPassword))); } _ => panic!("unexpected error"), } diff --git a/sdk/tests/wallet/mod.rs b/sdk/tests/wallet/mod.rs index 0cb359191e..4ef0f24d96 100644 --- a/sdk/tests/wallet/mod.rs +++ b/sdk/tests/wallet/mod.rs @@ -1,7 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod address_generation; #[cfg(all(feature = "stronghold", feature = "storage"))] mod backup_restore; mod balance; diff --git a/sdk/tests/wallet/native_tokens.rs b/sdk/tests/wallet/native_tokens.rs index 71bdeda795..acb228c002 100644 --- a/sdk/tests/wallet/native_tokens.rs +++ b/sdk/tests/wallet/native_tokens.rs @@ -3,7 +3,7 @@ use iota_sdk::{ types::block::output::feature::MetadataFeature, - wallet::{CreateNativeTokenParams, Result, SyncOptions}, + wallet::{CreateNativeTokenParams, SyncOptions}, U256, }; use pretty_assertions::assert_eq; @@ -12,7 +12,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] -async fn create_and_mint_native_token() -> Result<()> { +async fn create_and_mint_native_token() -> Result<(), Box> { let storage_path = "test-storage/create_and_mint_native_token"; setup(storage_path)?; @@ -63,7 +63,7 @@ async fn create_and_mint_native_token() -> Result<()> { #[ignore] #[tokio::test] -async fn native_token_foundry_metadata() -> Result<()> { +async fn native_token_foundry_metadata() -> Result<(), Box> { let storage_path = "test-storage/native_token_foundry_metadata"; setup(storage_path)?; diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index d4343fb8c2..9d4cfc4829 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -10,7 +10,7 @@ use iota_sdk::{ protocol::CommittableAgeRange, slot::SlotIndex, }, - wallet::{Assets, Features, MintNftParams, OutputParams, Result, ReturnStrategy, StorageDeposit, Unlocks}, + wallet::{Assets, Features, MintNftParams, OutputParams, ReturnStrategy, StorageDeposit, Unlocks}, }; use pretty_assertions::assert_eq; @@ -18,7 +18,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; #[ignore] #[tokio::test] -async fn output_preparation() -> Result<()> { +async fn output_preparation() -> Result<(), Box> { let storage_path = "test-storage/output_preparation"; setup(storage_path)?; @@ -224,7 +224,7 @@ async fn output_preparation() -> Result<()> { .await .unwrap_err(); match error { - iota_sdk::wallet::Error::NftNotFoundInUnspentOutputs => {} + iota_sdk::wallet::WalletError::NftNotFoundInUnspentOutputs => {} _ => panic!("should return NftNotFoundInUnspentOutputs error"), } @@ -308,7 +308,7 @@ async fn output_preparation() -> Result<()> { .await .unwrap_err(); match error { - iota_sdk::wallet::Error::MissingParameter(_) => {} + iota_sdk::wallet::WalletError::MissingParameter(_) => {} _ => panic!("should return MissingParameter error"), } @@ -432,7 +432,7 @@ async fn output_preparation() -> Result<()> { #[ignore] #[tokio::test] -async fn output_preparation_sdr() -> Result<()> { +async fn output_preparation_sdr() -> Result<(), Box> { let storage_path = "test-storage/output_preparation_sdr"; setup(storage_path)?; @@ -542,7 +542,7 @@ async fn output_preparation_sdr() -> Result<()> { #[ignore] #[tokio::test] -async fn prepare_nft_output_features_update() -> Result<()> { +async fn prepare_nft_output_features_update() -> Result<(), Box> { let storage_path = "test-storage/prepare_nft_output_features_update"; setup(storage_path)?; @@ -615,7 +615,7 @@ async fn prepare_nft_output_features_update() -> Result<()> { #[ignore] #[tokio::test] -async fn prepare_output_remainder_dust() -> Result<()> { +async fn prepare_output_remainder_dust() -> Result<(), Box> { let storage_path_0 = "test-storage/prepare_output_remainder_dust_0"; let storage_path_1 = "test-storage/prepare_output_remainder_dust_1"; setup(storage_path_0)?; @@ -693,7 +693,7 @@ async fn prepare_output_remainder_dust() -> Result<()> { ) .await; assert!( - matches!(result, Err(iota_sdk::wallet::Error::InsufficientFunds{available, required}) if available == balance.base_coin().available() && required == 85199) + matches!(result, Err(iota_sdk::wallet::WalletError::InsufficientFunds{available, required}) if available == balance.base_coin().available() && required == 85199) ); let output = wallet_0 @@ -755,7 +755,7 @@ async fn prepare_output_remainder_dust() -> Result<()> { #[ignore] #[tokio::test] -async fn prepare_output_only_single_nft() -> Result<()> { +async fn prepare_output_only_single_nft() -> Result<(), Box> { let storage_path_0 = "test-storage/prepare_output_only_single_nft_0"; let storage_path_1 = "test-storage/prepare_output_only_single_nft_1"; setup(storage_path_0)?; @@ -782,7 +782,7 @@ async fn prepare_output_only_single_nft() -> Result<()> { assert_eq!(balance.nfts().len(), 1); let nft_amount = wallet_1 - .data() + .ledger() .await .unspent_outputs() .values() @@ -827,7 +827,7 @@ async fn prepare_output_only_single_nft() -> Result<()> { #[ignore] #[tokio::test] -async fn prepare_existing_nft_output_gift() -> Result<()> { +async fn prepare_existing_nft_output_gift() -> Result<(), Box> { let storage_path = "test-storage/prepare_existing_nft_output_gift"; setup(storage_path)?; diff --git a/sdk/tests/wallet/syncing.rs b/sdk/tests/wallet/syncing.rs index cbafe71189..f1e75e40a6 100644 --- a/sdk/tests/wallet/syncing.rs +++ b/sdk/tests/wallet/syncing.rs @@ -14,7 +14,7 @@ // #[tokio::test] // #[cfg(feature = "rocksdb")] -// async fn updated_default_sync_options() -> Result<()> { +// async fn updated_default_sync_options() -> Result<(), WalletError> { // let storage_path = "test-storage/updated_default_sync_options"; // setup(storage_path)?; @@ -42,7 +42,7 @@ // #[ignore] // #[tokio::test] -// async fn sync_only_most_basic_outputs() -> Result<()> { +// async fn sync_only_most_basic_outputs() -> Result<(), WalletError> { // let storage_path_0 = "test-storage/sync_only_most_basic_outputs_0"; // setup(storage_path_0)?; // let storage_path_1 = "test-storage/sync_only_most_basic_outputs_1"; @@ -51,7 +51,7 @@ // let wallet_0 = create_wallet_with_funds(storage_path_0, None, None, 1).await?; // let wallet_1 = make_wallet(storage_path_1, None, None).await?; -// let wallet_1_address = wallet_1.address().await; +// let wallet_1_address = wallet_1.address().clone(); // let token_supply = wallet_0.client().get_token_supply().await?; // // Only one basic output without further unlock conditions @@ -145,7 +145,7 @@ // #[ignore] // #[tokio::test] -// async fn sync_incoming_transactions() -> Result<()> { +// async fn sync_incoming_transactions() -> Result<(), WalletError> { // let storage_path_0 = "test-storage/sync_incoming_transactions_0"; // setup(storage_path_0)?; // let storage_path_1 = "test-storage/sync_incoming_transactions_1"; @@ -154,7 +154,7 @@ // let wallet_0 = create_wallet_with_funds(storage_path_0, None, None, 1).await?; // let wallet_1 = make_wallet(storage_path_1, None, None).await?; -// let wallet_1_address = wallet_1.address().await; +// let wallet_1_address = wallet_1.address().clone(); // let token_supply = wallet_0.client().get_token_supply().await?; @@ -193,7 +193,7 @@ // #[ignore] // #[tokio::test] // #[cfg(feature = "storage")] -// async fn background_syncing() -> Result<()> { +// async fn background_syncing() -> Result<(), WalletError> { // let storage_path = "test-storage/background_syncing"; // setup(storage_path)?; @@ -203,7 +203,7 @@ // iota_sdk::client::request_funds_from_faucet( // crate::wallet::common::FAUCET_URL, -// &wallet.address().await, +// &wallet.address().clone(), // ) // .await?; diff --git a/sdk/tests/wallet/transactions.rs b/sdk/tests/wallet/transactions.rs index 30c7943c2b..e95263c58e 100644 --- a/sdk/tests/wallet/transactions.rs +++ b/sdk/tests/wallet/transactions.rs @@ -1,14 +1,14 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::wallet::{MintNftParams, Result, SendNftParams, SendParams, TransactionOptions}; -use pretty_assertions::assert_eq; +// use iota_sdk::wallet::{MintNftParams, Result, SendNftParams, SendParams, TransactionOptions}; +// use pretty_assertions::assert_eq; -use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; +// use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // #[ignore] // #[tokio::test] -// async fn send_amount() -> Result<()> { +// async fn send_amount() -> Result<(), WalletError> { // let storage_path_0 = "test-storage/send_amount_0"; // setup(storage_path_0)?; // let storage_path_1 = "test-storage/send_amount_1"; @@ -21,7 +21,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // let amount = 1_000_000; // let tx = wallet_0 -// .send_with_params([SendParams::new(amount, wallet_1.address().await)?], None) +// .send_with_params([SendParams::new(amount, wallet_1.address().clone())?], None) // .await?; // wallet_0 @@ -36,7 +36,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // #[ignore] // #[tokio::test] -// async fn send_amount_127_outputs() -> Result<()> { +// async fn send_amount_127_outputs() -> Result<(), WalletError> { // let storage_path_0 = "test-storage/send_amount_127_outputs_0"; // setup(storage_path_0)?; // let storage_path_1 = "test-storage/send_amount_127_outputs_1"; @@ -53,7 +53,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // vec![ // SendParams::new( // amount, -// wallet_1.address().await, +// wallet_1.address().clone(), // )?; // // Only 127, because we need one remainder // 127 @@ -74,7 +74,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // #[ignore] // #[tokio::test] -// async fn send_amount_custom_input() -> Result<()> { +// async fn send_amount_custom_input() -> Result<(), WalletError> { // let storage_path_0 = "test-storage/send_amount_custom_input_0"; // setup(storage_path_0)?; // let storage_path_1 = "test-storage/send_amount_custom_input_1"; @@ -121,7 +121,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // #[ignore] // #[tokio::test] -// async fn send_nft() -> Result<()> { +// async fn send_nft() -> Result<(), WalletError> { // let storage_path_0 = "test-storage/send_nft_0"; // setup(storage_path_0)?; // let storage_path_1 = "test-storage/send_nft_1"; @@ -133,7 +133,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // let wallet_1 = make_wallet(storage_path_1, None, None).await?; // let nft_options = [MintNftParams::new() -// .with_address(wallet_0.address().await) +// .with_address(wallet_0.address().clone()) // .with_metadata(b"some nft metadata".to_vec()) // .with_immutable_metadata(b"some immutable nft metadata".to_vec())]; @@ -146,7 +146,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // // Send to wallet 1 // let transaction = wallet_0 // .send_nft( -// [SendNftParams::new(wallet_1.address().await, nft_id)?], +// [SendNftParams::new(wallet_1.address().clone(), nft_id)?], // None, // ) // .await @@ -164,7 +164,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // #[ignore] // #[tokio::test] -// async fn send_with_note() -> Result<()> { +// async fn send_with_note() -> Result<(), WalletError> { // let storage_path_0 = "test-storage/send_with_note_0"; // setup(storage_path_0)?; // let storage_path_1 = "test-storage/send_with_note_1"; @@ -178,7 +178,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // let amount = 1_000_000; // let tx = wallet_0 // .send_with_params( -// [SendParams::new(amount, wallet_1.address().await)?], +// [SendParams::new(amount, wallet_1.address().clone())?], // Some(TransactionOptions { // note: Some(String::from("send_with_note")), // ..Default::default() @@ -193,7 +193,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // #[ignore] // #[tokio::test] -// async fn conflicting_transaction() -> Result<()> { +// async fn conflicting_transaction() -> Result<(), WalletError> { // let storage_path_0 = "test-storage/conflicting_transaction_0"; // let storage_path_1 = "test-storage/conflicting_transaction_1"; // setup(storage_path_0)?; @@ -213,7 +213,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // .send_with_params( // [SendParams::new( // 1_000_000, -// wallet_0.address().await, +// wallet_0.address().clone(), // )?], // None, // ) @@ -228,7 +228,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // // Something in the transaction must be different than in the first one, otherwise it will be the // same // one // 2_000_000, -// wallet_0.address().await, +// wallet_0.address().clone(), // )?], // None, // ) @@ -265,7 +265,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // #[tokio::test] // #[cfg(all(feature = "ledger_nano", feature = "events"))] // #[ignore = "requires ledger nano instance"] -// async fn prepare_transaction_ledger() -> Result<()> { +// async fn prepare_transaction_ledger() -> Result<(), WalletError> { // use iota_sdk::wallet::events::{types::TransactionProgressEvent, WalletEvent, WalletEventType}; // let storage_path_0 = "test-storage/wallet_address_generation_ledger_0"; @@ -297,7 +297,7 @@ use crate::wallet::common::{make_wallet, request_funds, setup, tear_down}; // .await; // let tx = wallet_0 -// .send_with_params([SendParams::new(amount, wallet_1.address().await)?], None) +// .send_with_params([SendParams::new(amount, wallet_1.address().clone())?], None) // .await?; // let data = receiver.recv().await.expect("never received event");