diff --git a/.changelog/unreleased/bug-fixes/1582-slash-validator-delay-set-update.md b/.changelog/unreleased/bug-fixes/1582-slash-validator-delay-set-update.md new file mode 100644 index 0000000000..f33add78c4 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1582-slash-validator-delay-set-update.md @@ -0,0 +1,3 @@ +- PoS: Ensure that when a validator is slashed, it gets removed from + validator set in the same epoch in Namada state as in CometBFT's state. + ([\#1582](https://github.com/anoma/namada/pull/1582)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1599-wasm-secp256k1-sig.md b/.changelog/unreleased/bug-fixes/1599-wasm-secp256k1-sig.md new file mode 100644 index 0000000000..8dfc4f5be5 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1599-wasm-secp256k1-sig.md @@ -0,0 +1,2 @@ +- Fix signature verification with secp256k1 in WASM VPs. + ([\#1599](https://github.com/anoma/namada/pull/1599)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1615-fix-optional-prefix-iter.md b/.changelog/unreleased/bug-fixes/1615-fix-optional-prefix-iter.md new file mode 100644 index 0000000000..651aabeaff --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1615-fix-optional-prefix-iter.md @@ -0,0 +1,2 @@ +- Storage: Fix iterator without a prefix. + ([\#1615](https://github.com/anoma/namada/pull/1615)) \ No newline at end of file diff --git a/.changelog/unreleased/features/1576-add-below-threshold-validators.md b/.changelog/unreleased/features/1576-add-below-threshold-validators.md new file mode 100644 index 0000000000..522e4a82e7 --- /dev/null +++ b/.changelog/unreleased/features/1576-add-below-threshold-validators.md @@ -0,0 +1,3 @@ +- Adds a third validator set, the below threshold set, which contains + all validators whose stake is below some parameterizable threshold. + ([#1576](https://github.com/anoma/namada/pull/1576)) \ No newline at end of file diff --git a/.changelog/unreleased/features/1578-fix-init-validator-tm-mode.md b/.changelog/unreleased/features/1578-fix-init-validator-tm-mode.md new file mode 100644 index 0000000000..84df295f5f --- /dev/null +++ b/.changelog/unreleased/features/1578-fix-init-validator-tm-mode.md @@ -0,0 +1,4 @@ +- Added `NAMADA_LOG_DIR` env var for logging to file(s) and `NAMADA_LOG_ROLLING` + for setting rolling logs frequency. The rolling frequency can be set to + never, minutely, hourly or daily. If not set, the default is never. + ([\#1578](https://github.com/anoma/namada/pull/1578)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1604-update-wasmer-wasmparser.md b/.changelog/unreleased/improvements/1604-update-wasmer-wasmparser.md new file mode 100644 index 0000000000..4593ac2024 --- /dev/null +++ b/.changelog/unreleased/improvements/1604-update-wasmer-wasmparser.md @@ -0,0 +1,2 @@ +- Updated wasmer to v2.3.0 and switched from pwasm-utils to wasm-instrument. + ([\#1604](https://github.com/anoma/namada/pull/1604)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/64-clap-up-v4.md b/.changelog/unreleased/improvements/64-clap-up-v4.md new file mode 100644 index 0000000000..3c385edb50 --- /dev/null +++ b/.changelog/unreleased/improvements/64-clap-up-v4.md @@ -0,0 +1,2 @@ +- Update clap to the latest version. + ([\#64](https://github.com/anoma/namada/issues/64)) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1008afe7c8..84c4895e3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.71" @@ -338,9 +387,9 @@ dependencies = [ [[package]] name = "async-std" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" dependencies = [ "async-channel", "async-global-executor", @@ -356,6 +405,7 @@ dependencies = [ "kv-log-macro", "log", "memchr", + "num_cpus", "once_cell", "pin-project-lite", "pin-utils", @@ -382,7 +432,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -399,7 +449,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -424,17 +474,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -600,13 +639,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.5", + "prettyplease 0.2.4", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -1108,21 +1147,32 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.0-beta.2" -source = "git+https://github.com/clap-rs/clap/?tag=v3.0.0-beta.2#08b2f4d4289eca8a9225bbc56d5a5ad1e99e38e1" +version = "4.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba77a07e4489fb41bd90e8d4201c3eb246b3c2c9ea2ba0bddd6c1d1df87db7d" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9b4a88bb4bc35d3d6f65a21b0f0bafe9c894fa00978de242c555ec28bea1c0" dependencies = [ - "atty", + "anstream", + "anstyle", "bitflags", - "indexmap", - "lazy_static", - "os_str_bytes", + "clap_lex", "strsim", - "termcolor", - "textwrap", - "unicode-width", - "vec_map", ] +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + [[package]] name = "clru" version = "0.5.0" @@ -1155,6 +1205,12 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "concat-idents" version = "1.1.4" @@ -1238,6 +1294,19 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "corosensei" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "libc", + "scopeguard", + "windows-sys 0.33.0", +] + [[package]] name = "cpufeatures" version = "0.2.7" @@ -1249,24 +1318,24 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" +checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" +checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" dependencies = [ "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli 0.25.0", + "gimli 0.26.2", "log", "regalloc", "smallvec", @@ -1275,31 +1344,30 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" +checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" dependencies = [ "cranelift-codegen-shared", - "cranelift-entity", ] [[package]] name = "cranelift-codegen-shared" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" +checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" [[package]] name = "cranelift-entity" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" +checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" [[package]] name = "cranelift-frontend" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" +checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ "cranelift-codegen", "log", @@ -1515,7 +1583,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -1526,7 +1594,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -1662,7 +1730,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -1845,7 +1913,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -2217,7 +2285,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -2297,9 +2365,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", "indexmap", @@ -2457,9 +2525,9 @@ dependencies = [ [[package]] name = "hdpath" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa5bc9db2c17d2660f53ce217b778d06d68de13d1cd01c0f4e5de4b7918935f" +checksum = "09ae1615f843ce3981b47468f3f7c435ac17deb33c2261e64d7f1e87f5c11acc" dependencies = [ "byteorder", ] @@ -2505,15 +2573,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.2.6" @@ -2864,7 +2923,7 @@ dependencies = [ "toml", "tonic", "tracing 0.1.37", - "uuid 1.3.3", + "uuid 1.3.2", ] [[package]] @@ -2937,6 +2996,17 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits 0.2.15", + "uint", +] + [[package]] name = "impl-serde" version = "0.4.0" @@ -3022,6 +3092,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits 0.2.15", +] + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -3039,6 +3118,18 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -3065,9 +3156,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" dependencies = [ "wasm-bindgen", ] @@ -3180,9 +3271,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "librocksdb-sys" @@ -3367,7 +3458,7 @@ dependencies = [ [[package]] name = "masp_note_encryption" version = "0.2.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "borsh", "chacha20 0.9.1", @@ -3380,7 +3471,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.9.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "aes", "bip0039", @@ -3410,7 +3501,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.9.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "bellman", "blake2b_simd", @@ -3617,7 +3708,7 @@ dependencies = [ "tagptr", "thiserror", "triomphe", - "uuid 1.3.3", + "uuid 1.3.2", ] [[package]] @@ -3662,13 +3753,10 @@ dependencies = [ "pretty_assertions", "proptest", "prost", - "pwasm-utils", "rand 0.8.5", "rand_core 0.6.4", "rayon", "ripemd", - "rust_decimal", - "rust_decimal_macros", "serde 1.0.163", "serde_json", "sha2 0.9.9", @@ -3683,13 +3771,14 @@ dependencies = [ "toml", "tracing 0.1.37", "tracing-subscriber 0.3.17", + "wasm-instrument", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", "wasmer-engine-dylib", "wasmer-engine-universal", "wasmer-vm", - "wasmparser 0.83.0", + "wasmparser 0.107.0", "zeroize", ] @@ -3749,8 +3838,6 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", - "rust_decimal", - "rust_decimal_macros", "serde 1.0.163", "serde_bytes", "serde_json", @@ -3770,6 +3857,7 @@ dependencies = [ "tower", "tower-abci", "tracing 0.1.37", + "tracing-appender", "tracing-log", "tracing-subscriber 0.3.17", "winapi", @@ -3790,17 +3878,20 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", + "impl-num-traits", "index-set", "itertools", "libsecp256k1 0.7.0", "masp_primitives", "namada_macros", + "num-traits 0.2.15", "pretty_assertions", "proptest", "prost", @@ -3808,8 +3899,6 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde 1.0.163", "serde_json", "sha2 0.9.9", @@ -3818,9 +3907,11 @@ dependencies = [ "tendermint-proto", "test-log", "thiserror", + "toml", "tonic-build", "tracing 0.1.37", "tracing-subscriber 0.3.17", + "uint", "zeroize", ] @@ -3856,8 +3947,7 @@ dependencies = [ "once_cell", "proptest", "proptest-state-machine", - "rust_decimal", - "rust_decimal_macros", + "rand 0.8.5", "test-log", "thiserror", "tracing 0.1.37", @@ -3905,8 +3995,6 @@ dependencies = [ "prost", "rand 0.8.5", "regex", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3927,7 +4015,6 @@ dependencies = [ "namada_macros", "namada_proof_of_stake", "namada_vm_env", - "rust_decimal", "sha2 0.9.9", "thiserror", ] @@ -4203,7 +4290,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -4236,12 +4323,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "os_str_bytes" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" - [[package]] name = "output_vt100" version = "0.1.3" @@ -4454,7 +4535,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -4557,12 +4638,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617feabb81566b593beb4886fb8c1f38064169dae4dccad0e3220160c3b37203" +checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" dependencies = [ "proc-macro2", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -4622,9 +4703,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -4752,16 +4833,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "pwasm-utils" -version = "0.20.0" -source = "git+https://github.com/heliaxdev/wasm-utils?tag=v0.20.0#782bfa7fb5e513b602e66af492cbc4cb1b06f2ba" -dependencies = [ - "byteorder", - "log", - "parity-wasm", -] - [[package]] name = "quanta" version = "0.10.1" @@ -4959,9 +5030,9 @@ dependencies = [ [[package]] name = "regalloc" -version = "0.0.31" +version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ "log", "rustc-hash", @@ -5023,9 +5094,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" dependencies = [ "base64 0.21.0", "bytes", @@ -5124,7 +5195,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.3.3", + "uuid 1.3.2", ] [[package]] @@ -5173,28 +5244,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" -[[package]] -name = "rust_decimal" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" -dependencies = [ - "arrayvec 0.7.2", - "borsh", - "num-traits 0.2.15", - "serde 1.0.163", -] - -[[package]] -name = "rust_decimal_macros" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" -dependencies = [ - "quote", - "rust_decimal", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -5233,9 +5282,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.13" +version = "0.37.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" +checksum = "11c63d26a2a123c50a1630342a7f394e863c02ea57d36accdd8bf10484c78811" dependencies = [ "bitflags", "errno", @@ -5570,7 +5619,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -5592,7 +5641,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -5905,9 +5954,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -6117,15 +6166,6 @@ dependencies = [ "time", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "termtree" version = "0.4.1" @@ -6143,15 +6183,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "textwrap" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.40" @@ -6169,7 +6200,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -6198,6 +6229,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ + "itoa", "serde 1.0.163", "time-core", "time-macros", @@ -6341,7 +6373,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -6430,24 +6462,24 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde 1.0.163", ] [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" [[package]] name = "toml_edit" -version = "0.19.9" +version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" +checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" dependencies = [ "indexmap", "toml_datetime", @@ -6586,6 +6618,17 @@ dependencies = [ "tracing-core 0.1.31", ] +[[package]] +name = "tracing-appender" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +dependencies = [ + "crossbeam-channel 0.5.8", + "time", + "tracing-subscriber 0.3.17", +] + [[package]] name = "tracing-attributes" version = "0.1.19" @@ -6604,7 +6647,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -6825,9 +6868,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle 2.4.1", @@ -6862,6 +6905,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" version = "0.8.2" @@ -6870,9 +6919,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "1.3.3" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" dependencies = [ "getrandom 0.2.9", ] @@ -6885,9 +6934,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" [[package]] name = "vcpkg" @@ -6895,12 +6944,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" @@ -7012,9 +7055,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7022,24 +7065,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "083abe15c5d88556b77bdf7aef403625be9e327ad37c62c4e4129af740168163" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7049,9 +7092,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7059,37 +7102,46 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" [[package]] name = "wasm-encoder" -version = "0.27.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77053dc709db790691d3732cfc458adc5acc881dec524965c608effdcd9c581" +checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" dependencies = [ "leb128", ] +[[package]] +name = "wasm-instrument" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" +dependencies = [ + "parity-wasm", +] + [[package]] name = "wasmer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc7dff846db3f38f8ed0be4a009fdfeb729cf1f94a2c7fb6ff2fec01cefa110" +checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" dependencies = [ "cfg-if 1.0.0", "indexmap", @@ -7099,6 +7151,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-bindgen", + "wasmer-artifact", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-derive", @@ -7111,11 +7164,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmer-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aaf9428c29c1d8ad2ac0e45889ba8a568a835e33fd058964e5e500f2f7ce325" +dependencies = [ + "enumset", + "loupe", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-cache" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "834a0de78bf30b9bce61c4c236344b9d8f2f4a3b7713f8de8a8274fbc2d4e9d5" +checksum = "0def391ee1631deac5ac1e6ce919c07a5ccb936ad0fd44708cdc2365c49561a4" dependencies = [ "blake3", "hex", @@ -7125,9 +7191,9 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c91abf22b16dad3826ec0d0e3ec0a8304262a6c7a14e16528c536131b80e63d" +checksum = "e67a6cd866aed456656db2cfea96c18baabbd33f676578482b85c51e1ee19d2c" dependencies = [ "enumset", "loupe", @@ -7138,20 +7204,19 @@ dependencies = [ "target-lexicon", "thiserror", "wasmer-types", - "wasmer-vm", - "wasmparser 0.78.2", + "wasmparser 0.83.0", ] [[package]] name = "wasmer-compiler-cranelift" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7624a1f496b163139a7e0b442426cad805bec70486900287506f9d15a29323ab" +checksum = "48be2f9f6495f08649e4f8b946a2cbbe119faf5a654aa1457f9504a99d23dae0" dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "gimli 0.25.0", + "gimli 0.26.2", "loupe", "more-asserts", "rayon", @@ -7160,18 +7225,18 @@ dependencies = [ "tracing 0.1.37", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-compiler-singlepass" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b63c1538ffb4b0e09edaebfcac35c34141d5944c52f77d137cbe0b634bd40fa" +checksum = "29ca2a35204d8befa85062bc7aac259a8db8070b801b8a783770ba58231d729e" dependencies = [ "byteorder", "dynasm", "dynasmrt", + "gimli 0.26.2", "lazy_static", "loupe", "more-asserts", @@ -7179,14 +7244,13 @@ dependencies = [ "smallvec", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-derive" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "933b23b5cee0f58aa6c17c6de7e1f3007279357e0d555f22e24d6b395cfe7f89" +checksum = "00e50405cc2a2f74ff574584710a5f2c1d5c93744acce2ca0866084739284b51" dependencies = [ "proc-macro-error", "proc-macro2", @@ -7196,9 +7260,9 @@ dependencies = [ [[package]] name = "wasmer-engine" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" +checksum = "3f98f010978c244db431b392aeab0661df7ea0822343334f8f2a920763548e45" dependencies = [ "backtrace", "enumset", @@ -7211,6 +7275,7 @@ dependencies = [ "serde_bytes", "target-lexicon", "thiserror", + "wasmer-artifact", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7218,9 +7283,9 @@ dependencies = [ [[package]] name = "wasmer-engine-dylib" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591683f3356ac31cc88aaecaf77ac2cc9f456014348b01af46c164f44f531162" +checksum = "ad0358af9c154724587731175553805648d9acb8f6657880d165e378672b7e53" dependencies = [ "cfg-if 1.0.0", "enum-iterator", @@ -7233,6 +7298,7 @@ dependencies = [ "serde 1.0.163", "tempfile", "tracing 0.1.37", + "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -7243,12 +7309,11 @@ dependencies = [ [[package]] name = "wasmer-engine-universal" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dccfde103e9b87427099a6de344b7c791574f307d035c8c7dbbc00974c1af0c1" +checksum = "440dc3d93c9ca47865a4f4edd037ea81bf983b5796b59b3d712d844b32dbef15" dependencies = [ "cfg-if 1.0.0", - "enum-iterator", "enumset", "leb128", "loupe", @@ -7256,16 +7321,33 @@ dependencies = [ "rkyv", "wasmer-compiler", "wasmer-engine", + "wasmer-engine-universal-artifact", "wasmer-types", "wasmer-vm", "winapi", ] +[[package]] +name = "wasmer-engine-universal-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f1db3f54152657eb6e86c44b66525ff7801dad8328fe677da48dd06af9ad41" +dependencies = [ + "enum-iterator", + "enumset", + "loupe", + "rkyv", + "thiserror", + "wasmer-artifact", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-object" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" +checksum = "8d831335ff3a44ecf451303f6f891175c642488036b92ceceb24ac8623a8fa8b" dependencies = [ "object 0.28.4", "thiserror", @@ -7275,12 +7357,15 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4deb854f178265a76b59823c41547d259c65da3687b606b0b9c12d80ab950e3e" +checksum = "39df01ea05dc0a9bab67e054c7cb01521e53b35a7bb90bd02eca564ed0b2667f" dependencies = [ + "backtrace", + "enum-iterator", "indexmap", "loupe", + "more-asserts", "rkyv", "serde 1.0.163", "thiserror", @@ -7288,44 +7373,53 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbc5c989cb14a102433927e630473da52f83d82c469acd5cfa8fc7efacc1e70" +checksum = "30d965fa61f4dc4cdb35a54daaf7ecec3563fbb94154a6c35433f879466247dd" dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", + "corosensei", "enum-iterator", "indexmap", + "lazy_static", "libc", "loupe", + "mach", "memoffset 0.6.5", "more-asserts", "region", "rkyv", + "scopeguard", "serde 1.0.163", "thiserror", + "wasmer-artifact", "wasmer-types", "winapi", ] [[package]] name = "wasmparser" -version = "0.78.2" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" +checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wasmparser" -version = "0.83.0" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" +checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" +dependencies = [ + "indexmap", + "semver 1.0.17", +] [[package]] name = "wast" -version = "58.0.0" +version = "57.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372eecae2d10a5091c2005b32377d7ecd6feecdf2c05838056d02d8b4f07c429" +checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" dependencies = [ "leb128", "memchr", @@ -7335,18 +7429,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.64" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d47446190e112ab1579ab40b3ad7e319d859d74e5134683f04e9f0747bf4173" +checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" dependencies = [ "js-sys", "wasm-bindgen", @@ -7459,6 +7553,19 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +dependencies = [ + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -7534,6 +7641,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -7546,6 +7659,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -7558,6 +7677,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -7570,6 +7695,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -7594,6 +7725,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -7677,7 +7814,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 317dd335e7..aa6b0c0b86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ byteorder = "1.4.2" borsh = "0.9.0" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} circular-queue = "0.2.6" -clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} +clap = "4.3.4" clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} color-eyre = "0.5.10" concat-idents = "1.1.2" @@ -83,8 +83,8 @@ libc = "0.2.97" libloading = "0.7.2" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} # branch = "murisi/namada-integration" -masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd", default-features = false, features = ["local-prover"] } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "9320c6b69b5d2e97134866871e960f0a31703813" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "9320c6b69b5d2e97134866871e960f0a31703813", default-features = false, features = ["local-prover"] } num_cpus = "1.13.0" num-derive = "0.3.3" num-rational = "0.4.1" @@ -132,9 +132,10 @@ tower = "0.4" # Also, using the same version of tendermint-rs as we do here. tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "367d8d958b83c501ed2c09e9c4595f8bf75a0b01"} tracing = "0.1.30" +tracing-appender = "0.2.2" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} -wasmparser = "0.83.0" +wasmparser = "0.107.0" winapi = "0.3.9" zeroize = {version = "1.5.5", features = ["zeroize_derive"]} diff --git a/Makefile b/Makefile index 6a63da44ec..f4984d7853 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,12 @@ wasms_for_tests := wasm_for_tests/wasm_source # Paths for all the wasm templates wasm_templates := wasm/tx_template wasm/vp_template +ifdef JOBS +jobs := -j $(JOBS) +else +jobs := +endif + # TODO upgrade libp2p audit-ignores += RUSTSEC-2021-0076 @@ -35,13 +41,13 @@ crates += namada_vm_env crates += namada_vp_prelude build: - $(cargo) build + $(cargo) build $(jobs) build-test: - $(cargo) build --tests + $(cargo) build --tests $(jobs) build-release: - NAMADA_DEV=false $(cargo) build --release --package namada_apps --manifest-path Cargo.toml + NAMADA_DEV=false $(cargo) build $(jobs) --release --package namada_apps --manifest-path Cargo.toml install-release: NAMADA_DEV=false $(cargo) install --path ./apps --locked @@ -69,7 +75,7 @@ check-crates: clippy-wasm = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --all-targets -- -D warnings clippy: - NAMADA_DEV=false $(cargo) +$(nightly) clippy --all-targets -- -D warnings && \ + NAMADA_DEV=false $(cargo) +$(nightly) clippy $(jobs) --all-targets -- -D warnings && \ make -C $(wasms) clippy && \ make -C $(wasms_for_tests) clippy && \ $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true @@ -129,6 +135,7 @@ test-e2e: test-unit: $(cargo) +$(nightly) test \ $(TEST_FILTER) \ + $(jobs) \ -- --skip e2e \ -Z unstable-options --report-time @@ -136,11 +143,13 @@ test-unit-mainnet: $(cargo) +$(nightly) test \ --features "mainnet" \ $(TEST_FILTER) \ + $(jobs) \ -- --skip e2e \ -Z unstable-options --report-time test-unit-debug: $(debug-cargo) +$(nightly) test \ + $(jobs) \ $(TEST_FILTER) -- \ -- --skip e2e \ --nocapture \ diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 35b5483d7c..2ad26e0238 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -108,8 +108,6 @@ ripemd.workspace = true rlimit.workspace = true rocksdb.workspace = true rpassword.workspace = true -rust_decimal_macros.workspace = true -rust_decimal.workspace = true serde_bytes.workspace = true serde_json = {workspace = true, features = ["raw_value"]} serde.workspace = true @@ -124,6 +122,7 @@ toml.workspace = true tonic.workspace = true tower-abci.workspace = true tower.workspace = true +tracing-appender.workspace = true tracing-log.workspace = true tracing-subscriber = { workspace = true, features = ["std", "json", "ansi", "tracing-log"]} tracing.workspace = true diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index bf9921a527..69ca15a89d 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -148,6 +148,17 @@ pub async fn main() -> Result<()> { tx::submit_withdraw::(&client, ctx, args) .await?; } + Sub::TxCommissionRateChange(TxCommissionRateChange(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_validator_commission_change::( + &client, ctx, args, + ) + .await?; + } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { wait_until_node_is_synched(&args.ledger_address).await; diff --git a/apps/src/bin/namada-client/main.rs b/apps/src/bin/namada-client/main.rs index 73876fe7d2..ccdc0bb2eb 100644 --- a/apps/src/bin/namada-client/main.rs +++ b/apps/src/bin/namada-client/main.rs @@ -10,7 +10,7 @@ async fn main() -> Result<()> { color_eyre::install()?; // init logging - logging::init_from_env_or(LevelFilter::INFO)?; + let _log_guard = logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI cli::main().await diff --git a/apps/src/bin/namada-node/main.rs b/apps/src/bin/namada-node/main.rs index b37feb3cdb..a5f187c160 100644 --- a/apps/src/bin/namada-node/main.rs +++ b/apps/src/bin/namada-node/main.rs @@ -9,7 +9,7 @@ fn main() -> Result<()> { color_eyre::install()?; // init logging - logging::init_from_env_or(LevelFilter::INFO)?; + let _log_guard = logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI cli::main() diff --git a/apps/src/bin/namada/main.rs b/apps/src/bin/namada/main.rs index f2cf49560a..488164ca0d 100644 --- a/apps/src/bin/namada/main.rs +++ b/apps/src/bin/namada/main.rs @@ -8,7 +8,7 @@ fn main() -> Result<()> { color_eyre::install()?; // init logging - logging::init_from_env_or(LevelFilter::INFO)?; + let _log_guard = logging::init_from_env_or(LevelFilter::INFO)?; // run the CLI cli::main() diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index b3078037ff..3e99ece7ac 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -9,7 +9,7 @@ pub mod context; mod utils; -use clap::{AppSettings, ArgGroup, ArgMatches}; +use clap::{ArgGroup, ArgMatches, ColorChoice}; use color_eyre::eyre::Result; pub use utils::safe_exit; use utils::*; @@ -26,8 +26,6 @@ const CLIENT_CMD: &str = "client"; const WALLET_CMD: &str = "wallet"; pub mod cmds { - use clap::AppSettings; - use super::utils::*; use super::{args, ArgMatches, CLIENT_CMD, NODE_CMD, WALLET_CMD}; @@ -129,7 +127,8 @@ pub mod cmds { ::add_sub( App::new(Self::CMD) .about("Node sub-commands.") - .setting(AppSettings::SubcommandRequiredElseHelp), + .subcommand_required(true) + .arg_required_else_help(true), ) } } @@ -165,6 +164,7 @@ pub mod cmds { .subcommand(Bond::def().display_order(2)) .subcommand(Unbond::def().display_order(2)) .subcommand(Withdraw::def().display_order(2)) + .subcommand(TxCommissionRateChange::def().display_order(2)) // Queries .subcommand(QueryEpoch::def().display_order(3)) .subcommand(QueryTransfers::def().display_order(3)) @@ -199,6 +199,8 @@ pub mod cmds { Self::parse_with_ctx(matches, TxInitProposal); let tx_vote_proposal = Self::parse_with_ctx(matches, TxVoteProposal); + let tx_commission_rate_change = + Self::parse_with_ctx(matches, TxCommissionRateChange); let bond = Self::parse_with_ctx(matches, Bond); let unbond = Self::parse_with_ctx(matches, Unbond); let withdraw = Self::parse_with_ctx(matches, Withdraw); @@ -233,6 +235,7 @@ pub mod cmds { .or(tx_init_proposal) .or(tx_vote_proposal) .or(tx_init_validator) + .or(tx_commission_rate_change) .or(bond) .or(unbond) .or(withdraw) @@ -279,7 +282,8 @@ pub mod cmds { ::add_sub( App::new(Self::CMD) .about("Client sub-commands.") - .setting(AppSettings::SubcommandRequiredElseHelp), + .subcommand_required(true) + .arg_required_else_help(true), ) } } @@ -294,6 +298,7 @@ pub mod cmds { TxUpdateVp(TxUpdateVp), TxInitAccount(TxInitAccount), TxInitValidator(TxInitValidator), + TxCommissionRateChange(TxCommissionRateChange), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), TxRevealPk(TxRevealPk), @@ -356,7 +361,8 @@ pub mod cmds { ::add_sub( App::new(Self::CMD) .about("Wallet sub-commands.") - .setting(AppSettings::SubcommandRequiredElseHelp), + .subcommand_required(true) + .arg_required_else_help(true), ) } } @@ -391,7 +397,8 @@ pub mod cmds { "Keypair management, including methods to generate and \ look-up keys.", ) - .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand_required(true) + .arg_required_else_help(true) .subcommand(KeyRestore::def()) .subcommand(KeyGen::def()) .subcommand(KeyFind::def()) @@ -539,7 +546,8 @@ pub mod cmds { including methods to generate and look-up addresses and \ keys.", ) - .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand_required(true) + .arg_required_else_help(true) .subcommand(MaspGenSpendKey::def()) .subcommand(MaspGenPayAddr::def()) .subcommand(MaspAddAddrKey::def()) @@ -699,7 +707,8 @@ pub mod cmds { "Address management, including methods to generate and \ look-up addresses.", ) - .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand_required(true) + .arg_required_else_help(true) .subcommand(AddressGen::def()) .subcommand(AddressRestore::def()) .subcommand(AddressOrAliasFind::def()) @@ -975,7 +984,8 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand_required(true) + .arg_required_else_help(true) .about("Configuration sub-commands.") .subcommand(ConfigGen::def()) } @@ -1529,6 +1539,32 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct TxCommissionRateChange( + pub args::CommissionRateChange, + ); + + impl SubCmd for TxCommissionRateChange { + const CMD: &'static str = "change-commission-rate"; + + fn parse(matches: &ArgMatches) -> Option + where + Self: Sized, + { + matches.subcommand_matches(Self::CMD).map(|matches| { + TxCommissionRateChange(args::CommissionRateChange::parse( + matches, + )) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Change commission raate.") + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct TxVoteProposal(pub args::VoteProposal); @@ -1625,7 +1661,8 @@ pub mod cmds { .subcommand(InitGenesisValidator::def()) .subcommand(PkToTmAddress::def()) .subcommand(DefaultBaseDir::def()) - .setting(AppSettings::SubcommandRequiredElseHelp) + .subcommand_required(true) + .arg_required_else_help(true) } } @@ -1766,12 +1803,13 @@ pub mod args { pub use namada::ledger::args::*; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; + use namada::types::dec::Dec; use namada::types::key::*; use namada::types::masp::MaspValue; use namada::types::storage::{self, BlockHeight, Epoch}; use namada::types::time::DateTimeUtc; use namada::types::token; - use rust_decimal::Decimal; + use namada::types::token::NATIVE_MAX_DECIMAL_PLACES; use super::context::*; use super::utils::*; @@ -1801,7 +1839,7 @@ pub mod args { pub const ALIAS: Arg = arg("alias"); pub const ALIAS_FORCE: ArgFlag = flag("alias-force"); pub const ALLOW_DUPLICATE_IP: ArgFlag = flag("allow-duplicate-ip"); - pub const AMOUNT: Arg = arg("amount"); + pub const AMOUNT: Arg = arg("amount"); pub const ARCHIVE_DIR: ArgOpt = arg_opt("archive-dir"); pub const BALANCE_OWNER: ArgOpt = arg_opt("owner"); pub const BASE_DIR: ArgDefault = arg_default( @@ -1820,7 +1858,7 @@ pub mod args { pub const CHANNEL_ID: Arg = arg("channel-id"); pub const CODE_PATH: Arg = arg("code-path"); pub const CODE_PATH_OPT: ArgOpt = CODE_PATH.opt(); - pub const COMMISSION_RATE: Arg = arg("commission-rate"); + pub const COMMISSION_RATE: Arg = arg("commission-rate"); pub const CONSENSUS_TIMEOUT_COMMIT: ArgDefault = arg_default( "consensus-timeout-commit", DefaultFn(|| Timeout::from_str("1s").unwrap()), @@ -1835,10 +1873,20 @@ pub mod args { pub const EXPIRATION_OPT: ArgOpt = arg_opt("expiration"); pub const FORCE: ArgFlag = flag("force"); pub const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); - pub const GAS_AMOUNT: ArgDefault = - arg_default("gas-amount", DefaultFn(|| token::Amount::from(0))); - pub const GAS_LIMIT: ArgDefault = - arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); + pub const GAS_AMOUNT: ArgDefault = arg_default( + "gas-amount", + DefaultFn(|| token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + ); + pub const GAS_LIMIT: ArgDefault = arg_default( + "gas-limit", + DefaultFn(|| token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + ); pub const GAS_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".parse().unwrap())); pub const GENESIS_PATH: Arg = arg("genesis-path"); @@ -1861,7 +1909,7 @@ pub mod args { pub const LEDGER_ADDRESS: Arg = arg("node"); pub const LOCALHOST: ArgFlag = flag("localhost"); pub const MASP_VALUE: Arg = arg("value"); - pub const MAX_COMMISSION_RATE_CHANGE: Arg = + pub const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); pub const NET_ADDRESS: Arg = arg("net-address"); pub const NAMADA_START_TIME: ArgOpt = arg_opt("time"); @@ -1947,8 +1995,8 @@ pub mod args { /// Add global args definition. Should be added to every top-level /// command. pub fn def(app: App) -> App { - app.arg(CHAIN_ID_OPT.def().about("The chain ID.")) - .arg(BASE_DIR.def().about( + app.arg(CHAIN_ID_OPT.def().help("The chain ID.")) + .arg(BASE_DIR.def().help( "The base directory is where the nodes, client and wallet \ configuration and state is stored. This value can also \ be set via `NAMADA_BASE_DIR` environment variable, but \ @@ -1958,7 +2006,7 @@ pub mod args { Unix,`$HOME/Library/Application Support/Namada` on \ Mac,and `%AppData%\\Namada` on Windows.", )) - .arg(WASM_DIR.def().about( + .arg(WASM_DIR.def().help( "Directory with built WASM validity predicates, \ transactions. This value can also be set via \ `NAMADA_WASM_DIR` environment variable, but the argument \ @@ -1979,7 +2027,7 @@ pub mod args { } fn def(app: App) -> App { - app.arg(NAMADA_START_TIME.def().about( + app.arg(NAMADA_START_TIME.def().help( "The start time of the ledger. Accepts a relaxed form of \ RFC3339. A space or a 'T' are accepted as the separator \ between the date and time components. Additional spaces are \ @@ -2015,18 +2063,18 @@ pub mod args { app.arg( NAMADA_START_TIME .def() - .about("The start time of the ledger."), + .help("The start time of the ledger."), ) - .arg(BLOCK_HEIGHT.def().about("The block height to run until.")) - .arg(HALT_ACTION.def().about("Halt at the given block height")) + .arg(BLOCK_HEIGHT.def().help("The block height to run until.")) + .arg(HALT_ACTION.def().help("Halt at the given block height")) .arg( SUSPEND_ACTION .def() - .about("Suspend consensus at the given block height"), + .help("Suspend consensus at the given block height"), ) .group( ArgGroup::new("find_flags") - .args(&[HALT_ACTION.name, SUSPEND_ACTION.name]) + .args([HALT_ACTION.name, SUSPEND_ACTION.name]) .required(true), ) } @@ -2057,17 +2105,19 @@ pub mod args { fn def(app: App) -> App { app - // .arg(BLOCK_HEIGHT_OPT.def().about( + // .arg(BLOCK_HEIGHT_OPT.def().help( // "The block height to dump. Defaults to latest committed // block.", )) - .arg(OUT_FILE_PATH_OPT.def().about( + .arg(OUT_FILE_PATH_OPT.def().help( "Path for the output file (omitting file extension). \ Defaults to \"db_dump.{block_height}.toml\" in the \ current working directory.", )) - .arg(HISTORIC.def().about( - "If provided, dump also the diff of the last height", - )) + .arg( + HISTORIC.def().help( + "If provided, dump also the diff of the last height", + ), + ) } } @@ -2095,7 +2145,7 @@ pub mod args { app.add_args::>().arg( TX_HASH .def() - .about("The hash of the transaction being looked up."), + .help("The hash of the transaction being looked up."), ) } } @@ -2130,9 +2180,9 @@ pub mod args { .arg( CODE_PATH .def() - .about("The path to the transaction's WASM code."), + .help("The path to the transaction's WASM code."), ) - .arg(DATA_PATH_OPT.def().about( + .arg(DATA_PATH_OPT.def().help( "The data file at this path containing arbitrary bytes \ will be passed to the transaction code when it's \ executed.", @@ -2162,7 +2212,7 @@ pub mod args { let target = TRANSFER_TARGET.parse(matches); let token = TOKEN.parse(matches); let sub_prefix = SUB_PREFIX.parse(matches); - let amount = AMOUNT.parse(matches); + let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); Self { tx, @@ -2178,17 +2228,17 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(TRANSFER_SOURCE.def().about( + .arg(TRANSFER_SOURCE.def().help( "The source account address. The source's key may be used \ to produce the signature.", )) - .arg(TRANSFER_TARGET.def().about( + .arg(TRANSFER_TARGET.def().help( "The target account address. The target's key may be used \ to produce the signature.", )) - .arg(TOKEN.def().about("The transfer token.")) - .arg(SUB_PREFIX.def().about("The token's sub prefix.")) - .arg(AMOUNT.def().about("The amount to transfer in decimal.")) + .arg(TOKEN.def().help("The transfer token.")) + .arg(SUB_PREFIX.def().help("The token's sub prefix.")) + .arg(AMOUNT.def().help("The amount to transfer in decimal.")) } } @@ -2229,7 +2279,7 @@ pub mod args { receiver, token, sub_prefix, - amount, + amount: amount.amount, port_id, channel_id, timeout_height, @@ -2240,24 +2290,24 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().about( + .arg(SOURCE.def().help( "The source account address. The source's key is used to \ produce the signature.", )) - .arg(RECEIVER.def().about( + .arg(RECEIVER.def().help( "The receiver address on the destination chain as string.", )) - .arg(TOKEN.def().about("The transfer token.")) - .arg(SUB_PREFIX.def().about("The token's sub prefix.")) - .arg(AMOUNT.def().about("The amount to transfer in decimal.")) - .arg(PORT_ID.def().about("The port ID.")) - .arg(CHANNEL_ID.def().about("The channel ID.")) + .arg(TOKEN.def().help("The transfer token.")) + .arg(SUB_PREFIX.def().help("The token's sub prefix.")) + .arg(AMOUNT.def().help("The amount to transfer in decimal.")) + .arg(PORT_ID.def().help("The port ID.")) + .arg(CHANNEL_ID.def().help("The channel ID.")) .arg( TIMEOUT_HEIGHT .def() - .about("The timeout height of the destination chain."), + .help("The timeout height of the destination chain."), ) - .arg(TIMEOUT_SEC_OFFSET.def().about("The timeout as seconds.")) + .arg(TIMEOUT_SEC_OFFSET.def().help("The timeout as seconds.")) } } @@ -2293,15 +2343,15 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().about( + .arg(SOURCE.def().help( "The source account's address that signs the transaction.", )) - .arg(CODE_PATH_OPT.def().about( + .arg(CODE_PATH_OPT.def().help( "The path to the validity predicate WASM code to be used \ for the new account. Uses the default user VP if none \ specified.", )) - .arg(PUBLIC_KEY.def().about( + .arg(PUBLIC_KEY.def().help( "A public key to be used for the new account in \ hexadecimal encoding.", )) @@ -2361,42 +2411,42 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().about( + .arg(SOURCE.def().help( "The source account's address that signs the transaction.", )) - .arg(SCHEME.def().about( + .arg(SCHEME.def().help( "The key scheme/type used for the validator keys. \ Currently supports ed25519 and secp256k1.", )) - .arg(VALIDATOR_ACCOUNT_KEY.def().about( + .arg(VALIDATOR_ACCOUNT_KEY.def().help( "A public key for the validator account. A new one will \ be generated if none given.", )) - .arg(VALIDATOR_CONSENSUS_KEY.def().about( + .arg(VALIDATOR_CONSENSUS_KEY.def().help( "A consensus key for the validator account. A new one \ will be generated if none given.", )) - .arg(PROTOCOL_KEY.def().about( + .arg(PROTOCOL_KEY.def().help( "A public key for signing protocol transactions. A new \ one will be generated if none given.", )) - .arg(COMMISSION_RATE.def().about( + .arg(COMMISSION_RATE.def().help( "The commission rate charged by the validator for \ delegation rewards. Expressed as a decimal between 0 and \ 1. This is a required parameter.", )) - .arg(MAX_COMMISSION_RATE_CHANGE.def().about( + .arg(MAX_COMMISSION_RATE_CHANGE.def().help( "The maximum change per epoch in the commission rate \ charged by the validator for delegation rewards. \ Expressed as a decimal between 0 and 1. This is a \ required parameter.", )) - .arg(VALIDATOR_CODE_PATH.def().about( + .arg(VALIDATOR_CODE_PATH.def().help( "The path to the validity predicate WASM code to be used \ for the validator account. Uses the default validator VP \ if none specified.", )) - .arg(UNSAFE_DONT_ENCRYPT.def().about( + .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", )) @@ -2431,11 +2481,11 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() .arg( - CODE_PATH.def().about( + CODE_PATH.def().help( "The path to the new validity predicate WASM code.", ), ) - .arg(ADDRESS.def().about( + .arg(ADDRESS.def().help( "The account's address. It's key is used to produce the \ signature.", )) @@ -2460,6 +2510,14 @@ pub mod args { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); let amount = AMOUNT.parse(matches); + let amount = amount + .canonical() + .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) + .unwrap_or_else(|e| { + println!("Could not parse bond amount: {:?}", e); + safe_exit(1); + }) + .amount; let source = SOURCE_OPT.parse(matches); let tx_code_path = PathBuf::from(TX_BOND_WASM); Self { @@ -2474,9 +2532,9 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(VALIDATOR.def().about("Validator address.")) - .arg(AMOUNT.def().about("Amount of tokens to stake in a bond.")) - .arg(SOURCE_OPT.def().about( + .arg(VALIDATOR.def().help("Validator address.")) + .arg(AMOUNT.def().help("Amount of tokens to stake in a bond.")) + .arg(SOURCE_OPT.def().help( "Source address for delegations. For self-bonds, the \ validator is also the source.", )) @@ -2500,6 +2558,14 @@ pub mod args { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); let amount = AMOUNT.parse(matches); + let amount = amount + .canonical() + .increase_precision(NATIVE_MAX_DECIMAL_PLACES.into()) + .unwrap_or_else(|e| { + println!("Could not parse bond amount: {:?}", e); + safe_exit(1); + }) + .amount; let source = SOURCE_OPT.parse(matches); let tx_code_path = PathBuf::from(TX_UNBOND_WASM); Self { @@ -2513,13 +2579,13 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(VALIDATOR.def().about("Validator address.")) + .arg(VALIDATOR.def().help("Validator address.")) .arg( AMOUNT .def() - .about("Amount of tokens to unbond from a bond."), + .help("Amount of tokens to unbond from a bond."), ) - .arg(SOURCE_OPT.def().about( + .arg(SOURCE_OPT.def().help( "Source address for unbonding from delegations. For \ unbonding from self-bonds, the validator is also the \ source.", @@ -2570,13 +2636,13 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(DATA_PATH.def().about( + .arg(DATA_PATH.def().help( "The data path file (json) that describes the proposal.", )) .arg( PROPOSAL_OFFLINE .def() - .about("Flag if the proposal vote should run offline."), + .help("Flag if the proposal vote should run offline."), ) } } @@ -2644,8 +2710,8 @@ pub mod args { .arg( PROPOSAL_ID_OPT .def() - .about("The proposal identifier.") - .conflicts_with_all(&[ + .help("The proposal identifier.") + .conflicts_with_all([ PROPOSAL_OFFLINE.name, DATA_PATH_OPT.name, ]), @@ -2653,12 +2719,12 @@ pub mod args { .arg( PROPOSAL_VOTE .def() - .about("The vote for the proposal. Either yay or nay"), + .help("The vote for the proposal. Either yay or nay"), ) .arg( PROPOSAL_VOTE_PGF_OPT .def() - .about( + .help( "The list of proposed councils and spending \ caps:\n$council1 $cap1 $council2 $cap2 ... \ (council is bech32m encoded address, cap is \ @@ -2670,7 +2736,7 @@ pub mod args { .arg( PROPOSAL_VOTE_ETH_OPT .def() - .about( + .help( "The signing key and message bytes (hex encoded) \ to be signed: $signing_key $message", ) @@ -2680,13 +2746,13 @@ pub mod args { .arg( PROPOSAL_OFFLINE .def() - .about("Flag if the proposal vote should run offline.") + .help("Flag if the proposal vote should run offline.") .conflicts_with(PROPOSAL_ID.name), ) .arg( DATA_PATH_OPT .def() - .about( + .help( "The data path file (json) that describes the \ proposal.", ) @@ -2714,7 +2780,7 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(PUBLIC_KEY.def().about("A public key to reveal.")) + .arg(PUBLIC_KEY.def().help("A public key to reveal.")) } } @@ -2737,7 +2803,7 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(PROPOSAL_ID_OPT.def().about("The proposal identifier.")) + .arg(PROPOSAL_ID_OPT.def().help("The proposal identifier.")) } } @@ -2781,11 +2847,11 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(PROPOSAL_ID_OPT.def().about("The proposal identifier.")) + .arg(PROPOSAL_ID_OPT.def().help("The proposal identifier.")) .arg( PROPOSAL_OFFLINE .def() - .about( + .help( "Flag if the proposal result should run on \ offline data.", ) @@ -2794,7 +2860,7 @@ pub mod args { .arg( DATA_PATH_OPT .def() - .about( + .help( "The path to the folder containing the proposal \ json and votes", ) @@ -2855,8 +2921,8 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(VALIDATOR.def().about("Validator address.")) - .arg(SOURCE_OPT.def().about( + .arg(VALIDATOR.def().help("Validator address.")) + .arg(SOURCE_OPT.def().help( "Source address for withdrawing from delegations. For \ withdrawing from self-bonds, the validator is also the \ source.", @@ -2891,10 +2957,10 @@ pub mod args { .arg( EPOCH .def() - .about("The epoch for which to query conversions."), + .help("The epoch for which to query conversions."), ) .arg( - TOKEN_OPT.def().about( + TOKEN_OPT.def().help( "The token address for which to query conversions.", ), ) @@ -2934,22 +3000,22 @@ pub mod args { .arg( BALANCE_OWNER .def() - .about("The account address whose balance to query."), + .help("The account address whose balance to query."), ) .arg( TOKEN_OPT .def() - .about("The token's address whose balance to query."), + .help("The token's address whose balance to query."), ) .arg( - NO_CONVERSIONS.def().about( + NO_CONVERSIONS.def().help( "Whether not to automatically perform conversions.", ), ) .arg( - SUB_PREFIX.def().about( - "The token's sub prefix whose balance to query.", - ), + SUB_PREFIX + .def() + .help("The token's sub prefix whose balance to query."), ) } } @@ -2960,6 +3026,7 @@ pub mod args { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get_cached(&x)), token: self.token.map(|x| ctx.get(&x)), + sub_prefix: self.sub_prefix, } } } @@ -2969,21 +3036,28 @@ pub mod args { let query = Query::parse(matches); let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); + let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, + sub_prefix, } } fn def(app: App) -> App { app.add_args::>() - .arg(BALANCE_OWNER.def().about( + .arg(BALANCE_OWNER.def().help( "The account address that queried transfers must involve.", )) - .arg(TOKEN_OPT.def().about( + .arg(TOKEN_OPT.def().help( "The token address that queried transfers must involve.", )) + .arg( + SUB_PREFIX + .def() + .help("The token's sub prefix whose balance to query."), + ) } } @@ -3012,14 +3086,14 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() .arg( - OWNER_OPT.def().about( + OWNER_OPT.def().help( "The owner account address whose bonds to query.", ), ) .arg( VALIDATOR_OPT .def() - .about("The validator's address whose bonds to query."), + .help("The validator's address whose bonds to query."), ) } } @@ -3048,21 +3122,21 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(VALIDATOR_OPT.def().about( + .arg(VALIDATOR_OPT.def().help( "The validator's address whose bonded stake to query.", )) - .arg(EPOCH.def().about( + .arg(EPOCH.def().help( "The epoch at which to query (last committed, if not \ specified).", )) } } - impl CliToSdk> - for TxCommissionRateChange + impl CliToSdk> + for CommissionRateChange { - fn to_sdk(self, ctx: &mut Context) -> TxCommissionRateChange { - TxCommissionRateChange:: { + fn to_sdk(self, ctx: &mut Context) -> CommissionRateChange { + CommissionRateChange:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), rate: self.rate, @@ -3071,7 +3145,7 @@ pub mod args { } } - impl Args for TxCommissionRateChange { + impl Args for CommissionRateChange { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); @@ -3087,13 +3161,13 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(VALIDATOR.def().about( + .arg(VALIDATOR.def().help( "The validator's address whose commission rate to change.", )) .arg( COMMISSION_RATE .def() - .about("The desired new commission rate."), + .help("The desired new commission rate."), ) } } @@ -3128,7 +3202,7 @@ pub mod args { fn def(app: App) -> App { app.add_args::>().arg( - VALIDATOR.def().about( + VALIDATOR.def().help( "The address of the jailed validator to re-activate.", ), ) @@ -3159,10 +3233,10 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(VALIDATOR.def().about( + .arg(VALIDATOR.def().help( "The validator's address whose commission rate to query.", )) - .arg(EPOCH.def().about( + .arg(EPOCH.def().help( "The epoch at which to query (last committed, if not \ specified).", )) @@ -3189,7 +3263,7 @@ pub mod args { app.add_args::>().arg( VALIDATOR_OPT .def() - .about("The validator's address whose slashes to query."), + .help("The validator's address whose slashes to query."), ) } } @@ -3203,7 +3277,7 @@ pub mod args { fn def(app: App) -> App { app.add_args::>().arg( - OWNER.def().about( + OWNER.def().help( "The address of the owner of the delegations to find.", ), ) @@ -3230,7 +3304,7 @@ pub mod args { app.add_args::>().arg( TM_ADDRESS .def() - .about("The address of the validator in Tendermint."), + .help("The address of the validator in Tendermint."), ) } } @@ -3262,7 +3336,7 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(STORAGE_KEY.def().about("Storage key")) + .arg(STORAGE_KEY.def().help("Storage key")) } } @@ -3311,42 +3385,42 @@ pub mod args { app.arg( DRY_RUN_TX .def() - .about("Simulate the transaction application."), + .help("Simulate the transaction application."), ) - .arg(DUMP_TX.def().about("Dump transaction bytes to a file.")) - .arg(FORCE.def().about( + .arg(DUMP_TX.def().help("Dump transaction bytes to a file.")) + .arg(FORCE.def().help( "Submit the transaction even if it doesn't pass client checks.", )) - .arg(BROADCAST_ONLY.def().about( + .arg(BROADCAST_ONLY.def().help( "Do not wait for the transaction to be applied. This will \ return once the transaction is added to the mempool.", )) .arg( LEDGER_ADDRESS_DEFAULT .def() - .about(LEDGER_ADDRESS_ABOUT) + .help(LEDGER_ADDRESS_ABOUT) // This used to be "ledger-address", alias for compatibility .alias("ledger-address"), ) - .arg(ALIAS_OPT.def().about( + .arg(ALIAS_OPT.def().help( "If any new account is initialized by the tx, use the given \ alias to save it in the wallet. If multiple accounts are \ initialized, the alias will be the prefix of each new \ address joined with a number.", )) - .arg(WALLET_ALIAS_FORCE.def().about( + .arg(WALLET_ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) - .arg(GAS_AMOUNT.def().about( + .arg(GAS_AMOUNT.def().help( "The amount being paid for the inclusion of this transaction", )) - .arg(GAS_TOKEN.def().about("The token for paying the gas")) + .arg(GAS_TOKEN.def().help("The token for paying the gas")) .arg( - GAS_LIMIT.def().about( + GAS_LIMIT.def().help( "The maximum amount of gas needed to run transaction", ), ) - .arg(EXPIRATION_OPT.def().about( + .arg(EXPIRATION_OPT.def().help( "The expiration datetime of the transaction, after which the \ tx won't be accepted anymore. All of these examples are \ equivalent:\n2012-12-12T12:12:12Z\n2012-12-12 \ @@ -3355,7 +3429,7 @@ pub mod args { .arg( SIGNING_KEY_OPT .def() - .about( + .help( "Sign the transaction with the key for the given \ public key, public key hash or alias from your \ wallet.", @@ -3365,12 +3439,13 @@ pub mod args { .arg( SIGNER .def() - .about( + .help( "Sign the transaction with the keypair of the public \ key of the given address.", ) .conflicts_with(SIGNING_KEY_OPT.name), ) + .arg(CHAIN_ID_OPT.def().help("The chain ID.")) } fn parse(matches: &ArgMatches) -> Self { @@ -3381,9 +3456,10 @@ pub mod args { let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); let wallet_alias_force = WALLET_ALIAS_FORCE.parse(matches); - let fee_amount = GAS_AMOUNT.parse(matches); + let fee_amount = + InputAmount::Unvalidated(GAS_AMOUNT.parse(matches)); let fee_token = GAS_TOKEN.parse(matches); - let gas_limit = GAS_LIMIT.parse(matches).into(); + let gas_limit = GAS_LIMIT.parse(matches).amount.into(); let expiration = EXPIRATION_OPT.parse(matches); let signing_key = SIGNING_KEY_OPT.parse(matches); let signer = SIGNER.parse(matches); @@ -3422,7 +3498,7 @@ pub mod args { app.arg( LEDGER_ADDRESS_DEFAULT .def() - .about(LEDGER_ADDRESS_ABOUT) + .help(LEDGER_ADDRESS_ABOUT) // This used to be "ledger-address", alias for compatibility .alias("ledger-address"), ) @@ -3452,17 +3528,17 @@ pub mod args { app.arg( ALIAS .def() - .about("An alias to be associated with the new entry."), + .help("An alias to be associated with the new entry."), ) - .arg(ALIAS_FORCE.def().about( + .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) .arg( MASP_VALUE .def() - .about("A spending key, viewing key, or payment address."), + .help("A spending key, viewing key, or payment address."), ) - .arg(UNSAFE_DONT_ENCRYPT.def().about( + .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ used in a live network.", )) @@ -3485,9 +3561,12 @@ pub mod args { app.arg( ALIAS .def() - .about("An alias to be associated with the spending key."), + .help("An alias to be associated with the spending key."), ) - .arg(UNSAFE_DONT_ENCRYPT.def().about( + .arg(ALIAS_FORCE.def().help( + "Override the alias without confirmation if it already exists.", + )) + .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ used in a live network.", )) @@ -3521,15 +3600,15 @@ pub mod args { fn def(app: App) -> App { app.arg( - ALIAS.def().about( + ALIAS.def().help( "An alias to be associated with the payment address.", ), ) - .arg(ALIAS_FORCE.def().about( + .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) - .arg(VIEWING_KEY.def().about("The viewing key.")) - .arg(PIN.def().about( + .arg(VIEWING_KEY.def().help("The viewing key.")) + .arg(PIN.def().help( "Require that the single transaction to this address be \ pinned.", )) @@ -3553,25 +3632,25 @@ pub mod args { } fn def(app: App) -> App { - app.arg(SCHEME.def().about( + app.arg(SCHEME.def().help( "The type of key that should be added. Argument must be \ either ed25519 or secp256k1. If none provided, the default \ key scheme is ed25519.", )) - .arg(ALIAS_OPT.def().about( + .arg(ALIAS_OPT.def().help( "The key and address alias. If none provided, the alias will \ be the public key hash.", )) .arg( ALIAS_FORCE .def() - .about("Force overwrite the alias if it already exists."), + .help("Force overwrite the alias if it already exists."), ) - .arg(UNSAFE_DONT_ENCRYPT.def().about( + .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ used in a live network.", )) - .arg(HD_WALLET_DERIVATION_PATH_OPT.def().about( + .arg(HD_WALLET_DERIVATION_PATH_OPT.def().help( "HD key derivation path. Use keyword `default` to refer to a \ scheme default path:\n- m/44'/60'/0'/0/0 for secp256k1 \ scheme\n- m/44'/877'/0'/0'/0' for ed25519 scheme.\nFor \ @@ -3599,23 +3678,23 @@ pub mod args { } fn def(app: App) -> App { - app.arg(SCHEME.def().about( + app.arg(SCHEME.def().help( "The type of key that should be generated. Argument must be \ either ed25519 or secp256k1. If none provided, the default \ key scheme is ed25519.", )) - .arg(ALIAS_OPT.def().about( + .arg(ALIAS_OPT.def().help( "The key and address alias. If none provided, the alias will \ be the public key hash.", )) - .arg(ALIAS_FORCE.def().about( + .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) - .arg(UNSAFE_DONT_ENCRYPT.def().about( + .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the keypair. Do not use this for keys \ used in a live network.", )) - .arg(HD_WALLET_DERIVATION_PATH_OPT.def().about( + .arg(HD_WALLET_DERIVATION_PATH_OPT.def().help( "Generate a new key and wallet using BIP39 mnemonic code and \ HD derivation path. Use keyword `default` to refer to a \ scheme default path:\n- m/44'/60'/0'/0/0 for secp256k1 \ @@ -3646,24 +3725,24 @@ pub mod args { app.arg( RAW_PUBLIC_KEY_OPT .def() - .about("A public key associated with the keypair.") - .conflicts_with_all(&[ALIAS_OPT.name, VALUE.name]), + .help("A public key associated with the keypair.") + .conflicts_with_all([ALIAS_OPT.name, VALUE.name]), ) .arg( ALIAS_OPT .def() - .about("An alias associated with the keypair.") + .help("An alias associated with the keypair.") .conflicts_with(VALUE.name), ) .arg( - VALUE.def().about( - "A public key or alias associated with the keypair.", - ), + VALUE + .def() + .help("A public key or alias associated with the keypair."), ) .arg( UNSAFE_SHOW_SECRET .def() - .about("UNSAFE: Print the secret key."), + .help("UNSAFE: Print the secret key."), ) } } @@ -3679,11 +3758,11 @@ pub mod args { } fn def(app: App) -> App { - app.arg(ALIAS.def().about("The alias that is to be found.")) + app.arg(ALIAS.def().help("The alias that is to be found.")) .arg( UNSAFE_SHOW_SECRET .def() - .about("UNSAFE: Print the spending key values."), + .help("UNSAFE: Print the spending key values."), ) } } @@ -3699,11 +3778,11 @@ pub mod args { } fn def(app: App) -> App { - app.arg(DECRYPT.def().about("Decrypt keys that are encrypted.")) + app.arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) .arg( UNSAFE_SHOW_SECRET .def() - .about("UNSAFE: Print the spending key values."), + .help("UNSAFE: Print the spending key values."), ) } } @@ -3719,11 +3798,11 @@ pub mod args { } fn def(app: App) -> App { - app.arg(DECRYPT.def().about("Decrypt keys that are encrypted.")) + app.arg(DECRYPT.def().help("Decrypt keys that are encrypted.")) .arg( UNSAFE_SHOW_SECRET .def() - .about("UNSAFE: Print the secret keys."), + .help("UNSAFE: Print the secret keys."), ) } } @@ -3737,9 +3816,7 @@ pub mod args { fn def(app: App) -> App { app.arg( - ALIAS - .def() - .about("The alias of the key you wish to export."), + ALIAS.def().help("The alias of the key you wish to export."), ) } } @@ -3755,16 +3832,16 @@ pub mod args { app.arg( ALIAS_OPT .def() - .about("An alias associated with the address."), + .help("An alias associated with the address."), ) .arg( RAW_ADDRESS_OPT .def() - .about("The bech32m encoded address string."), + .help("The bech32m encoded address string."), ) .group( ArgGroup::new("find_flags") - .args(&[ALIAS_OPT.name, RAW_ADDRESS_OPT.name]) + .args([ALIAS_OPT.name, RAW_ADDRESS_OPT.name]) .required(true), ) } @@ -3786,15 +3863,15 @@ pub mod args { app.arg( ALIAS .def() - .about("An alias to be associated with the address."), + .help("An alias to be associated with the address."), ) - .arg(ALIAS_FORCE.def().about( + .arg(ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) .arg( RAW_ADDRESS .def() - .about("The bech32m encoded address string."), + .help("The bech32m encoded address string."), ) } } @@ -3822,11 +3899,11 @@ pub mod args { } fn def(app: App) -> App { - app.arg(CHAIN_ID.def().about("The chain ID. The chain must be known in the repository: \ + app.arg(CHAIN_ID.def().help("The chain ID. The chain must be known in the repository: \ https://github.com/heliaxdev/anoma-network-config")) - .arg(GENESIS_VALIDATOR.def().about("The alias of the genesis validator that you want to set up as, if any.")) - .arg(PRE_GENESIS_PATH.def().about("The path to the pre-genesis directory for genesis validator, if any. Defaults to \"{base-dir}/pre-genesis/{genesis-validator}\".")) - .arg(DONT_PREFETCH_WASM.def().about( + .arg(GENESIS_VALIDATOR.def().help("The alias of the genesis validator that you want to set up as, if any.")) + .arg(PRE_GENESIS_PATH.def().help("The path to the pre-genesis directory for genesis validator, if any. Defaults to \"{base-dir}/pre-genesis/{genesis-validator}\".")) + .arg(DONT_PREFETCH_WASM.def().help( "Do not pre-fetch WASM.", )) } @@ -3844,7 +3921,7 @@ pub mod args { } fn def(app: App) -> App { - app.arg(RAW_PUBLIC_KEY.def().about( + app.arg(RAW_PUBLIC_KEY.def().help( "The consensus public key to be converted to Tendermint \ address.", )) @@ -3876,7 +3953,7 @@ pub mod args { } fn def(app: App) -> App { - app.arg(CHAIN_ID.def().about("The chain ID. The chain must be known in the https://github.com/heliaxdev/anoma-network-config repository, in which case it should have pre-built wasms available for download.")) + app.arg(CHAIN_ID.def().help("The chain ID. The chain must be known in the https://github.com/heliaxdev/anoma-network-config repository, in which case it should have pre-built wasms available for download.")) } } @@ -3920,41 +3997,41 @@ pub mod args { fn def(app: App) -> App { app.arg( - GENESIS_PATH.def().about( + GENESIS_PATH.def().help( "Path to the preliminary genesis configuration file.", ), ) .arg( WASM_CHECKSUMS_PATH .def() - .about("Path to the WASM checksums file."), + .help("Path to the WASM checksums file."), ) - .arg(CHAIN_ID_PREFIX.def().about( + .arg(CHAIN_ID_PREFIX.def().help( "The chain ID prefix. Up to 19 alphanumeric, '.', '-' or '_' \ characters.", )) - .arg(UNSAFE_DONT_ENCRYPT.def().about( + .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the generated keypairs. Do not use \ this for keys used in a live network.", )) - .arg(CONSENSUS_TIMEOUT_COMMIT.def().about( + .arg(CONSENSUS_TIMEOUT_COMMIT.def().help( "The Tendermint consensus timeout_commit configuration as \ e.g. `1s` or `1000ms`. Defaults to 10 seconds.", )) - .arg(LOCALHOST.def().about( + .arg(LOCALHOST.def().help( "Use localhost address for P2P and RPC connections for the \ validators ledger", )) - .arg(ALLOW_DUPLICATE_IP.def().about( + .arg(ALLOW_DUPLICATE_IP.def().help( "Toggle to disable guard against peers connecting from the \ same IP. This option shouldn't be used in mainnet.", )) .arg( DONT_ARCHIVE .def() - .about("Do NOT create the release archive."), + .help("Do NOT create the release archive."), ) - .arg(ARCHIVE_DIR.def().about( + .arg(ARCHIVE_DIR.def().help( "Specify a directory into which to store the archive. Default \ is the current working directory.", )) @@ -3964,8 +4041,8 @@ pub mod args { #[derive(Clone, Debug)] pub struct InitGenesisValidator { pub alias: String, - pub commission_rate: Decimal, - pub max_commission_rate_change: Decimal, + pub commission_rate: Dec, + pub max_commission_rate_change: Dec, pub net_address: SocketAddr, pub unsafe_dont_encrypt: bool, pub key_scheme: SchemeType, @@ -3991,26 +4068,26 @@ pub mod args { } fn def(app: App) -> App { - app.arg(ALIAS.def().about("The validator address alias.")) - .arg(NET_ADDRESS.def().about( + app.arg(ALIAS.def().help("The validator address alias.")) + .arg(NET_ADDRESS.def().help( "Static {host:port} of your validator node's P2P address. \ Namada uses port `26656` for P2P connections by default, \ but you can configure a different value.", )) - .arg(COMMISSION_RATE.def().about( + .arg(COMMISSION_RATE.def().help( "The commission rate charged by the validator for \ delegation rewards. This is a required parameter.", )) - .arg(MAX_COMMISSION_RATE_CHANGE.def().about( + .arg(MAX_COMMISSION_RATE_CHANGE.def().help( "The maximum change per epoch in the commission rate \ charged by the validator for delegation rewards. This is \ a required parameter.", )) - .arg(UNSAFE_DONT_ENCRYPT.def().about( + .arg(UNSAFE_DONT_ENCRYPT.def().help( "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", )) - .arg(SCHEME.def().about( + .arg(SCHEME.def().help( "The key scheme/type used for the validator keys. \ Currently supports ed25519 and secp256k1.", )) @@ -4046,7 +4123,6 @@ pub enum NamadaClient { pub fn namada_client_cli() -> Result { let app = namada_client_app(); - let mut app = cmds::NamadaClient::add_sub(app); let matches = app.clone().get_matches(); match Cmd::parse(&matches) { Some(cmd) => { @@ -4062,6 +4138,7 @@ pub fn namada_client_cli() -> Result { } } None => { + let mut app = app; app.print_help().unwrap(); safe_exit(2); } @@ -4077,7 +4154,9 @@ fn namada_app() -> App { let app = App::new(APP_NAME) .version(namada_version()) .about("Namada command line interface.") - .setting(AppSettings::SubcommandRequiredElseHelp); + .color(ColorChoice::Auto) + .subcommand_required(true) + .arg_required_else_help(true); cmds::Namada::add_sub(args::Global::def(app)) } @@ -4085,7 +4164,9 @@ fn namada_node_app() -> App { let app = App::new(APP_NAME) .version(namada_version()) .about("Namada node command line interface.") - .setting(AppSettings::SubcommandRequiredElseHelp); + .color(ColorChoice::Auto) + .subcommand_required(true) + .arg_required_else_help(true); cmds::NamadaNode::add_sub(args::Global::def(app)) } @@ -4093,7 +4174,9 @@ fn namada_client_app() -> App { let app = App::new(APP_NAME) .version(namada_version()) .about("Namada client command line interface.") - .setting(AppSettings::SubcommandRequiredElseHelp); + .color(ColorChoice::Auto) + .subcommand_required(true) + .arg_required_else_help(true); cmds::NamadaClient::add_sub(args::Global::def(app)) } @@ -4101,6 +4184,8 @@ fn namada_wallet_app() -> App { let app = App::new(APP_NAME) .version(namada_version()) .about("Namada wallet command line interface.") - .setting(AppSettings::SubcommandRequiredElseHelp); + .color(ColorChoice::Auto) + .subcommand_required(true) + .arg_required_else_help(true); cmds::NamadaWallet::add_sub(args::Global::def(app)) } diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index 56965d72ef..eac233ed28 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -3,22 +3,21 @@ use std::fmt::Debug; use std::marker::PhantomData; use std::str::FromStr; -use clap::ArgMatches; +use clap::{ArgAction, ArgMatches}; use color_eyre::eyre::Result; use super::args; use super::context::{Context, FromContext}; // We only use static strings -pub type App = clap::App<'static>; -pub type ClapArg = clap::Arg<'static>; +pub type App = clap::Command; +pub type ClapArg = clap::Arg; pub trait Cmd: Sized { fn add_sub(app: App) -> App; fn parse(matches: &ArgMatches) -> Option; fn parse_or_print_help(app: App) -> Result<(Self, Context)> { - let mut app = Self::add_sub(app); let matches = app.clone().get_matches(); match Self::parse(&matches) { Some(cmd) => { @@ -27,6 +26,7 @@ pub trait Cmd: Sized { Ok((cmd, context)) } None => { + let mut app = app; app.print_help().unwrap(); safe_exit(2); } @@ -160,7 +160,7 @@ impl Arg { pub fn def(&self) -> ClapArg { ClapArg::new(self.name) .long(self.name) - .takes_value(true) + .num_args(1) .required(true) } } @@ -177,14 +177,14 @@ where impl Arg> { pub fn parse(&self, matches: &ArgMatches) -> FromContext { - let raw = matches.value_of(self.name).unwrap(); + let raw = matches.get_one::(self.name).unwrap(); FromContext::new(raw.to_string()) } } impl ArgOpt { pub fn def(&self) -> ClapArg { - ClapArg::new(self.name).long(self.name).takes_value(true) + ClapArg::new(self.name).long(self.name).num_args(1) } } @@ -200,7 +200,7 @@ where impl ArgOpt> { pub fn parse(&self, matches: &ArgMatches) -> Option> { - let raw = matches.value_of(self.name)?; + let raw = matches.get_one::(self.name).map(|s| s.as_str())?; Some(FromContext::new(raw.to_string())) } } @@ -211,7 +211,7 @@ where ::Err: Debug, { pub fn def(&self) -> ClapArg { - ClapArg::new(self.name).long(self.name).takes_value(true) + ClapArg::new(self.name).long(self.name).num_args(1) } pub fn parse(&self, matches: &ArgMatches) -> T { @@ -228,7 +228,7 @@ where ::Err: Debug, { pub fn def(&self) -> ClapArg { - ClapArg::new(self.name).long(self.name).takes_value(true) + ClapArg::new(self.name).long(self.name).num_args(1) } pub fn parse(&self, matches: &ArgMatches) -> FromContext { @@ -242,11 +242,13 @@ where impl ArgFlag { pub fn def(&self) -> ClapArg { - ClapArg::new(self.name).long(self.name).takes_value(false) + ClapArg::new(self.name) + .long(self.name) + .action(ArgAction::SetTrue) } pub fn parse(&self, matches: &ArgMatches) -> bool { - matches.is_present(self.name) + matches.get_flag(self.name) } } @@ -257,14 +259,16 @@ where ::Err: Debug, { pub fn def(&self) -> ClapArg { - ClapArg::new(self.name).long(self.name).multiple(true) + ClapArg::new(self.name) + .long(self.name) + .action(ArgAction::Append) } pub fn parse(&self, matches: &ArgMatches) -> Vec { matches - .values_of(self.name) + .get_many(self.name) .unwrap_or_default() - .map(|raw| { + .map(|raw: &String| { raw.parse().unwrap_or_else(|e| { eprintln!( "Failed to parse the {} argument. Raw value: {}, \ @@ -307,11 +311,11 @@ where T: FromStr, T::Err: Debug, { - args.value_of(field).map(|arg| { - arg.parse().unwrap_or_else(|e| { + args.get_one::(field).map(|s| { + s.as_str().parse().unwrap_or_else(|e| { eprintln!( "Failed to parse the argument {}. Raw value: {}, error: {:?}", - field, arg, e + field, s, e ); safe_exit(1) }) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 8aa466420e..5c73b3f4ec 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -17,14 +17,15 @@ use itertools::Either; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::{Node, ViewingKey}; -use masp_primitives::transaction::components::Amount; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::core::types::transaction::governance::ProposalType; +use namada::ledger::args::InputAmount; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; use namada::ledger::masp::{ - Conversions, PinnedBalanceError, ShieldedContext, ShieldedUtils, + Conversions, MaspAmount, MaspChange, PinnedBalanceError, ShieldedContext, + ShieldedUtils, }; use namada::ledger::native_vp::governance::utils::{self, Votes}; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; @@ -33,7 +34,8 @@ use namada::ledger::pos::{ }; use namada::ledger::queries::RPC; use namada::ledger::rpc::{ - enriched_bonds_and_unbonds, query_epoch, TxResponse, + enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, + TxResponse, }; use namada::ledger::storage::ConversionState; use namada::ledger::wallet::{AddressVpType, Wallet}; @@ -46,6 +48,7 @@ use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; +use namada::types::token::{Change, Denomination, MaspDenom, TokenAddress}; use namada::types::{storage, token}; use crate::cli::{self, args}; @@ -115,6 +118,7 @@ pub async fn query_transfers< args: args::QueryTransfers, ) { let query_token = args.token; + let sub_prefix = args.sub_prefix.map(|s| Key::parse(s).unwrap()); let query_owner = args.owner.map_or_else( || Either::Right(wallet.get_addresses().into_values().collect()), Either::Left, @@ -168,8 +172,17 @@ pub async fn query_transfers< // Check if this transfer pertains to the supplied token relevant &= match &query_token { Some(token) => { - tfer_delta.values().any(|x| x[token] != 0) - || shielded_accounts.values().any(|x| x[token] != 0) + let check = |(tok, chg): (&TokenAddress, &Change)| { + tok.sub_prefix == sub_prefix + && &tok.address == token + && !chg.is_zero() + }; + tfer_delta.values().cloned().any( + |MaspChange { ref asset, change }| check((asset, &change)), + ) || shielded_accounts + .values() + .cloned() + .any(|x| x.iter().any(check)) } None => true, }; @@ -179,34 +192,34 @@ pub async fn query_transfers< } println!("Height: {}, Index: {}, Transparent Transfer:", height, idx); // Display the transparent changes first - for (account, amt) in tfer_delta { + for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { print!(" {}:", account); - for (addr, val) in amt.components() { - let token_alias = lookup_alias(wallet, addr); - let sign = match val.cmp(&0) { - Ordering::Greater => "+", - Ordering::Less => "-", - Ordering::Equal => "", - }; - print!( - " {}{} {}", - sign, - token::Amount::from(val.unsigned_abs()), - token_alias - ); - } - println!(); + let token_alias = lookup_alias(wallet, &asset.address); + let sign = match change.cmp(&Change::zero()) { + Ordering::Greater => "+", + Ordering::Less => "-", + Ordering::Equal => "", + }; + print!( + " {}{} {}", + sign, + format_denominated_amount(client, asset, change.into(),) + .await, + asset.format_with_alias(&token_alias) + ); } + println!(); } // Then display the shielded changes afterwards // TODO: turn this to a display impl - for (account, amt) in shielded_accounts { + // (account, amt) + for (account, masp_change) in shielded_accounts { if fvk_map.contains_key(&account) { print!(" {}:", fvk_map[&account]); - for (addr, val) in amt.components() { - let token_alias = lookup_alias(wallet, addr); - let sign = match val.cmp(&0) { + for (token_addr, val) in masp_change { + let token_alias = lookup_alias(wallet, &token_addr.address); + let sign = match val.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", Ordering::Equal => "", @@ -214,8 +227,13 @@ pub async fn query_transfers< print!( " {}{} {}", sign, - token::Amount::from(val.unsigned_abs()), - token_alias + format_denominated_amount( + client, + &token_addr, + val.into(), + ) + .await, + token_addr.format_with_alias(&token_alias), ); } println!(); @@ -286,29 +304,48 @@ pub async fn query_transparent_balance< let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); match (args.token, args.owner) { (Some(token), Some(owner)) => { - let key = match &args.sub_prefix { + let (balance_key, sub_prefix) = match &args.sub_prefix { Some(sub_prefix) => { let sub_prefix = Key::parse(sub_prefix).unwrap(); let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); - token::multitoken_balance_key( - &prefix, - &owner.address().unwrap(), + ( + token::multitoken_balance_key( + &prefix, + &owner.address().unwrap(), + ), + Some(sub_prefix), ) } - None => token::balance_key(&token, &owner.address().unwrap()), + None => ( + token::balance_key(&token, &owner.address().unwrap()), + None, + ), }; let token_alias = lookup_alias(wallet, &token); - match query_storage_value::(client, &key).await { - Some(balance) => match &args.sub_prefix { - Some(sub_prefix) => { - println!( - "{} with {}: {}", - token_alias, sub_prefix, balance - ); + match query_storage_value::(client, &balance_key) + .await + { + Some(balance) => { + let balance = format_denominated_amount( + client, + &TokenAddress { + address: token, + sub_prefix, + }, + balance, + ) + .await; + match &args.sub_prefix { + Some(sub_prefix) => { + println!( + "{} with {}: {}", + token_alias, sub_prefix, balance + ); + } + None => println!("{}: {}", token_alias, balance), } - None => println!("{}: {}", token_alias, balance), - }, + } None => { println!("No {} balance found for {}", token_alias, owner) } @@ -323,11 +360,13 @@ pub async fn query_transparent_balance< .await; if let Some(balances) = balances { print_balances( + client, wallet, balances, &token, owner.address().as_ref(), - ); + ) + .await; } } } @@ -336,7 +375,7 @@ pub async fn query_transparent_balance< let balances = query_storage_prefix::(client, &prefix).await; if let Some(balances) = balances { - print_balances(wallet, balances, &token, None); + print_balances(client, wallet, balances, &token, None).await; } } (None, None) => { @@ -346,7 +385,8 @@ pub async fn query_transparent_balance< query_storage_prefix::(client, &key) .await; if let Some(balances) = balances { - print_balances(wallet, balances, &token, None); + print_balances(client, wallet, balances, &token, None) + .await; } } } @@ -383,20 +423,21 @@ pub async fn query_pinned_balance< .collect(); let _ = shielded.load().await; // Print the token balances by payment address + let pinned_error = Err(PinnedBalanceError::InvalidViewingKey); for owner in owners { - let mut balance = Err(PinnedBalanceError::InvalidViewingKey); + let mut balance = pinned_error.clone(); // Find the viewing key that can recognize payments the current payment // address for vk in &viewing_keys { balance = shielded .compute_exchanged_pinned_balance(client, owner, vk) .await; - if balance != Err(PinnedBalanceError::InvalidViewingKey) { + if balance != pinned_error { break; } } // If a suitable viewing key was not found, then demand it from the user - if balance == Err(PinnedBalanceError::InvalidViewingKey) { + if balance == pinned_error { print!("Enter the viewing key for {}: ", owner); io::stdout().flush().unwrap(); let mut vk_str = String::new(); @@ -414,42 +455,61 @@ pub async fn query_pinned_balance< .compute_exchanged_pinned_balance(client, owner, &vk) .await } + // Now print out the received quantities according to CLI arguments - match (balance, args.token.as_ref()) { - (Err(PinnedBalanceError::InvalidViewingKey), _) => println!( + match (balance, args.token.as_ref(), args.sub_prefix.as_ref()) { + (Err(PinnedBalanceError::InvalidViewingKey), _, _) => println!( "Supplied viewing key cannot decode transactions to given \ payment address." ), - (Err(PinnedBalanceError::NoTransactionPinned), _) => { + (Err(PinnedBalanceError::NoTransactionPinned), _, _) => { println!("Payment address {} has not yet been consumed.", owner) } - (Ok((balance, epoch)), Some(token)) => { - // Extract and print only the specified token from the total - let (_asset_type, balance) = - value_by_address(&balance, token.clone(), epoch); + (Ok((balance, epoch)), Some(token), sub_prefix) => { let token_alias = lookup_alias(wallet, token); - if balance == 0 { + + let token_address = TokenAddress { + address: token.clone(), + sub_prefix: sub_prefix + .map(|string| Key::parse(string).unwrap()), + }; + let total_balance = balance + .get(&(epoch, token_address.clone())) + .cloned() + .unwrap_or_default(); + + if total_balance.is_zero() { println!( "Payment address {} was consumed during epoch {}. \ Received no shielded {}", - owner, epoch, token_alias + owner, + epoch, + token_address.format_with_alias(&token_alias) ); } else { - let asset_value = token::Amount::from(balance as u64); + let formatted = format_denominated_amount( + client, + &token_address, + total_balance.into(), + ) + .await; println!( "Payment address {} was consumed during epoch {}. \ Received {} {}", - owner, epoch, asset_value, token_alias + owner, + epoch, + formatted, + token_address.format_with_alias(&token_alias), ); } } - (Ok((balance, epoch)), None) => { + (Ok((balance, epoch)), None, _) => { let mut found_any = false; - // Print balances by human-readable token names - let balance = - shielded.decode_amount(client, balance, epoch).await; - for (addr, value) in balance.components() { - let asset_value = token::Amount::from(*value as u64); + + for ((_, token_addr), value) in balance + .iter() + .filter(|((token_epoch, _), _)| *token_epoch == epoch) + { if !found_any { println!( "Payment address {} was consumed during epoch {}. \ @@ -458,13 +518,20 @@ pub async fn query_pinned_balance< ); found_any = true; } + let formatted = format_denominated_amount( + client, + token_addr, + (*value).into(), + ) + .await; + let token_alias = tokens + .get(&token_addr.address) + .map(|a| a.to_string()) + .unwrap_or_else(|| token_addr.address.to_string()); println!( - " {}: {}", - tokens - .get(addr) - .cloned() - .unwrap_or_else(|| addr.clone()), - asset_value, + " {}: {}", + token_addr.format_with_alias(&token_alias), + formatted, ); } if !found_any { @@ -479,7 +546,8 @@ pub async fn query_pinned_balance< } } -fn print_balances( +async fn print_balances( + client: &C, wallet: &Wallet, balances: impl Iterator, token: &Address, @@ -490,40 +558,59 @@ fn print_balances( let token_alias = lookup_alias(wallet, token); writeln!(w, "Token {}", token_alias).unwrap(); - - let print_num = balances - .filter_map( - |(key, balance)| match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => Some(( - owner.clone(), - format!( - "with {}: {}, owned by {}", - sub_prefix, - balance, - lookup_alias(wallet, owner) - ), - )), - None => token::is_any_token_balance_key(&key).map(|owner| { + let mut print_num = 0; + for (key, balance) in balances { + let (o, s) = match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, [tok, owner])) => ( + owner.clone(), + format!( + "with {}: {}, owned by {}", + sub_prefix.clone(), + format_denominated_amount( + client, + &TokenAddress { + address: tok.clone(), + sub_prefix: Some(sub_prefix) + }, + balance + ) + .await, + lookup_alias(wallet, owner) + ), + ), + None => { + if let Some([tok, owner]) = + token::is_any_token_balance_key(&key) + { ( owner.clone(), format!( ": {}, owned by {}", - balance, + format_denominated_amount( + client, + &TokenAddress { + address: tok.clone(), + sub_prefix: None + }, + balance + ) + .await, lookup_alias(wallet, owner) ), ) - }), - }, - ) - .filter_map(|(o, s)| match target { - Some(t) if o == *t => Some(s), - Some(_) => None, - None => Some(s), - }) - .map(|s| { - writeln!(w, "{}", s).unwrap(); - }) - .count(); + } else { + continue; + } + } + }; + let s = match target { + Some(t) if o == *t => s, + Some(_) => continue, + None => s, + }; + writeln!(w, "{}", s).unwrap(); + print_num += 1; + } if print_num == 0 { match target { @@ -587,8 +674,10 @@ pub async fn query_proposal( println!("{:4}End Epoch: {}", "", end_epoch); println!("{:4}Grace Epoch: {}", "", grace_epoch); let votes = get_proposal_votes(client, start_epoch, id).await; - let total_stake = - get_total_staked_tokens(client, start_epoch).await.into(); + let total_stake = get_total_staked_tokens(client, start_epoch) + .await + .try_into() + .unwrap(); if start_epoch > current_epoch { println!("{:4}Status: pending", ""); } else if start_epoch <= current_epoch && current_epoch <= end_epoch @@ -668,23 +757,6 @@ pub async fn query_proposal( } } -/// Get the component of the given amount corresponding to the given token -pub fn value_by_address( - amt: &masp_primitives::transaction::components::Amount, - token: Address, - epoch: Epoch, -) -> (AssetType, i64) { - // Compute the unique asset identifier from the token address - let asset_type = AssetType::new( - (token, epoch.0) - .try_to_vec() - .expect("token addresses should serialize") - .as_ref(), - ) - .unwrap(); - (asset_type, amt[&asset_type]) -} - /// Query token shielded balance(s) pub async fn query_shielded_balance< C: namada::ledger::queries::Client + Sync, @@ -724,9 +796,10 @@ pub async fn query_shielded_balance< // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; - let balance: Amount = if no_conversions { + let balance: MaspAmount = if no_conversions { shielded - .compute_shielded_balance(&viewing_key) + .compute_shielded_balance(client, &viewing_key) + .await .expect("context should contain viewing key") } else { shielded @@ -734,25 +807,34 @@ pub async fn query_shielded_balance< .await .expect("context should contain viewing key") }; - // Compute the unique asset identifier from the token address - let token = token; - let asset_type = AssetType::new( - (token.clone(), epoch.0) - .try_to_vec() - .expect("token addresses should serialize") - .as_ref(), - ) - .unwrap(); + let token_alias = lookup_alias(wallet, &token); - if balance[&asset_type] == 0 { + + let token_address = TokenAddress { + address: token, + sub_prefix: args.sub_prefix.map(|k| Key::parse(k).unwrap()), + }; + + let total_balance = balance + .get(&(epoch, token_address.clone())) + .cloned() + .unwrap_or_default(); + if total_balance.is_zero() { println!( "No shielded {} balance found for given key", - token_alias + token_address.format_with_alias(&token_alias) ); } else { - let asset_value = - token::Amount::from(balance[&asset_type] as u64); - println!("{}: {}", token_alias, asset_value); + println!( + "{}: {}", + token_address.format_with_alias(&token_alias), + format_denominated_amount( + client, + &token_address, + token::Amount::from(total_balance) + ) + .await + ); } } // Here the user wants to know the balance of all tokens across users @@ -764,7 +846,8 @@ pub async fn query_shielded_balance< let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { shielded - .compute_shielded_balance(&viewing_key) + .compute_shielded_balance(client, &viewing_key) + .await .expect("context should contain viewing key") } else { shielded @@ -772,58 +855,100 @@ pub async fn query_shielded_balance< .await .expect("context should contain viewing key") }; - for (asset_type, value) in balance.components() { - if !balances.contains_key(asset_type) { - balances.insert(*asset_type, Vec::new()); + for (key, value) in balance.iter() { + if !balances.contains_key(key) { + balances.insert(key.clone(), Vec::new()); } - balances.get_mut(asset_type).unwrap().push((fvk, *value)); + balances.get_mut(key).unwrap().push((fvk, *value)); } } // These are the asset types for which we have human-readable names - let mut read_tokens = HashSet::new(); + let mut read_tokens: HashMap>> = + HashMap::new(); // Print non-zero balances whose asset types can be decoded - for (asset_type, balances) in balances { - // Decode the asset type - let decoded = - shielded.decode_asset_type(client, asset_type).await; - match decoded { - Some((addr, asset_epoch)) if asset_epoch == epoch => { - // Only assets with the current timestamp count + // TODO Implement a function for this + + let mut balance_map = HashMap::new(); + for ((asset_epoch, token_addr), balances) in balances { + if asset_epoch == epoch { + // remove this from here, should not be making the + // hashtable creation any uglier + if balances.is_empty() { println!( - "Shielded Token {}:", - tokens - .get(&addr) - .cloned() - .unwrap_or_else(|| addr.clone()) + "No shielded {} balance found for any wallet key", + &token_addr ); - read_tokens.insert(addr); } - _ => continue, - } - - let mut found_any = false; - for (fvk, value) in balances { - let value = token::Amount::from(value as u64); - println!(" {}, owned by {}", value, fvk); - found_any = true; - } - if !found_any { - println!( - "No shielded {} balance found for any wallet key", - asset_type - ); + for (fvk, value) in balances { + balance_map.insert((fvk, token_addr.clone()), value); + } } } + for ( + ( + fvk, + TokenAddress { + address: addr, + sub_prefix, + }, + ), + token_balance, + ) in balance_map + { + read_tokens + .entry(addr.clone()) + .and_modify(|addr_vec| addr_vec.push(sub_prefix.clone())) + .or_insert_with(|| vec![sub_prefix.clone()]); + let token_address = TokenAddress { + address: addr, + sub_prefix, + }; + // Only assets with the current timestamp count + let alias = tokens + .get(&token_address.address) + .map(|a| a.to_string()) + .unwrap_or_else(|| token_address.address.to_string()); + println!( + "Shielded Token {}:", + token_address.format_with_alias(&alias), + ); + let formatted = format_denominated_amount( + client, + &token_address, + token_balance.into(), + ) + .await; + println!(" {}, owned by {}", formatted, fvk); + } // Print zero balances for remaining assets for token in tokens { - if !read_tokens.contains(&token) { + if let Some(sub_addrs) = read_tokens.get(&token) { let token_alias = lookup_alias(wallet, &token); - println!("Shielded Token {}:", token_alias); - println!( - "No shielded {} balance found for any wallet key", - token_alias - ); + for sub_addr in sub_addrs { + match sub_addr { + // abstract out these prints + Some(sub_addr) => { + println!( + "Shielded Token {}/{}:", + token_alias, sub_addr + ); + println!( + "No shielded {}/{} balance found for any \ + wallet key", + token_alias, sub_addr + ); + } + None => { + println!("Shielded Token {}:", token_alias,); + println!( + "No shielded {} balance found for any \ + wallet key", + token_alias + ); + } + } + } } } } @@ -832,7 +957,7 @@ pub async fn query_shielded_balance< (Some(token), false) => { // Compute the unique asset identifier from the token address let token = token; - let asset_type = AssetType::new( + let _asset_type = AssetType::new( (token.clone(), epoch.0) .try_to_vec() .expect("token addresses should serialize") @@ -842,12 +967,25 @@ pub async fn query_shielded_balance< let token_alias = lookup_alias(wallet, &token); println!("Shielded Token {}:", token_alias); let mut found_any = false; + let token_alias = lookup_alias(wallet, &token); + let token_address = TokenAddress { + address: token.clone(), + sub_prefix: args + .sub_prefix + .as_ref() + .map(|k| Key::parse(k).unwrap()), + }; + println!( + "Shielded Token {}:", + token_address.format_with_alias(&token_alias), + ); for fvk in viewing_keys { // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; let balance = if no_conversions { shielded - .compute_shielded_balance(&viewing_key) + .compute_shielded_balance(client, &viewing_key) + .await .expect("context should contain viewing key") } else { shielded @@ -855,17 +993,24 @@ pub async fn query_shielded_balance< .await .expect("context should contain viewing key") }; - if balance[&asset_type] != 0 { - let asset_value = - token::Amount::from(balance[&asset_type] as u64); - println!(" {}, owned by {}", asset_value, fvk); - found_any = true; + + for ((_, address), val) in balance.iter() { + if !val.is_zero() { + found_any = true; + } + let formatted = format_denominated_amount( + client, + address, + (*val).into(), + ) + .await; + println!(" {}, owned by {}", formatted, fvk); } } if !found_any { println!( "No shielded {} balance found for any wallet key", - token_alias + token_address.format_with_alias(&token_alias), ); } } @@ -874,62 +1019,76 @@ pub async fn query_shielded_balance< // Query the multi-asset balance at the given spending key let viewing_key = ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; - let balance; if no_conversions { - balance = shielded - .compute_shielded_balance(&viewing_key) + let balance = shielded + .compute_shielded_balance(client, &viewing_key) + .await .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = - shielded.decode_all_amounts(client, balance).await; - print_decoded_balance_with_epoch(wallet, decoded_balance); + print_decoded_balance_with_epoch(client, wallet, balance).await; } else { - balance = shielded + let balance = shielded .compute_exchanged_balance(client, &viewing_key, epoch) .await .expect("context should contain viewing key"); // Print balances by human-readable token names - let decoded_balance = - shielded.decode_amount(client, balance, epoch).await; - print_decoded_balance(wallet, decoded_balance); + print_decoded_balance(client, wallet, balance, epoch).await; } } } } -pub fn print_decoded_balance( +pub async fn print_decoded_balance< + C: namada::ledger::queries::Client + Sync, +>( + client: &C, wallet: &mut Wallet, - decoded_balance: Amount
, + decoded_balance: MaspAmount, + epoch: Epoch, ) { - let mut found_any = false; - for (addr, value) in decoded_balance.components() { - let asset_value = token::Amount::from(*value as u64); - println!("{} : {}", lookup_alias(wallet, addr), asset_value); - found_any = true; - } - if !found_any { + if decoded_balance.is_empty() { println!("No shielded balance found for given key"); + } else { + for ((_, token_addr), amount) in decoded_balance + .iter() + .filter(|((token_epoch, _), _)| *token_epoch == epoch) + { + println!( + "{} : {}", + token_addr.format_with_alias(&lookup_alias( + wallet, + &token_addr.address + )), + format_denominated_amount(client, token_addr, (*amount).into()) + .await, + ); + } } } -pub fn print_decoded_balance_with_epoch( +pub async fn print_decoded_balance_with_epoch< + C: namada::ledger::queries::Client + Sync, +>( + client: &C, wallet: &mut Wallet, - decoded_balance: Amount<(Address, Epoch)>, + decoded_balance: MaspAmount, ) { let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); - let mut found_any = false; - for ((addr, epoch), value) in decoded_balance.components() { - let asset_value = token::Amount::from(*value as u64); + if decoded_balance.is_empty() { + println!("No shielded balance found for given key"); + } + for ((epoch, token_addr), value) in decoded_balance.iter() { + let asset_value = (*value).into(); + let alias = tokens + .get(&token_addr.address) + .map(|a| a.to_string()) + .unwrap_or_else(|| token_addr.to_string()); println!( "{} | {} : {}", - tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + token_addr.format_with_alias(&alias), epoch, - asset_value + format_denominated_amount(client, token_addr, asset_value).await, ); - found_any = true; - } - if !found_any { - println!("No shielded balance found for given key"); } } @@ -974,7 +1133,8 @@ pub async fn query_proposal_result< let total_stake = get_total_staked_tokens(client, end_epoch) .await - .into(); + .try_into() + .unwrap(); println!("Proposal: {}", id); match utils::compute_tally( votes, @@ -1080,7 +1240,8 @@ pub async fn query_proposal_result< proposal.tally_epoch, ) .await - .into(); + .try_into() + .unwrap(); match utils::compute_tally( votes, total_stake, @@ -1231,15 +1392,18 @@ pub async fn query_and_print_unbonds< } } if total_withdrawable != token::Amount::default() { - println!("Total withdrawable now: {total_withdrawable}."); + println!( + "Total withdrawable now: {}.", + total_withdrawable.to_string_native() + ); } if !not_yet_withdrawable.is_empty() { println!("Current epoch: {current_epoch}.") } for (withdraw_epoch, amount) in not_yet_withdrawable { println!( - "Amount {amount} withdrawable starting from epoch \ - {withdraw_epoch}." + "Amount {} withdrawable starting from epoch {withdraw_epoch}.", + amount.to_string_native(), ); } } @@ -1291,17 +1455,18 @@ pub async fn query_bonds( writeln!( w, " Remaining active bond from epoch {}: Δ {}", - bond.start, bond.amount + bond.start, + bond.amount.to_string_native() )?; } - if details.bonds_total_slashed != token::Amount::default() { + if details.bonds_total != token::Amount::zero() { writeln!( w, "Active (slashed) bonds total: {}", - details.bonds_total_active() + details.bonds_total_active().to_string_native() )?; } - writeln!(w, "Bonds total: {}", details.bonds_total)?; + writeln!(w, "Bonds total: {}", details.bonds_total.to_string_native())?; writeln!(w)?; if !details.data.unbonds.is_empty() { @@ -1315,22 +1480,36 @@ pub async fn query_bonds( writeln!( w, " Withdrawable from epoch {} (active from {}): Δ {}", - unbond.withdraw, unbond.start, unbond.amount + unbond.withdraw, + unbond.start, + unbond.amount.to_string_native() )?; } - writeln!(w, "Unbonded total: {}", details.unbonds_total)?; + writeln!( + w, + "Unbonded total: {}", + details.unbonds_total.to_string_native() + )?; } - writeln!(w, "Withdrawable total: {}", details.total_withdrawable)?; + writeln!( + w, + "Withdrawable total: {}", + details.total_withdrawable.to_string_native() + )?; writeln!(w)?; } if bonds_and_unbonds.bonds_total != bonds_and_unbonds.bonds_total_slashed { writeln!( w, "All bonds total active: {}", - bonds_and_unbonds.bonds_total_active() + bonds_and_unbonds.bonds_total_active().to_string_native() )?; } - writeln!(w, "All bonds total: {}", bonds_and_unbonds.bonds_total)?; + writeln!( + w, + "All bonds total: {}", + bonds_and_unbonds.bonds_total.to_string_native() + )?; if bonds_and_unbonds.unbonds_total != bonds_and_unbonds.unbonds_total_slashed @@ -1338,14 +1517,18 @@ pub async fn query_bonds( writeln!( w, "All unbonds total active: {}", - bonds_and_unbonds.unbonds_total_active() + bonds_and_unbonds.unbonds_total_active().to_string_native() )?; } - writeln!(w, "All unbonds total: {}", bonds_and_unbonds.unbonds_total)?; + writeln!( + w, + "All unbonds total: {}", + bonds_and_unbonds.unbonds_total.to_string_native() + )?; writeln!( w, "All unbonds total withdrawable: {}", - bonds_and_unbonds.total_withdrawable + bonds_and_unbonds.total_withdrawable.to_string_native() )?; Ok(()) } @@ -1369,7 +1552,10 @@ pub async fn query_bonded_stake( Some(stake) => { // TODO: show if it's in consensus set, below capacity, or // below threshold set - println!("Bonded stake of validator {validator}: {stake}",) + println!( + "Bonded stake of validator {validator}: {}", + stake.to_string_native() + ) } None => { println!("No bonded stake found for {validator}") @@ -1398,8 +1584,13 @@ pub async fn query_bonded_stake( writeln!(w, "Consensus validators:").unwrap(); for val in consensus { - writeln!(w, " {}: {}", val.address.encode(), val.bonded_stake) - .unwrap(); + writeln!( + w, + " {}: {}", + val.address.encode(), + val.bonded_stake.to_string_native() + ) + .unwrap(); } if !below_capacity.is_empty() { writeln!(w, "Below capacity validators:").unwrap(); @@ -1408,7 +1599,7 @@ pub async fn query_bonded_stake( w, " {}: {}", val.address.encode(), - val.bonded_stake + val.bonded_stake.to_string_native() ) .unwrap(); } @@ -1417,7 +1608,10 @@ pub async fn query_bonded_stake( } let total_staked_tokens = get_total_staked_tokens(client, epoch).await; - println!("Total bonded stake: {total_staked_tokens}"); + println!( + "Total bonded stake: {}", + total_staked_tokens.to_string_native() + ); } /// Query and return validator's commission rate and max commission rate change @@ -1652,7 +1846,7 @@ pub async fn query_conversions( .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found let mut conversions_found = false; - for (addr, epoch, conv, _) in conv_state.assets.values() { + for ((addr, sub, _), epoch, conv, _) in conv_state.assets.values() { let amt: masp_primitives::transaction::components::Amount = conv.clone().into(); // If the user has specified any targets, then meet them @@ -1666,8 +1860,9 @@ pub async fn query_conversions( conversions_found = true; // Print the asset to which the conversion applies print!( - "{}[{}]: ", + "{}{}[{}]: ", tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + sub.as_ref().map(|k| format!("/{}", k)).unwrap_or_default(), epoch, ); // Now print out the components of the allowed conversion @@ -1675,13 +1870,14 @@ pub async fn query_conversions( for (asset_type, val) in amt.components() { // Look up the address and epoch of asset to facilitate pretty // printing - let (addr, epoch, _, _) = &conv_state.assets[asset_type]; + let ((addr, sub, _), epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion print!( - "{}{} {}[{}]", + "{}{} {}{}[{}]", prefix, val, tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + sub.as_ref().map(|k| format!("/{}", k)).unwrap_or_default(), epoch ); // Future iterations need to be prefixed with + @@ -1701,6 +1897,8 @@ pub async fn query_conversion( asset_type: AssetType, ) -> Option<( Address, + Option, + MaspDenom, Epoch, masp_primitives::transaction::components::Amount, MerklePath, @@ -1883,7 +2081,8 @@ pub async fn get_proposal_offline_votes< ) .await .unwrap_or_default() - .into(); + .try_into() + .expect("Amount out of bounds"); yay_validators.insert( proposal_vote.address, (amount, ProposalVote::Yay(VoteType::Default)), @@ -1920,7 +2119,7 @@ pub async fn get_proposal_offline_votes< }, ) in bonds_and_unbonds { - let mut delegated_amount = token::Amount::default(); + let mut delegated_amount = token::Amount::zero(); for delta in bonds { if delta.start <= proposal.tally_epoch { delegated_amount += delta.amount @@ -1934,7 +2133,7 @@ pub async fn get_proposal_offline_votes< entry.insert( validator, ( - VotePower::from(delegated_amount), + VotePower::try_from(delegated_amount).unwrap(), proposal_vote.vote.clone(), ), ); @@ -1975,7 +2174,7 @@ pub async fn get_proposal_offline_votes< // continue; // } else { // delta -= to_deduct; - // to_deduct = token::Amount::default(); + // to_deduct = token::Amount::zero(); // } // delta = apply_slashes( @@ -2052,6 +2251,10 @@ pub async fn get_total_staked_tokens< namada::ledger::rpc::get_total_staked_tokens(client, epoch).await } +/// Get the total stake of a validator at the given epoch. The total stake is a +/// sum of validator's self-bonds and delegations to their address. +/// Returns `None` when the given address is not a validator address. For a +/// validator with `0` stake, this returns `Ok(token::Amount::zero())`. async fn get_validator_stake( client: &C, epoch: Epoch, @@ -2100,3 +2303,52 @@ fn unwrap_client_response( cli::safe_exit(1) }) } + +/// Get the correct representation of the amount given the token type. +pub async fn validate_amount( + client: &C, + amount: InputAmount, + token: &Address, + sub_prefix: &Option, + force: bool, +) -> token::DenominatedAmount { + let input_amount = match amount { + InputAmount::Unvalidated(amt) => amt.canonical(), + InputAmount::Validated(amt) => return amt, + }; + let denom = unwrap_client_response::>( + RPC.vp() + .token() + .denomination(client, token, sub_prefix) + .await, + ) + .unwrap_or_else(|| { + if force { + println!( + "No denomination found for token: {token}, but --force was \ + passed. Defaulting to the provided denomination." + ); + input_amount.denom + } else { + println!( + "No denomination found for token: {token}, the input \ + arguments could not be parsed." + ); + cli::safe_exit(1); + } + }); + if denom < input_amount.denom && !force { + println!( + "The input amount contained a higher precision than allowed by \ + {token}." + ); + cli::safe_exit(1); + } else { + input_amount.increase_precision(denom).unwrap_or_else(|_| { + println!( + "The amount provided requires more the 256 bits to represent." + ); + cli::safe_exit(1); + }) + } +} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 237cef3c92..9a3a757c0c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -19,19 +19,15 @@ use namada::ledger::{masp, pos, tx}; use namada::proof_of_stake::parameters::PosParams; use namada::proto::{Code, Data, Section, Tx}; use namada::types::address::Address; +use namada::types::dec::Dec; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; -use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{Epoch, Key}; use namada::types::token; -use namada::types::transaction::governance::{ - InitProposalData, ProposalType, VoteProposalData, -}; +use namada::types::transaction::governance::{ProposalType, VoteProposalData}; use namada::types::transaction::{InitValidator, TxType}; -use rust_decimal::Decimal; -use sha2::{Digest as Sha2Digest, Sha256}; use super::rpc; use crate::cli::context::WalletAddress; @@ -184,7 +180,7 @@ pub async fn submit_init_validator< .unwrap(); // Validate the commission rate data - if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { + if commission_rate > Dec::one() || commission_rate < Dec::zero() { eprintln!( "The validator commission rate must not exceed 1.0 or 100%, and \ it must be 0 or positive" @@ -193,8 +189,8 @@ pub async fn submit_init_validator< safe_exit(1) } } - if max_commission_rate_change > Decimal::ONE - || max_commission_rate_change < Decimal::ZERO + if max_commission_rate_change > Dec::one() + || max_commission_rate_change < Dec::zero() { eprintln!( "The validator maximum change in commission rate per epoch must \ @@ -213,8 +209,6 @@ pub async fn submit_init_validator< let extra = tx.add_section(Section::ExtraData(Code::from_hash( validator_vp_code_hash, ))); - let extra_hash = - Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), @@ -222,7 +216,7 @@ pub async fn submit_init_validator< dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code_hash: extra_hash, + validator_vp_code_hash: extra.get_hash(), }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); tx.header.chain_id = tx_args.chain_id.clone().unwrap(); @@ -566,20 +560,25 @@ pub async fn submit_init_proposal( Ok(()) } else { let signer = ctx.get(&signer); - let tx_data: Result = proposal.clone().try_into(); - let init_proposal_data = if let Ok(data) = tx_data { - data - } else { - eprintln!("Invalid data for init proposal transaction."); - safe_exit(1) - }; + let tx_data = proposal.clone().try_into(); + let (mut init_proposal_data, init_proposal_content, init_proposal_code) = + if let Ok(data) = tx_data { + data + } else { + eprintln!("Invalid data for init proposal transaction."); + safe_exit(1) + }; let balance = rpc::get_token_balance(client, &ctx.native_token, &proposal.author) .await .unwrap_or_default(); if balance - < token::Amount::from(governance_parameters.min_proposal_fund) + < token::Amount::from_uint( + governance_parameters.min_proposal_fund, + 0, + ) + .unwrap() { eprintln!( "Address {} doesn't have enough funds.", @@ -588,7 +587,7 @@ pub async fn submit_init_proposal( safe_exit(1); } - if init_proposal_data.content.len() + if init_proposal_content.len() > governance_parameters.max_proposal_content_size as usize { eprintln!("Proposal content size too big.",); @@ -596,14 +595,28 @@ pub async fn submit_init_proposal( } let mut tx = Tx::new(TxType::Raw); - let data = init_proposal_data - .try_to_vec() - .expect("Encoding proposal data shouldn't fail"); let tx_code_hash = query_wasm_code_hash(client, args::TX_INIT_PROPOSAL) .await .unwrap(); tx.header.chain_id = ctx.config.ledger.chain_id.clone(); tx.header.expiration = args.tx.expiration; + // Put the content of this proposal into an extra section + { + let content_sec = tx.add_section(Section::ExtraData(Code::new( + init_proposal_content, + ))); + init_proposal_data.content = content_sec.get_hash(); + } + // Put any proposal code into an extra section + if let Some(init_proposal_code) = init_proposal_code { + let code_sec = tx + .add_section(Section::ExtraData(Code::new(init_proposal_code))); + init_proposal_data.r#type = + ProposalType::Default(Some(code_sec.get_hash())); + } + let data = init_proposal_data + .try_to_vec() + .expect("Encoding proposal data shouldn't fail"); tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); @@ -655,7 +668,10 @@ pub async fn submit_vote_proposal( ) }) { - set.insert((address, cap.into())); + set.insert(( + address, + token::Amount::from_uint(cap, 0).unwrap(), + )); } ProposalVote::Yay(VoteType::PGFCouncil(set)) @@ -1029,7 +1045,7 @@ pub async fn submit_validator_commission_change< >( client: &C, mut ctx: Context, - mut args: args::TxCommissionRateChange, + mut args: args::CommissionRateChange, ) -> Result<(), tx::Error> { args.tx.chain_id = args .tx @@ -1122,3 +1138,51 @@ pub async fn submit_tx( ) -> Result { tx::submit_tx(client, to_broadcast).await } + +#[cfg(test)] +mod test_tx { + use masp_primitives::transaction::components::Amount; + use namada::ledger::masp::{make_asset_type, MaspAmount}; + use namada::types::address::testing::gen_established_address; + use namada::types::storage::DbKeySeg; + use namada::types::token::MaspDenom; + + use super::*; + + #[test] + fn test_masp_add_amount() { + let address_1 = gen_established_address(); + let prefix_1: Key = + DbKeySeg::StringSeg("eth_seg".parse().unwrap()).into(); + let prefix_2: Key = + DbKeySeg::StringSeg("crypto_kitty".parse().unwrap()).into(); + let denom_1 = MaspDenom::One; + let denom_2 = MaspDenom::Three; + let epoch = Epoch::default(); + let _masp_amount = MaspAmount::default(); + + let asset_base = make_asset_type( + Some(epoch), + &address_1, + &Some(prefix_1.clone()), + denom_1, + ); + let _asset_denom = + make_asset_type(Some(epoch), &address_1, &Some(prefix_1), denom_2); + let _asset_prefix = + make_asset_type(Some(epoch), &address_1, &Some(prefix_2), denom_1); + + let _amount_base = + Amount::from_pair(asset_base, 16).expect("Test failed"); + let _amount_denom = + Amount::from_pair(asset_base, 2).expect("Test failed"); + let _amount_prefix = + Amount::from_pair(asset_base, 4).expect("Test failed"); + + // masp_amount += amount_base; + // assert_eq!(masp_amount.get((epoch,)), Uint::zero()); + // Amount::from_pair(atype, amount) + // MaspDenom::One + // assert_eq!(zero.abs(), Uint::zero()); + } +} diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index ee63ecb1d2..95a2a6e11f 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -13,11 +13,11 @@ use flate2::Compression; use namada::ledger::wallet::Wallet; use namada::types::address; use namada::types::chain::ChainId; +use namada::types::dec::Dec; use namada::types::key::*; use prost::bytes::Bytes; use rand::prelude::ThreadRng; use rand::thread_rng; -use rust_decimal::Decimal; use serde_json::json; use sha2::{Digest, Sha256}; @@ -47,6 +47,41 @@ const DEFAULT_NETWORK_CONFIGS_SERVER: &str = /// We do pre-genesis validator set up in this directory pub const PRE_GENESIS_DIR: &str = "pre-genesis"; +/// Environment variable set to reduce the amount of printing the CLI +/// tools perform. Extra prints, while good for UI, clog up test tooling. +pub const REDUCED_CLI_PRINTING: &str = "REDUCED_CLI_PRINTING"; + +macro_rules! cli_print { + ($($arg:tt)*) => {{ + if std::env::var(REDUCED_CLI_PRINTING) + .map(|v| if v.to_lowercase().trim() == "true" { + false + } else { + true + }).unwrap_or(true) { + let mut stdout = std::io::stdout().lock(); + _ = stdout.write_all(format!("{}", std::format_args!($($arg)*)).as_bytes()); + _ = stdout.flush(); + } + }}; +} + +#[allow(unused)] +macro_rules! cli_println { + ($($arg:tt)*) => {{ + if std::env::var(REDUCED_CLI_PRINTING) + .map(|v| if v.to_lowercase().trim() == "true" { + false + } else { + true + }).unwrap_or(true) { + let mut stdout = std::io::stdout().lock(); + _ = stdout.write_all(format!("{}\n", std::format_args!($($arg)*)).as_bytes()); + _ = stdout.flush(); + } + }}; +} + /// Configure Namada to join an existing network. The chain must be released in /// the repository. pub async fn join_network( @@ -924,16 +959,11 @@ pub fn init_genesis_validator( }: args::InitGenesisValidator, ) { // Validate the commission rate data - if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { - eprintln!( - "The validator commission rate must not exceed 1.0 or 100%, and \ - it must be 0 or positive" - ); + if commission_rate > Dec::one() { + eprintln!("The validator commission rate must not exceed 1.0 or 100%"); cli::safe_exit(1) } - if max_commission_rate_change > Decimal::ONE - || max_commission_rate_change < Decimal::ZERO - { + if max_commission_rate_change > Dec::one() { eprintln!( "The validator maximum change in commission rate per epoch must \ not exceed 1.0 or 100%" @@ -1098,6 +1128,30 @@ pub fn validator_pre_genesis_dir(base_dir: &Path, alias: &str) -> PathBuf { base_dir.join(PRE_GENESIS_DIR).join(alias) } +/// Add a spinning wheel to a message for long running commands. +/// Can be turned off for E2E tests by setting the `REDUCED_CLI_PRINTING` +/// environment variable. +pub fn with_spinny_wheel(msg: &str, func: F) -> Out +where + F: FnOnce() -> Out + Send + 'static, + Out: Send + 'static, +{ + let task = std::thread::spawn(func); + let spinny_wheel = "|/-\\"; + print!("{}", msg); + _ = std::io::stdout().flush(); + for c in spinny_wheel.chars().cycle() { + cli_print!("{}", c); + std::thread::sleep(std::time::Duration::from_secs(1)); + cli_print!("{}", (8u8 as char)); + if task.is_finished() { + break; + } + } + println!(); + task.join().unwrap() +} + fn is_valid_validator_for_current_chain( tendermint_node_pk: &common::PublicKey, genesis_config: &GenesisConfig, diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 23cc402eae..904abcd949 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -8,14 +8,14 @@ use derivative::Derivative; use namada::core::ledger::testnet_pow; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::EpochDuration; -use namada::ledger::pos::{GenesisValidator, PosParams}; +use namada::ledger::pos::{Dec, GenesisValidator, PosParams}; use namada::types::address::Address; use namada::types::chain::ProposalBytes; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; use namada::types::time::{DateTimeUtc, DurationSecs}; +use namada::types::token::Denomination; use namada::types::{storage, token}; -use rust_decimal::Decimal; /// Genesis configuration file format pub mod genesis_config { @@ -31,14 +31,14 @@ pub mod genesis_config { use namada::core::ledger::testnet_pow; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::EpochDuration; - use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::ledger::pos::{Dec, GenesisValidator, PosParams}; use namada::types::address::Address; use namada::types::chain::ProposalBytes; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; use namada::types::time::Rfc3339String; + use namada::types::token::Denomination; use namada::types::{storage, token}; - use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -138,22 +138,16 @@ pub mod genesis_config { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct GovernanceParamsConfig { // Min funds to stake to submit a proposal - // XXX: u64 doesn't work with toml-rs! pub min_proposal_fund: u64, // Maximum size of proposal in kibibytes (KiB) - // XXX: u64 doesn't work with toml-rs! pub max_proposal_code_size: u64, // Minimum proposal period length in epochs - // XXX: u64 doesn't work with toml-rs! pub min_proposal_period: u64, // Maximum proposal period length in epochs - // XXX: u64 doesn't work with toml-rs! pub max_proposal_period: u64, // Maximum number of characters in the proposal content - // XXX: u64 doesn't work with toml-rs! pub max_proposal_content_size: u64, // Minimum number of epoch between end and grace epoch - // XXX: u64 doesn't work with toml-rs! pub min_proposal_grace_epochs: u64, } @@ -180,16 +174,14 @@ pub mod genesis_config { // Validator address (default: generate). pub address: Option, // Total number of tokens held at genesis. - // XXX: u64 doesn't work with toml-rs! pub tokens: Option, // Unstaked balance at genesis. - // XXX: u64 doesn't work with toml-rs! pub non_staked_balance: Option, /// Commission rate charged on rewards for delegators (bounded inside /// 0-1) - pub commission_rate: Option, + pub commission_rate: Option, /// Maximum change in commission rate permitted per epoch - pub max_commission_rate_change: Option, + pub max_commission_rate_change: Option, // Filename of validator VP. (default: default validator VP) pub validator_vp: Option, // IP:port of the validator. (used in generation only) @@ -203,11 +195,12 @@ pub mod genesis_config { pub struct TokenAccountConfig { // Address of token account (default: generate). pub address: Option, + // The number of decimal places amounts of this token has + pub denom: Denomination, // Filename of token account VP. (default: token VP) pub vp: Option, // Initial balances held by accounts defined elsewhere. - // XXX: u64 doesn't work with toml-rs! - pub balances: Option>, + pub balances: Option>, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -244,7 +237,6 @@ pub mod genesis_config { /// serialization overhead in Tendermint blocks. pub max_proposal_bytes: ProposalBytes, /// Minimum number of blocks per epoch. - // XXX: u64 doesn't work with toml-rs! pub min_num_of_blocks: u64, /// Maximum duration per block (in seconds). // TODO: this is i64 because datetime wants it @@ -260,9 +252,9 @@ pub mod genesis_config { /// Expected number of epochs per year pub epochs_per_year: u64, /// PoS gain p - pub pos_gain_p: Decimal, + pub pos_gain_p: Dec, /// PoS gain d - pub pos_gain_d: Decimal, + pub pos_gain_d: Dec, #[cfg(not(feature = "mainnet"))] /// Fix wrapper tx fees pub wrapper_tx_fees: Option, @@ -271,39 +263,33 @@ pub mod genesis_config { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct PosParamsConfig { // Maximum number of consensus validators. - // XXX: u64 doesn't work with toml-rs! pub max_validator_slots: u64, // Pipeline length (in epochs). - // XXX: u64 doesn't work with toml-rs! pub pipeline_len: u64, // Unbonding length (in epochs). - // XXX: u64 doesn't work with toml-rs! pub unbonding_len: u64, // Votes per token. - // XXX: u64 doesn't work with toml-rs! - pub tm_votes_per_token: Decimal, + pub tm_votes_per_token: Dec, // Reward for proposing a block. - // XXX: u64 doesn't work with toml-rs! - pub block_proposer_reward: Decimal, + pub block_proposer_reward: Dec, // Reward for voting on a block. - // XXX: u64 doesn't work with toml-rs! - pub block_vote_reward: Decimal, + pub block_vote_reward: Dec, // Maximum staking APY - // XXX: u64 doesn't work with toml-rs! - pub max_inflation_rate: Decimal, + pub max_inflation_rate: Dec, // Target ratio of staked NAM tokens to total NAM tokens - pub target_staked_ratio: Decimal, + pub target_staked_ratio: Dec, // Portion of a validator's stake that should be slashed on a // duplicate vote. - // XXX: u64 doesn't work with toml-rs! - pub duplicate_vote_min_slash_rate: Decimal, + pub duplicate_vote_min_slash_rate: Dec, // Portion of a validator's stake that should be slashed on a // light client attack. - // XXX: u64 doesn't work with toml-rs! - pub light_client_attack_min_slash_rate: Decimal, + pub light_client_attack_min_slash_rate: Dec, /// Number of epochs above and below (separately) the current epoch to /// consider when doing cubic slashing pub cubic_slashing_window_length: u64, + /// The minimum amount of bonded tokens that a validator needs to be in + /// either the `consensus` or `below_capacity` validator sets + pub validator_stake_threshold: token::Amount, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -323,7 +309,9 @@ pub mod genesis_config { pos_data: GenesisValidator { address: Address::decode(config.address.as_ref().unwrap()) .unwrap(), - tokens: token::Amount::whole(config.tokens.unwrap_or_default()), + tokens: token::Amount::native_whole( + config.tokens.unwrap_or_default(), + ), consensus_key: config .consensus_public_key .as_ref() @@ -333,21 +321,13 @@ pub mod genesis_config { commission_rate: config .commission_rate .and_then(|rate| { - if rate >= Decimal::ZERO && rate <= Decimal::ONE { - Some(rate) - } else { - None - } + if rate <= Dec::one() { Some(rate) } else { None } }) .expect("Commission rate must be between 0.0 and 1.0"), max_commission_rate_change: config .max_commission_rate_change .and_then(|rate| { - if rate >= Decimal::ZERO && rate <= Decimal::ONE { - Some(rate) - } else { - None - } + if rate <= Dec::one() { Some(rate) } else { None } }) .expect( "Max commission rate change must be between 0.0 and \ @@ -372,7 +352,7 @@ pub mod genesis_config { .unwrap() .to_dkg_public_key() .unwrap(), - non_staked_balance: token::Amount::whole( + non_staked_balance: token::Amount::native_whole( config.non_staked_balance.unwrap_or_default(), ), validator_vp_code_path: validator_vp_config.filename.to_owned(), @@ -397,6 +377,7 @@ pub mod genesis_config { TokenAccount { address: Address::decode(config.address.as_ref().unwrap()).unwrap(), + denom: config.denom, vp_code_path: token_vp_config.filename.to_owned(), vp_sha256: token_vp_config .sha256 @@ -462,7 +443,9 @@ pub mod genesis_config { } } }, - token::Amount::whole(*amount), + token::Amount::from_uint(*amount, config.denom).expect( + "expected a balance that fits into 256 bits", + ), ) }) .collect(), @@ -613,8 +596,8 @@ pub mod genesis_config { epochs_per_year: parameters.epochs_per_year, pos_gain_p: parameters.pos_gain_p, pos_gain_d: parameters.pos_gain_d, - staked_ratio: Decimal::ZERO, - pos_inflation_amount: 0, + staked_ratio: Dec::zero(), + pos_inflation_amount: token::Amount::zero(), wrapper_tx_fees: parameters.wrapper_tx_fees, }; @@ -647,6 +630,7 @@ pub mod genesis_config { duplicate_vote_min_slash_rate, light_client_attack_min_slash_rate, cubic_slashing_window_length, + validator_stake_threshold, } = pos_params; let pos_params = PosParams { max_validator_slots, @@ -660,6 +644,7 @@ pub mod genesis_config { duplicate_vote_min_slash_rate, light_client_attack_min_slash_rate, cubic_slashing_window_length, + validator_stake_threshold, }; let mut genesis = Genesis { @@ -798,6 +783,8 @@ pub struct EstablishedAccount { pub struct TokenAccount { /// Address pub address: Address, + /// The number of decimal places amounts of this token has + pub denom: Denomination, /// Validity predicate code WASM pub vp_code_path: String, /// Expected SHA-256 hash of the validity predicate wasm @@ -856,13 +843,13 @@ pub struct Parameters { /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// PoS gain p (read only) - pub pos_gain_p: Decimal, + pub pos_gain_p: Dec, /// PoS gain d (read only) - pub pos_gain_d: Decimal, + pub pos_gain_d: Dec, /// PoS staked ratio (read + write for every epoch) - pub staked_ratio: Decimal, + pub staked_ratio: Dec, /// PoS inflation amount from the last epoch (read + write for every epoch) - pub pos_inflation_amount: u64, + pub pos_inflation_amount: token::Amount, /// Fixed Wrapper tx fees #[cfg(not(feature = "mainnet"))] pub wrapper_tx_fees: Option, @@ -883,7 +870,6 @@ pub fn genesis(num_validators: u64) -> Genesis { use namada::types::address::{ self, apfel, btc, dot, eth, kartoffel, nam, schnitzel, }; - use rust_decimal_macros::dec; use crate::wallet; @@ -904,15 +890,16 @@ pub fn genesis(num_validators: u64) -> Genesis { let validator = Validator { pos_data: GenesisValidator { address, - tokens: token::Amount::whole(200_000), + tokens: token::Amount::native_whole(200_000), consensus_key: consensus_keypair.ref_to(), - commission_rate: dec!(0.05), - max_commission_rate_change: dec!(0.01), + commission_rate: Dec::new(5, 2).expect("This can't fail"), + max_commission_rate_change: Dec::new(1, 2) + .expect("This can't fail"), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), dkg_public_key: dkg_keypair.public(), - non_staked_balance: token::Amount::whole(100_000), + non_staked_balance: token::Amount::native_whole(100_000), // TODO replace with https://github.com/anoma/namada/issues/25) validator_vp_code_path: vp_user_path.into(), validator_vp_sha256: Default::default(), @@ -932,15 +919,16 @@ pub fn genesis(num_validators: u64) -> Genesis { let validator = Validator { pos_data: GenesisValidator { address, - tokens: token::Amount::whole(200_000), + tokens: token::Amount::native_whole(200_000), consensus_key: consensus_keypair.ref_to(), - commission_rate: dec!(0.05), - max_commission_rate_change: dec!(0.01), + commission_rate: Dec::new(5, 2).expect("This can't fail"), + max_commission_rate_change: Dec::new(1, 2) + .expect("This can't fail"), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), dkg_public_key: dkg_keypair.public(), - non_staked_balance: token::Amount::whole(100_000), + non_staked_balance: token::Amount::native_whole(100_000), // TODO replace with https://github.com/anoma/namada/issues/25) validator_vp_code_path: vp_user_path.into(), validator_vp_sha256: Default::default(), @@ -961,11 +949,11 @@ pub fn genesis(num_validators: u64) -> Genesis { implicit_vp_sha256: Default::default(), epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds * per epoch (60 = min_duration) */ - pos_gain_p: dec!(0.1), - pos_gain_d: dec!(0.1), - staked_ratio: dec!(0.0), - pos_inflation_amount: 0, - wrapper_tx_fees: Some(token::Amount::whole(0)), + pos_gain_p: Dec::new(1, 1).expect("This can't fail"), + pos_gain_d: Dec::new(1, 1).expect("This can't fail"), + staked_ratio: Dec::zero(), + pos_inflation_amount: token::Amount::zero(), + wrapper_tx_fees: Some(token::Amount::native_whole(0)), }; let albert = EstablishedAccount { address: wallet::defaults::albert_address(), @@ -995,11 +983,16 @@ pub fn genesis(num_validators: u64) -> Genesis { public_key: None, storage: HashMap::default(), }; - let implicit_accounts = vec![ImplicitAccount { - public_key: wallet::defaults::daewon_keypair().ref_to(), - }]; - let default_user_tokens = token::Amount::whole(1_000_000); - let default_key_tokens = token::Amount::whole(1_000); + let implicit_accounts = vec![ + ImplicitAccount { + public_key: wallet::defaults::daewon_keypair().ref_to(), + }, + ImplicitAccount { + public_key: wallet::defaults::ester_keypair().ref_to(), + }, + ]; + let default_user_tokens = token::Amount::native_whole(1_000_000); + let default_key_tokens = token::Amount::native_whole(1_000); let mut balances: HashMap = HashMap::from_iter([ // established accounts' balances (wallet::defaults::albert_address(), default_user_tokens), @@ -1026,23 +1019,24 @@ pub fn genesis(num_validators: u64) -> Genesis { } /// Deprecated function, soon to be deleted. Generates default tokens - fn tokens() -> HashMap { + fn tokens() -> HashMap { vec![ - (nam(), "NAM"), - (btc(), "BTC"), - (eth(), "ETH"), - (dot(), "DOT"), - (schnitzel(), "Schnitzel"), - (apfel(), "Apfel"), - (kartoffel(), "Kartoffel"), + (nam(), ("NAM", 6.into())), + (btc(), ("BTC", 8.into())), + (eth(), ("ETH", 18.into())), + (dot(), ("DOT", 10.into())), + (schnitzel(), ("Schnitzel", 6.into())), + (apfel(), ("Apfel", 6.into())), + (kartoffel(), ("Kartoffel", 6.into())), ] .into_iter() .collect() } let token_accounts = tokens() - .into_keys() - .map(|address| TokenAccount { + .into_iter() + .map(|(address, (_, denom))| TokenAccount { address, + denom, vp_code_path: vp_token_path.into(), vp_sha256: Default::default(), balances: balances.clone(), diff --git a/apps/src/lib/logging.rs b/apps/src/lib/logging.rs index a84d452324..b0188aa6c4 100644 --- a/apps/src/lib/logging.rs +++ b/apps/src/lib/logging.rs @@ -3,6 +3,7 @@ use std::env; use color_eyre::eyre::Result; use eyre::WrapErr; +use tracing_appender::non_blocking::WorkerGuard; use tracing_log::LogTracer; use tracing_subscriber::filter::{Directive, EnvFilter}; use tracing_subscriber::fmt::Subscriber; @@ -13,6 +14,12 @@ pub const ENV_KEY: &str = "NAMADA_LOG"; const COLOR_ENV_KEY: &str = "NAMADA_LOG_COLOR"; // Env var to log formatting (one of "full" (default), "json", "pretty") const FMT_ENV_KEY: &str = "NAMADA_LOG_FMT"; +// Env var to append logs to file(s) in the given dir +const DIR_ENV_KEY: &str = "NAMADA_LOG_DIR"; +// Env var to set rolling log frequency +const ROLLING_ENV_KEY: &str = "NAMADA_LOG_ROLLING"; + +const LOG_FILE_NAME_PREFIX: &str = "namada.log"; #[derive(Clone, Debug)] enum Fmt { @@ -27,10 +34,18 @@ impl Default for Fmt { } } -pub fn init_from_env_or(default: impl Into) -> Result<()> { +/// When logging to a file is enabled, returns a guard that handles flushing of +/// remaining logs on termination. +/// +/// Important: The returned guard, if any, must be assigned to a binding that is +/// not _, as _ will result in the WorkerGuard being dropped immediately. +pub fn init_from_env_or( + default: impl Into, +) -> Result> { let filter = filter_from_env_or(default); - set_subscriber(filter)?; - init_log_tracer() + let guard = set_subscriber(filter)?; + init_log_tracer()?; + Ok(guard) } pub fn filter_from_env_or(default: impl Into) -> EnvFilter { @@ -39,7 +54,11 @@ pub fn filter_from_env_or(default: impl Into) -> EnvFilter { .unwrap_or_else(|_| EnvFilter::default().add_directive(default.into())) } -pub fn set_subscriber(filter: EnvFilter) -> Result<()> { +pub fn init_log_tracer() -> Result<()> { + LogTracer::init().wrap_err("Failed to initialize log adapter") +} + +pub fn set_subscriber(filter: EnvFilter) -> Result> { let with_color = if let Ok(val) = env::var(COLOR_ENV_KEY) { val.to_ascii_lowercase() != "false" } else { @@ -54,28 +73,79 @@ pub fn set_subscriber(filter: EnvFilter) -> Result<()> { _ => None, }) .unwrap_or_default(); + let log_dir = env::var(DIR_ENV_KEY).ok(); + let builder = Subscriber::builder() .with_ansi(with_color) .with_env_filter(filter); - match format { - Fmt::Full => { - let my_collector = builder.with_ansi(with_color).finish(); - tracing::subscriber::set_global_default(my_collector) - .wrap_err("Failed to set log subscriber") - } - Fmt::Json => { - let my_collector = builder.json().finish(); - tracing::subscriber::set_global_default(my_collector) - .wrap_err("Failed to set log subscriber") + + // We're using macros here to help as the `format` match arms and `log_dir` + // if/else branches have incompatible types. + macro_rules! finish { + ($($builder:tt)*) => { + { + let my_collector = $($builder)*.finish(); + tracing::subscriber::set_global_default(my_collector) + .wrap_err("Failed to set log subscriber") + } } - Fmt::Pretty => { - let my_collector = builder.pretty().finish(); - tracing::subscriber::set_global_default(my_collector) - .wrap_err("Failed to set log subscriber") + } + macro_rules! select_format { + ($($builder:tt)*) => { + { + match format { + Fmt::Full => finish!($($builder)*), + Fmt::Json => finish!($($builder)*.json()), + Fmt::Pretty => finish!($($builder)*.pretty()), + } + } } } + + if let Some(dir) = log_dir { + use tracing_appender::rolling::{self, RollingFileAppender}; + + let rolling_fn: fn(_, _) -> RollingFileAppender = match rolling_freq() { + RollingFreq::Never => rolling::never, + RollingFreq::Minutely => rolling::minutely, + RollingFreq::Hourly => rolling::hourly, + RollingFreq::Daily => rolling::daily, + }; + let file_appender = rolling_fn(dir, LOG_FILE_NAME_PREFIX); + let (non_blocking, guard) = + tracing_appender::non_blocking(file_appender); + let builder = builder.with_writer(non_blocking); + select_format!(builder)?; + Ok(Some(guard)) + } else { + select_format!(builder)?; + Ok(None) + } } -pub fn init_log_tracer() -> Result<()> { - LogTracer::init().wrap_err("Failed to initialize log adapter") +enum RollingFreq { + Never, + Minutely, + Hourly, + Daily, +} + +/// Get the rolling frequency from env var or default to `Never`. +fn rolling_freq() -> RollingFreq { + if let Ok(freq) = env::var(ROLLING_ENV_KEY) { + match freq.to_ascii_lowercase().as_str() { + "never" => RollingFreq::Never, + "minutely" => RollingFreq::Minutely, + "hourly" => RollingFreq::Hourly, + "daily" => RollingFreq::Daily, + _ => { + panic!( + "Unrecognized option set for {ROLLING_ENV_KEY}. Expecing \ + one of: never, minutely, hourly, daily. Default is never." + ); + } + } + } else { + RollingFreq::Never + } } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index f1e3e53ceb..e740613d3e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use data_encoding::HEXUPPER; use namada::ledger::parameters::storage as params_storage; -use namada::ledger::pos::types::{decimal_mult_u64, into_tm_voting_power}; +use namada::ledger::pos::types::into_tm_voting_power; use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; use namada::ledger::storage_api::token::credit_tokens; @@ -18,10 +18,10 @@ use namada::proof_of_stake::{ write_last_block_proposer_address, }; use namada::types::address::Address; +use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::token::{total_supply_key, Amount}; -use rust_decimal::prelude::Decimal; use super::governance::execute_governance_proposals; use super::*; @@ -107,8 +107,10 @@ where } // Invariant: This has to be applied after - // `copy_validator_sets_and_positions` if we're starting a new epoch + // `copy_validator_sets_and_positions` and before `self.update_epoch`. self.record_slashes_from_evidence(); + // Invariant: This has to be applied after + // `copy_validator_sets_and_positions` if we're starting a new epoch if new_epoch { self.process_slashes(); } @@ -157,7 +159,7 @@ where continue; } - let tx = if let Ok(()) = tx.validate_header() { + let tx = if tx.validate_tx().is_ok() { tx } else { tracing::error!( @@ -268,7 +270,9 @@ where .storage .write( &balance_key, - Amount::from(0).try_to_vec().unwrap(), + Amount::native_whole(0) + .try_to_vec() + .unwrap(), ) .unwrap(); tx_event["info"] = @@ -307,7 +311,7 @@ where DecryptedTx::Decrypted { has_valid_pow: _ } => { if let Some(code_sec) = tx .get_section(tx.code_sechash()) - .and_then(Section::code_sec) + .and_then(|x| Section::code_sec(x.as_ref())) { stats.increment_tx_type( code_sec.code.hash().to_string(), @@ -315,9 +319,15 @@ where } } DecryptedTx::Undecryptable => { + tracing::info!( + "Tx with hash {} was un-decryptable", + wrapper_hash + ); + event["info"] = "Transaction is invalid.".into(); event["log"] = "Transaction could not be decrypted.".into(); event["code"] = ErrorCodes::Undecryptable.into(); + continue; } } (event, Some(wrapper_hash)) @@ -624,19 +634,19 @@ where let epochs_per_year: u64 = self .read_storage_key(¶ms_storage::get_epochs_per_year_key()) .expect("Epochs per year should exist in storage"); - let pos_p_gain_nom: Decimal = self + let pos_p_gain_nom: Dec = self .read_storage_key(¶ms_storage::get_pos_gain_p_key()) .expect("PoS P-gain factor should exist in storage"); - let pos_d_gain_nom: Decimal = self + let pos_d_gain_nom: Dec = self .read_storage_key(¶ms_storage::get_pos_gain_d_key()) .expect("PoS D-gain factor should exist in storage"); - let pos_last_staked_ratio: Decimal = self + let pos_last_staked_ratio: Dec = self .read_storage_key(¶ms_storage::get_staked_ratio_key()) .expect("PoS staked ratio should exist in storage"); - let pos_last_inflation_amount: u64 = self + let pos_last_inflation_amount: token::Amount = self .read_storage_key(¶ms_storage::get_pos_inflation_amount_key()) - .expect("PoS inflation rate should exist in storage"); + .expect("PoS inflation amount should exist in storage"); // Read from PoS storage let total_tokens = self .read_storage_key(&total_supply_key(&staking_token_address( @@ -650,12 +660,12 @@ where // TODO: properly fetch these values (arbitrary for now) let masp_locked_supply: Amount = Amount::default(); - let masp_locked_ratio_target = Decimal::new(5, 1); - let masp_locked_ratio_last = Decimal::new(5, 1); - let masp_max_inflation_rate = Decimal::new(2, 1); - let masp_last_inflation_rate = Decimal::new(12, 2); - let masp_p_gain = Decimal::new(1, 1); - let masp_d_gain = Decimal::new(1, 1); + let masp_locked_ratio_target = Dec::new(5, 1).expect("Cannot fail"); + let masp_locked_ratio_last = Dec::new(5, 1).expect("Cannot fail"); + let masp_max_inflation_rate = Dec::new(2, 1).expect("Cannot fail"); + let masp_last_inflation_rate = Dec::new(12, 2).expect("Cannot fail"); + let masp_p_gain = Dec::new(1, 1).expect("Cannot fail"); + let masp_d_gain = Dec::new(1, 1).expect("Cannot fail"); // Run rewards PD controller let pos_controller = inflation::RewardsController { @@ -664,9 +674,7 @@ where locked_ratio_target: pos_locked_ratio_target, locked_ratio_last: pos_last_staked_ratio, max_reward_rate: pos_max_inflation_rate, - last_inflation_amount: token::Amount::from( - pos_last_inflation_amount, - ), + last_inflation_amount: pos_last_inflation_amount, p_gain_nom: pos_p_gain_nom, d_gain_nom: pos_d_gain_nom, epochs_per_year, @@ -711,15 +719,14 @@ where // // TODO: think about changing the reward to Decimal let mut reward_tokens_remaining = inflation; - let mut new_rewards_products: HashMap = + let mut new_rewards_products: HashMap = HashMap::new(); for acc in rewards_accumulator_handle().iter(&self.wl_storage)? { let (address, value) = acc?; // Get reward token amount for this validator - let fractional_claim = - value / Decimal::from(num_blocks_in_last_epoch); - let reward = decimal_mult_u64(fractional_claim, inflation); + let fractional_claim = value / num_blocks_in_last_epoch; + let reward = fractional_claim * inflation; // Get validator data at the last epoch let stake = read_validator_stake( @@ -728,25 +735,25 @@ where &address, last_epoch, )? - .map(Decimal::from) + .map(Dec::from) .unwrap_or_default(); let last_rewards_product = validator_rewards_products_handle(&address) .get(&self.wl_storage, &last_epoch)? - .unwrap_or(Decimal::ONE); + .unwrap_or_else(Dec::one); let last_delegation_product = delegator_rewards_products_handle(&address) .get(&self.wl_storage, &last_epoch)? - .unwrap_or(Decimal::ONE); + .unwrap_or_else(Dec::one); let commission_rate = validator_commission_rate_handle(&address) .get(&self.wl_storage, last_epoch, ¶ms)? .expect("Should be able to find validator commission rate"); - let new_product = last_rewards_product - * (Decimal::ONE + Decimal::from(reward) / stake); + let new_product = + last_rewards_product * (Dec::one() + Dec::from(reward) / stake); let new_delegation_product = last_delegation_product - * (Decimal::ONE - + (Decimal::ONE - commission_rate) * Decimal::from(reward) + * (Dec::one() + + (Dec::one() - commission_rate) * Dec::from(reward) / stake); new_rewards_products .insert(address, (new_product, new_delegation_product)); @@ -772,11 +779,11 @@ where let staking_token = staking_token_address(&self.wl_storage); // Mint tokens to the PoS account for the last epoch's inflation - let pos_reward_tokens = - Amount::from(inflation - reward_tokens_remaining); + let pos_reward_tokens = inflation - reward_tokens_remaining; tracing::info!( "Minting tokens for PoS rewards distribution into the PoS \ - account. Amount: {pos_reward_tokens}.", + account. Amount: {}.", + pos_reward_tokens.to_string_native(), ); credit_tokens( &mut self.wl_storage, @@ -785,11 +792,12 @@ where pos_reward_tokens, )?; - if reward_tokens_remaining > 0 { - let amount = Amount::from(reward_tokens_remaining); + if reward_tokens_remaining > token::Amount::zero() { + let amount = Amount::from_uint(reward_tokens_remaining, 0).unwrap(); tracing::info!( "Minting tokens remaining from PoS rewards distribution into \ - the Governance account. Amount: {amount}.", + the Governance account. Amount: {}.", + amount.to_string_native() ); credit_tokens( &mut self.wl_storage, @@ -907,8 +915,7 @@ mod test_finalize_block { is_validator_slashes_key, slashes_prefix, }; use namada::proof_of_stake::types::{ - decimal_mult_amount, BondId, SlashType, ValidatorState, - WeightedValidator, + BondId, SlashType, ValidatorState, WeightedValidator, }; use namada::proof_of_stake::{ enqueued_slashes_handle, get_num_consensus_validators, @@ -918,17 +925,19 @@ mod test_finalize_block { validator_slashes_handle, validator_state_handle, write_pos_params, }; use namada::proto::{Code, Data, Section, Signature}; + use namada::types::dec::POS_DECIMAL_PRECISION; use namada::types::governance::ProposalVote; + use namada::types::hash::Hash; use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; use namada::types::time::DurationSecs; - use namada::types::token::Amount; + use namada::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; use namada::types::transaction::{Fee, WrapperTx, MIN_FEE}; + use namada::types::uint::Uint; use namada_test_utils::TestWasms; - use rust_decimal_macros::dec; use test_log::test; use super::*; @@ -958,7 +967,10 @@ mod test_finalize_block { shell .wl_storage .storage - .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .write( + &balance_key, + Amount::native_whole(1000).try_to_vec().unwrap(), + ) .unwrap(); // create some wrapper txs @@ -971,7 +983,7 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -980,11 +992,11 @@ mod test_finalize_block { wrapper.set_code(Code::new( format!("transaction data: {}", i).as_bytes().to_owned(), )); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); if i > 1 { processed_txs.push(ProcessedTx { tx: wrapper.to_bytes(), @@ -1041,12 +1053,12 @@ mod test_finalize_block { let keypair = gen_keypair(); let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1096,12 +1108,12 @@ mod test_finalize_block { // not valid tx bytes let wrapper = WrapperTx { fee: Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, pk: keypair.ref_to(), epoch: Epoch(0), - gas_limit: 0.into(), + gas_limit: Default::default(), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; @@ -1153,7 +1165,10 @@ mod test_finalize_block { shell .wl_storage .storage - .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .write( + &balance_key, + Amount::native_whole(1000).try_to_vec().unwrap(), + ) .unwrap(); // create two decrypted txs @@ -1167,7 +1182,7 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1204,7 +1219,7 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1215,11 +1230,14 @@ mod test_finalize_block { .as_bytes() .to_owned(), )); + wrapper_tx.encrypt(&Default::default()); wrapper_tx.add_section(Section::Signature(Signature::new( - &wrapper_tx.header_hash(), + vec![ + wrapper_tx.header_hash(), + wrapper_tx.sections[0].get_hash(), + ], &keypair, ))); - wrapper_tx.encrypt(&Default::default()); valid_txs.push(wrapper_tx.clone()); processed_txs.push(ProcessedTx { tx: wrapper_tx.to_bytes(), @@ -1302,7 +1320,7 @@ mod test_finalize_block { let proposal = InitProposalData { id: Some(proposal_id), - content: vec![], + content: Hash::default(), author: validator.clone(), voting_start_epoch: Epoch::default(), voting_end_epoch: Epoch::default().next(), @@ -1313,6 +1331,8 @@ mod test_finalize_block { storage_api::governance::init_proposal( &mut shell.wl_storage, proposal, + vec![], + None, ) .unwrap(); @@ -1345,7 +1365,7 @@ mod test_finalize_block { .wl_storage .storage .db - .iter_optional_prefix(None) + .iter_prefix(None) .map(|(key, val, _gas)| (key, val)) .collect() }; @@ -1380,7 +1400,7 @@ mod test_finalize_block { let votes = vec![VoteInfo { validator: Some(Validator { address: proposer_address.clone(), - power: u64::from(val_stake) as i64, + power: u128::try_from(val_stake).expect("Test failed") as i64, }), signed_last_block: true, }]; @@ -1458,28 +1478,36 @@ mod test_finalize_block { VoteInfo { validator: Some(Validator { address: pkh1.clone(), - power: u64::from(val1.bonded_stake) as i64, + power: u128::try_from(val1.bonded_stake) + .expect("Test failed") + as i64, }), signed_last_block: true, }, VoteInfo { validator: Some(Validator { address: pkh2.clone(), - power: u64::from(val2.bonded_stake) as i64, + power: u128::try_from(val2.bonded_stake) + .expect("Test failed") + as i64, }), signed_last_block: true, }, VoteInfo { validator: Some(Validator { address: pkh3.clone(), - power: u64::from(val3.bonded_stake) as i64, + power: u128::try_from(val3.bonded_stake) + .expect("Test failed") + as i64, }), signed_last_block: true, }, VoteInfo { validator: Some(Validator { address: pkh4.clone(), - power: u64::from(val4.bonded_stake) as i64, + power: u128::try_from(val4.bonded_stake) + .expect("Test failed") + as i64, }), signed_last_block: true, }, @@ -1490,18 +1518,18 @@ mod test_finalize_block { let rewards_prod_3 = validator_rewards_products_handle(&val3.address); let rewards_prod_4 = validator_rewards_products_handle(&val4.address); - let is_decimal_equal_enough = - |target: Decimal, to_compare: Decimal| -> bool { - // also return false if to_compare > target since this should - // never happen for the use cases - if to_compare < target { - let tolerance = Decimal::new(1, 9); - let res = Decimal::ONE - to_compare / target; - res < tolerance - } else { - to_compare == target - } - }; + let is_decimal_equal_enough = |target: Dec, to_compare: Dec| -> bool { + // also return false if to_compare > target since this should + // never happen for the use cases + if to_compare < target { + let tolerance = Dec::new(1, POS_DECIMAL_PRECISION / 2) + .expect("Dec creation failed"); + let res = Dec::one() - to_compare / target; + res < tolerance + } else { + to_compare == target + } + }; // NOTE: Want to manually set the block proposer and the vote // information in a FinalizeBlock object. In non-abcipp mode, @@ -1534,7 +1562,7 @@ mod test_finalize_block { // Val1 was the proposer, so its reward should be larger than all // others, which should themselves all be equal let acc_sum = get_rewards_sum(&shell.wl_storage); - assert!(is_decimal_equal_enough(Decimal::ONE, acc_sum)); + assert!(is_decimal_equal_enough(Dec::one(), acc_sum)); let acc = get_rewards_acc(&shell.wl_storage); assert_eq!(acc.get(&val2.address), acc.get(&val3.address)); assert_eq!(acc.get(&val2.address), acc.get(&val4.address)); @@ -1553,7 +1581,7 @@ mod test_finalize_block { // should be the same as val1 now. Val3 and val4 should be equal as // well. let acc_sum = get_rewards_sum(&shell.wl_storage); - assert!(is_decimal_equal_enough(Decimal::TWO, acc_sum)); + assert!(is_decimal_equal_enough(Dec::two(), acc_sum)); let acc = get_rewards_acc(&shell.wl_storage); assert_eq!(acc.get(&val1.address), acc.get(&val2.address)); assert_eq!(acc.get(&val3.address), acc.get(&val4.address)); @@ -1567,28 +1595,36 @@ mod test_finalize_block { VoteInfo { validator: Some(Validator { address: pkh1.clone(), - power: u64::from(val1.bonded_stake) as i64, + power: u128::try_from(val1.bonded_stake) + .expect("Test failed") + as i64, }), signed_last_block: true, }, VoteInfo { validator: Some(Validator { address: pkh2, - power: u64::from(val2.bonded_stake) as i64, + power: u128::try_from(val2.bonded_stake) + .expect("Test failed") + as i64, }), signed_last_block: true, }, VoteInfo { validator: Some(Validator { address: pkh3, - power: u64::from(val3.bonded_stake) as i64, + power: u128::try_from(val3.bonded_stake) + .expect("Test failed") + as i64, }), signed_last_block: true, }, VoteInfo { validator: Some(Validator { address: pkh4, - power: u64::from(val4.bonded_stake) as i64, + power: u128::try_from(val4.bonded_stake) + .expect("Test failed") + as i64, }), signed_last_block: false, }, @@ -1602,7 +1638,7 @@ mod test_finalize_block { assert!(rewards_prod_3.is_empty(&shell.wl_storage).unwrap()); assert!(rewards_prod_4.is_empty(&shell.wl_storage).unwrap()); let acc_sum = get_rewards_sum(&shell.wl_storage); - assert!(is_decimal_equal_enough(dec!(3), acc_sum)); + assert!(is_decimal_equal_enough(Dec::new(3, 0).unwrap(), acc_sum)); let acc = get_rewards_acc(&shell.wl_storage); assert!( acc.get(&val1.address).cloned().unwrap() @@ -1662,7 +1698,7 @@ mod test_finalize_block { assert!(rp3 > rp4); } - fn get_rewards_acc(storage: &S) -> HashMap + fn get_rewards_acc(storage: &S) -> HashMap where S: StorageRead, { @@ -1670,18 +1706,18 @@ mod test_finalize_block { .iter(storage) .unwrap() .map(|elem| elem.unwrap()) - .collect::>() + .collect::>() } - fn get_rewards_sum(storage: &S) -> Decimal + fn get_rewards_sum(storage: &S) -> Dec where S: StorageRead, { let acc = get_rewards_acc(storage); if acc.is_empty() { - Decimal::ZERO + Dec::zero() } else { - acc.iter().fold(Decimal::default(), |sum, elm| sum + *elm.1) + acc.iter().fold(Dec::zero(), |sum, elm| sum + *elm.1) } } @@ -1732,7 +1768,7 @@ mod test_finalize_block { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -2029,8 +2065,8 @@ mod test_finalize_block { // Each validator has equal weight in this test, and two have been // slashed - let frac = dec!(2) / dec!(7); - let cubic_rate = dec!(9) * frac * frac; + let frac = Dec::two() / Dec::new(7, 0).unwrap(); + let cubic_rate = Dec::new(9, 0).unwrap() * frac * frac; assert_eq!(slash1.rate, cubic_rate); assert_eq!(slash2.rate, cubic_rate); @@ -2082,10 +2118,10 @@ mod test_finalize_block { let total_stake = read_total_stake(&shell.wl_storage, ¶ms, pipeline_epoch)?; - let expected_slashed = decimal_mult_amount(cubic_rate, initial_stake); + let expected_slashed = cubic_rate * initial_stake; assert_eq!(stake1, initial_stake - expected_slashed); assert_eq!(stake2, initial_stake - expected_slashed); - assert_eq!(total_stake, total_initial_stake - 2 * expected_slashed); + assert_eq!(total_stake, total_initial_stake - 2u64 * expected_slashed); // Unjail one of the validators let current_epoch = shell.wl_storage.storage.block.epoch; @@ -2219,13 +2255,13 @@ mod test_finalize_block { // Make an account with balance and delegate some tokens let delegator = address::testing::gen_implicit_address(); - let del_1_amount = token::Amount::whole(67_231); + let del_1_amount = token::Amount::native_whole(67_231); let staking_token = shell.wl_storage.storage.native_token.clone(); credit_tokens( &mut shell.wl_storage, &staking_token, &delegator, - token::Amount::whole(200_000), + token::Amount::native_whole(200_000), ) .unwrap(); namada_proof_of_stake::bond_tokens( @@ -2238,7 +2274,7 @@ mod test_finalize_block { .unwrap(); // Self-unbond - let self_unbond_1_amount = token::Amount::whole(154_654); + let self_unbond_1_amount = token::Amount::native_whole(154_654); namada_proof_of_stake::unbond_tokens( &mut shell.wl_storage, None, @@ -2281,7 +2317,7 @@ mod test_finalize_block { ); let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); println!("\nUnbonding in epoch 2"); - let del_unbond_1_amount = token::Amount::whole(18_000); + let del_unbond_1_amount = token::Amount::native_whole(18_000); namada_proof_of_stake::unbond_tokens( &mut shell.wl_storage, Some(&delegator), @@ -2327,7 +2363,7 @@ mod test_finalize_block { let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); println!("\nBonding in epoch 3"); - let self_bond_1_amount = token::Amount::whole(9_123); + let self_bond_1_amount = token::Amount::native_whole(9_123); namada_proof_of_stake::bond_tokens( &mut shell.wl_storage, None, @@ -2346,7 +2382,7 @@ mod test_finalize_block { let current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None); assert_eq!(current_epoch.0, 4_u64); - let self_unbond_2_amount = token::Amount::whole(15_000); + let self_unbond_2_amount = token::Amount::native_whole(15_000); namada_proof_of_stake::unbond_tokens( &mut shell.wl_storage, None, @@ -2367,7 +2403,7 @@ mod test_finalize_block { println!("Delegating in epoch 5"); // Delegate - let del_2_amount = token::Amount::whole(8_144); + let del_2_amount = token::Amount::native_whole(8_144); namada_proof_of_stake::bond_tokens( &mut shell.wl_storage, Some(&delegator), @@ -2432,7 +2468,7 @@ mod test_finalize_block { .unwrap(); assert_eq!(enqueued_slash.epoch, misbehavior_epoch); assert_eq!(enqueued_slash.r#type, SlashType::DuplicateVote); - assert_eq!(enqueued_slash.rate, Decimal::ZERO); + assert_eq!(enqueued_slash.rate, Dec::zero()); let last_slash = namada_proof_of_stake::read_validator_last_slash_epoch( &shell.wl_storage, @@ -2583,16 +2619,18 @@ mod test_finalize_block { ) .unwrap(); - let vp_frac_3 = Decimal::from(val_stake_3) / Decimal::from(tot_stake_3); - let vp_frac_4 = Decimal::from(val_stake_4) / Decimal::from(tot_stake_4); - let tot_frac = dec!(2) * vp_frac_3 + vp_frac_4; - let cubic_rate = - std::cmp::min(Decimal::ONE, dec!(9) * tot_frac * tot_frac); + let vp_frac_3 = Dec::from(val_stake_3) / Dec::from(tot_stake_3); + let vp_frac_4 = Dec::from(val_stake_4) / Dec::from(tot_stake_4); + let tot_frac = Dec::two() * vp_frac_3 + vp_frac_4; + let cubic_rate = std::cmp::min( + Dec::one(), + Dec::new(9, 0).unwrap() * tot_frac * tot_frac, + ); dbg!(&cubic_rate); - let equal_enough = |rate1: Decimal, rate2: Decimal| -> bool { - let tolerance = dec!(0.000000001); - (rate1 - rate2).abs() < tolerance + let equal_enough = |rate1: Dec, rate2: Dec| -> bool { + let tolerance = Dec::new(1, 9).unwrap(); + rate1.abs_diff(&rate2) < tolerance }; // There should be 2 slashes processed for the validator, each with rate @@ -2621,32 +2659,35 @@ mod test_finalize_block { // self_unbond_2_amount` (since this self-bond was enacted then unbonded // all after the infraction). Thus, the additional deltas to be // deducted is the (infraction stake - this) * rate - let slash_rate_3 = std::cmp::min(Decimal::ONE, dec!(2) * cubic_rate); - let exp_slashed_during_processing_9 = decimal_mult_amount( - slash_rate_3, - initial_stake + del_1_amount + let slash_rate_3 = std::cmp::min(Dec::one(), Dec::two() * cubic_rate); + let exp_slashed_during_processing_9 = slash_rate_3 + * (initial_stake + del_1_amount - self_unbond_1_amount - del_unbond_1_amount + self_bond_1_amount - - self_unbond_2_amount, - ); - assert_eq!( - pre_stake_10 - post_stake_10, - exp_slashed_during_processing_9 + - self_unbond_2_amount); + assert!( + ((pre_stake_10 - post_stake_10).change() + - exp_slashed_during_processing_9.change()) + .abs() + < Uint::from(1000) ); // Check that we can compute the stake at the pipeline epoch // NOTE: may be off. by 1 namnam due to rounding; - let exp_pipeline_stake = decimal_mult_amount( - Decimal::ONE - slash_rate_3, - initial_stake + del_1_amount - - self_unbond_1_amount - - del_unbond_1_amount - + self_bond_1_amount - - self_unbond_2_amount, - ) + del_2_amount; + let exp_pipeline_stake = (Dec::one() - slash_rate_3) + * Dec::from( + initial_stake + del_1_amount + - self_unbond_1_amount + - del_unbond_1_amount + + self_bond_1_amount + - self_unbond_2_amount, + ) + + Dec::from(del_2_amount); + assert!( - (exp_pipeline_stake.change() - post_stake_10.change()).abs() <= 1 + exp_pipeline_stake.abs_diff(&Dec::from(post_stake_10)) + <= Dec::new(1, NATIVE_MAX_DECIMAL_PLACES).unwrap() ); // Check the balance of the Slash Pool @@ -2807,13 +2848,12 @@ mod test_finalize_block { // TODO: decimal mult issues should be resolved with PR 1282 assert!( (del_details.bonds[0].slashed_amount.unwrap().change() - - decimal_mult_amount( - std::cmp::min(Decimal::ONE, dec!(3) * cubic_rate), - del_1_amount - del_unbond_1_amount - ) - .change()) + - std::cmp::min( + Dec::one(), + Dec::new(3, 0).unwrap() * cubic_rate + ) * (del_1_amount.change() - del_unbond_1_amount.change())) .abs() - <= 2 + <= Uint::from(2) ); assert_eq!(del_details.bonds[1].start, Epoch(7)); assert_eq!(del_details.bonds[1].amount, del_2_amount); @@ -2830,13 +2870,18 @@ mod test_finalize_block { // TODO: not sure why this is correct??? (with + self_bond_1_amount - // self_unbond_2_amount) // TODO: Make sure this is sound and what we expect - assert_eq!( - self_details.bonds[0].slashed_amount, - Some(decimal_mult_amount( - std::cmp::min(Decimal::ONE, dec!(3) * cubic_rate), - initial_stake - self_unbond_1_amount + self_bond_1_amount - - self_unbond_2_amount - )) + assert!( + (self_details.bonds[0].slashed_amount.unwrap().change() + - (std::cmp::min( + Dec::one(), + Dec::new(3, 0).unwrap() * cubic_rate + ) * (initial_stake - self_unbond_1_amount + + self_bond_1_amount + - self_unbond_2_amount)) + .change()) + <= Amount::from_uint(1000, NATIVE_MAX_DECIMAL_PLACES) + .unwrap() + .change() ); // Check delegation unbonds @@ -2846,13 +2891,11 @@ mod test_finalize_block { assert_eq!(del_details.unbonds[0].amount, del_unbond_1_amount); assert!( (del_details.unbonds[0].slashed_amount.unwrap().change() - - decimal_mult_amount( - std::cmp::min(Decimal::ONE, dec!(2) * cubic_rate), - del_unbond_1_amount - ) - .change()) + - (std::cmp::min(Dec::one(), Dec::two() * cubic_rate) + * del_unbond_1_amount) + .change()) .abs() - <= 1 + <= Uint::from(1) ); // Check self-unbonds @@ -2871,10 +2914,10 @@ mod test_finalize_block { ); assert_eq!( self_details.unbonds[1].slashed_amount, - Some(decimal_mult_amount( - std::cmp::min(Decimal::ONE, dec!(3) * cubic_rate), - self_unbond_2_amount - self_bond_1_amount - )) + Some( + std::cmp::min(Dec::one(), Dec::new(3, 0).unwrap() * cubic_rate) + * (self_unbond_2_amount - self_bond_1_amount) + ) ); assert_eq!(self_details.unbonds[2].amount, self_bond_1_amount); assert_eq!(self_details.unbonds[2].slashed_amount, None); @@ -2892,7 +2935,7 @@ mod test_finalize_block { .unwrap(); let exp_del_withdraw_slashed_amount = - decimal_mult_amount(slash_rate_3, del_unbond_1_amount); + slash_rate_3 * del_unbond_1_amount; assert_eq!( del_withdraw, del_unbond_1_amount - exp_del_withdraw_slashed_amount @@ -2967,7 +3010,7 @@ mod test_finalize_block { VoteInfo { validator: Some(Validator { address: pkh, - power: u64::from(val.bonded_stake) as i64, + power: u128::try_from(val.bonded_stake).unwrap() as i64, }), signed_last_block: true, } diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 9ff5ebc279..df0fa1ec5e 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -68,7 +68,8 @@ where let total_stake = read_total_stake(&shell.wl_storage, ¶ms, proposal_end_epoch) .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; - let total_stake = VotePower::from(u64::from(total_stake)); + let total_stake = VotePower::try_from(total_stake) + .expect("Voting power exceeds NAM supply"); let tally_result = compute_tally(votes, total_stake, &proposal_type) .map_err(|msg| Error::BadProposal(id, msg.to_string()))? .result; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index e7ab680e8a..cf7eda3884 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -7,12 +7,12 @@ use namada::core::ledger::testnet_pow; use namada::ledger::parameters::{self, Parameters}; use namada::ledger::pos::{into_tm_voting_power, staking_token_address}; use namada::ledger::storage_api::token::{ - credit_tokens, read_balance, read_total_supply, + credit_tokens, read_balance, read_total_supply, write_denom, }; use namada::ledger::storage_api::{ResultExt, StorageRead, StorageWrite}; +use namada::types::dec::Dec; use namada::types::hash::Hash as CodeHash; use namada::types::key::*; -use rust_decimal::Decimal; use super::*; use crate::facade::tendermint_proto::abci; @@ -251,7 +251,7 @@ where // withdrawal limit defaults to 1000 NAM when not set let withdrawal_limit = genesis .faucet_withdrawal_limit - .unwrap_or_else(|| token::Amount::whole(1_000)); + .unwrap_or_else(|| token::Amount::native_whole(1_000)); testnet_pow::init_faucet_storage( &mut self.wl_storage, &address, @@ -273,11 +273,21 @@ where // Initialize genesis token accounts for genesis::TokenAccount { address, + denom, vp_code_path, vp_sha256, balances, } in genesis.token_accounts { + // associate a token with its denomination. + write_denom( + &mut self.wl_storage, + &address, + // TODO: Should we support multi-tokens at genesis? + None, + denom, + ) + .unwrap(); let vp_code_hash = read_wasm_hash(&self.wl_storage, vp_code_path.clone())?.ok_or( Error::LoadingWasm(format!( @@ -386,13 +396,19 @@ where read_balance(&self.wl_storage, &staking_token, &address::POS) .unwrap(); - tracing::info!("Genesis total native tokens: {total_nam}."); - tracing::info!("Total staked tokens: {total_staked_nam}."); + tracing::info!( + "Genesis total native tokens: {}.", + total_nam.to_string_native() + ); + tracing::info!( + "Total staked tokens: {}.", + total_staked_nam.to_string_native() + ); // Set the ratio of staked to total NAM tokens in the parameters storage parameters::update_staked_ratio_parameter( &mut self.wl_storage, - &(Decimal::from(total_staked_nam) / Decimal::from(total_nam)), + &(Dec::from(total_staked_nam) / Dec::from(total_nam)), ) .expect("unable to set staked ratio of NAM in storage"); @@ -479,7 +495,7 @@ mod test { .wl_storage .storage .db - .iter_optional_prefix(None) + .iter_prefix(None) .map(|(key, val, _gas)| (key, val)) .collect() }; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 6797d9a06c..a5ffcef28f 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -31,6 +31,7 @@ use namada::ledger::pos::namada_proof_of_stake::types::{ use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{ DBIter, Sha256Hasher, Storage, StorageHasher, TempWlStorage, WlStorage, DB, + EPOCH_SWITCH_BLOCKS_DELAY, }; use namada::ledger::storage_api::{self, StorageRead, StorageWrite}; use namada::ledger::{ibc, pos, protocol, replay_protection}; @@ -591,9 +592,28 @@ where continue; } }; + // Check if we're gonna switch to a new epoch after a delay + let validator_set_update_epoch = if let Some(delay) = + self.wl_storage.storage.update_epoch_blocks_delay + { + if delay == EPOCH_SWITCH_BLOCKS_DELAY { + // If we're about to update validator sets for the + // upcoming epoch, we can still remove the validator + current_epoch.next() + } else { + // If we're waiting to switch to a new epoch, it's too + // late to update validator sets + // on the next epoch, so we need to + // wait for the one after. + current_epoch.next().next() + } + } else { + current_epoch.next() + }; tracing::info!( "Slashing {} for {} in epoch {}, block height {} (current \ - epoch = {})", + epoch = {}, validator set update epoch = \ + {validator_set_update_epoch})", validator, slash_type, evidence_epoch, @@ -608,6 +628,7 @@ where evidence_height, slash_type, &validator, + validator_set_update_epoch, ) { tracing::error!("Error in slashing: {}", err); } @@ -772,8 +793,8 @@ where } // Tx signature check - let tx_type = match tx.validate_header() { - Ok(()) => tx.header(), + let tx_type = match tx.validate_tx() { + Ok(_) => tx.header(), Err(msg) => { response.code = ErrorCodes::InvalidSig.into(); response.log = msg.to_string(); @@ -961,7 +982,7 @@ where &self.wl_storage, ) .expect("Must be able to read wrapper tx fees parameter"); - fees.unwrap_or(token::Amount::whole(MIN_FEE)) + fees.unwrap_or_else(|| token::Amount::native_whole(MIN_FEE)) } #[cfg(not(feature = "mainnet"))] @@ -1239,12 +1260,12 @@ mod test_utils { // enqueue a wrapper tx let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: native_token, }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1312,7 +1333,7 @@ mod test_mempool_validate { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1349,7 +1370,7 @@ mod test_mempool_validate { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1357,16 +1378,19 @@ mod test_mempool_validate { invalid_wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); invalid_wrapper .set_data(Data::new("transaction data".as_bytes().to_owned())); + invalid_wrapper.encrypt(&Default::default()); invalid_wrapper.add_section(Section::Signature(Signature::new( - &invalid_wrapper.header_hash(), + vec![ + invalid_wrapper.header_hash(), + invalid_wrapper.sections[0].get_hash(), + ], &keypair, ))); - invalid_wrapper.encrypt(&Default::default()); // we mount a malleability attack to try and remove the fee let mut new_wrapper = invalid_wrapper.header().wrapper().expect("Test failed"); - new_wrapper.fee.amount = 0.into(); + new_wrapper.fee.amount = Default::default(); invalid_wrapper.update_header(TxType::Wrapper(Box::new(new_wrapper))); let mut result = shell.mempool_validate( @@ -1409,23 +1433,24 @@ mod test_mempool_validate { let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 100.into(), + amount: token::Amount::from_uint(100, 0) + .expect("This can't fail"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); // Write wrapper hash to storage let wrapper_hash = wrapper.header_hash(); @@ -1517,7 +1542,11 @@ mod test_mempool_validate { tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + vec![ + tx.header_hash(), + tx.sections[0].get_hash(), + tx.sections[1].get_hash(), + ], &keypair, ))); @@ -1548,7 +1577,11 @@ mod test_mempool_validate { tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + vec![ + tx.header_hash(), + tx.sections[0].get_hash(), + tx.sections[1].get_hash(), + ], &keypair, ))); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 9a9aa58491..3a91612e44 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -138,7 +138,7 @@ where if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.header.expiration) { if block_time > exp { return None } } - if tx.validate_header().is_ok() && tx.header().wrapper().is_some() && self.replay_protection_checks(&tx, tx_bytes.as_slice(), &mut temp_wl_storage).is_ok() { + if tx.validate_tx().is_ok() && tx.header().wrapper().is_some() && self.replay_protection_checks(&tx, tx_bytes.as_slice(), &mut temp_wl_storage).is_ok() { return Some(tx_bytes.clone()); } } @@ -320,7 +320,7 @@ mod test_prepare_proposal { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -356,12 +356,12 @@ mod test_prepare_proposal { for i in 0..2 { let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -370,11 +370,11 @@ mod test_prepare_proposal { tx.set_data(Data::new( format!("transaction data: {}", i).as_bytes().to_owned(), )); + tx.encrypt(&Default::default()); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + vec![tx.header_hash(), tx.sections[0].get_hash()], &keypair, ))); - tx.encrypt(&Default::default()); shell.enqueue_tx(tx.clone()); expected_wrapper.push(tx.clone()); @@ -430,18 +430,18 @@ mod test_prepare_proposal { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); // Write wrapper hash to storage let wrapper_unsigned_hash = wrapper.header_hash(); @@ -482,18 +482,18 @@ mod test_prepare_proposal { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); let req = RequestPrepareProposal { txs: vec![wrapper.to_bytes(); 2], @@ -523,18 +523,18 @@ mod test_prepare_proposal { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); let inner_unsigned_hash = wrapper.clone().update_header(TxType::Raw).header_hash(); @@ -576,7 +576,7 @@ mod test_prepare_proposal { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -585,11 +585,11 @@ mod test_prepare_proposal { wrapper.set_code(tx_code.clone()); let tx_data = Data::new("transaction data".as_bytes().to_owned()); wrapper.set_data(tx_data.clone()); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); let mut new_wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( @@ -599,7 +599,7 @@ mod test_prepare_proposal { }, &keypair_2, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -607,11 +607,14 @@ mod test_prepare_proposal { new_wrapper.header.timestamp = wrapper.header.timestamp; new_wrapper.set_code(tx_code); new_wrapper.set_data(tx_data); + new_wrapper.encrypt(&Default::default()); new_wrapper.add_section(Section::Signature(Signature::new( - &new_wrapper.header_hash(), + vec![ + new_wrapper.header_hash(), + new_wrapper.sections[0].get_hash(), + ], &keypair, ))); - new_wrapper.encrypt(&Default::default()); let req = RequestPrepareProposal { txs: vec![wrapper.to_bytes(), new_wrapper.to_bytes()], @@ -641,7 +644,7 @@ mod test_prepare_proposal { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -650,11 +653,11 @@ mod test_prepare_proposal { wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper_tx.encrypt(&Default::default()); wrapper_tx.add_section(Section::Signature(Signature::new( - &wrapper_tx.header_hash(), + vec![wrapper_tx.header_hash(), wrapper_tx.sections[0].get_hash()], &keypair, ))); - wrapper_tx.encrypt(&Default::default()); let time = DateTimeUtc::now(); let block_time = diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 2837ac6600..f81af5bc21 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -185,7 +185,7 @@ where /// 5. More decrypted txs than expected /// 6. A transaction could not be decrypted /// 7. Not enough block space was available for some tx - /// 8: Replay attack + /// 8: Replay attack /// /// INVARIANT: Any changes applied in this method must be reverted if the /// proposal is rejected (unless we can simply overwrite them in the @@ -231,7 +231,7 @@ where |tx| { let tx_chain_id = tx.header.chain_id.clone(); let tx_expiration = tx.header.expiration; - if let Err(err) = tx.validate_header() { + if let Err(err) = tx.validate_tx() { // This occurs if the wrapper / protocol tx signature is // invalid return Err(TxResult { @@ -250,7 +250,7 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - if let Err(err) = tx.validate_header() { + if let Err(err) = tx.validate_tx() { return TxResult { code: ErrorCodes::InvalidSig.into(), info: err.to_string(), @@ -537,12 +537,12 @@ mod test_process_proposal { let keypair = gen_keypair(); let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -582,27 +582,27 @@ mod test_process_proposal { let keypair = gen_keypair(); let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 100.into(), + amount: Amount::from_uint(100, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.encrypt(&Default::default()); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + vec![outer_tx.header_hash(), outer_tx.sections[0].get_hash()], &keypair, ))); - outer_tx.encrypt(&Default::default()); let mut new_tx = outer_tx.clone(); if let TxType::Wrapper(wrapper) = &mut new_tx.header.tx_type { // we mount a malleability attack to try and remove the fee - wrapper.fee.amount = 0.into(); + wrapper.fee.amount = Default::default(); } else { panic!("Test failed") }; @@ -640,29 +640,29 @@ mod test_process_proposal { .write_log .write( &get_wrapper_tx_fees_key(), - token::Amount::whole(MIN_FEE).try_to_vec().unwrap(), + token::Amount::native_whole(MIN_FEE).try_to_vec().unwrap(), ) .unwrap(); let keypair = gen_keypair(); let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 1.into(), + amount: Amount::from_uint(1, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.encrypt(&Default::default()); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + vec![outer_tx.header_hash(), outer_tx.sections[0].get_hash()], &keypair, ))); - outer_tx.encrypt(&Default::default()); let request = ProcessProposal { txs: vec![outer_tx.to_bytes()], @@ -700,36 +700,36 @@ mod test_process_proposal { shell .wl_storage .write_log - .write(&balance_key, Amount::whole(99).try_to_vec().unwrap()) + .write(&balance_key, Amount::native_whole(99).try_to_vec().unwrap()) .unwrap(); shell .wl_storage .write_log .write( &get_wrapper_tx_fees_key(), - token::Amount::whole(MIN_FEE).try_to_vec().unwrap(), + token::Amount::native_whole(MIN_FEE).try_to_vec().unwrap(), ) .unwrap(); let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: Amount::whole(1_000_100), + amount: Amount::native_whole(1_000_100), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.encrypt(&Default::default()); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + vec![outer_tx.header_hash(), outer_tx.sections[0].get_hash()], &keypair, ))); - outer_tx.encrypt(&Default::default()); let request = ProcessProposal { txs: vec![outer_tx.to_bytes()], @@ -769,7 +769,7 @@ mod test_process_proposal { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -823,12 +823,12 @@ mod test_process_proposal { let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -871,12 +871,12 @@ mod test_process_proposal { let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -915,12 +915,12 @@ mod test_process_proposal { // not valid tx bytes let wrapper = WrapperTx { fee: Fee { - amount: 0.into(), + amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, pk: keypair.ref_to(), epoch: Epoch(0), - gas_limit: 0.into(), + gas_limit: Default::default(), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; @@ -1022,23 +1022,23 @@ mod test_process_proposal { let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); // Write wrapper hash to storage let wrapper_unsigned_hash = wrapper.header_hash(); @@ -1089,28 +1089,31 @@ mod test_process_proposal { shell .wl_storage .storage - .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .write( + &balance_key, + Amount::native_whole(1000).try_to_vec().unwrap(), + ) .unwrap(); let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { @@ -1152,23 +1155,23 @@ mod test_process_proposal { let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); let inner_unsigned_hash = wrapper.clone().update_header(TxType::Raw).header_hash(); @@ -1220,7 +1223,10 @@ mod test_process_proposal { shell .wl_storage .storage - .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .write( + &balance_key, + Amount::native_whole(1000).try_to_vec().unwrap(), + ) .unwrap(); // Add unshielded balance for fee payment @@ -1231,17 +1237,20 @@ mod test_process_proposal { shell .wl_storage .storage - .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) + .write( + &balance_key, + Amount::native_whole(1000).try_to_vec().unwrap(), + ) .unwrap(); let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1249,30 +1258,33 @@ mod test_process_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); let mut new_wrapper = wrapper.clone(); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); let inner_unsigned_hash = wrapper.clone().update_header(TxType::Raw).header_hash(); new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair_2, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); + new_wrapper.encrypt(&Default::default()); new_wrapper.add_section(Section::Signature(Signature::new( - &new_wrapper.header_hash(), + vec![ + new_wrapper.header_hash(), + new_wrapper.sections[0].get_hash(), + ], &keypair, ))); - new_wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { @@ -1307,12 +1319,12 @@ mod test_process_proposal { let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1321,18 +1333,22 @@ mod test_process_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); let mut protocol_tx = wrapper.clone(); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); protocol_tx.update_header(TxType::Protocol(Box::new(ProtocolTx { pk: keypair.ref_to(), tx: ProtocolTxType::EthereumStateUpdate, }))); protocol_tx.add_section(Section::Signature(Signature::new( - &protocol_tx.header_hash(), + vec![ + protocol_tx.header_hash(), + protocol_tx.sections[0].get_hash(), + protocol_tx.sections[1].get_hash(), + ], &keypair, ))); @@ -1371,12 +1387,12 @@ mod test_process_proposal { let wrong_chain_id = ChainId("Wrong chain id".to_string()); let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: token::Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1391,7 +1407,11 @@ mod test_process_proposal { has_valid_pow: false, })); decrypted.add_section(Section::Signature(Signature::new( - &decrypted.header_hash(), + vec![ + decrypted.header_hash(), + decrypted.sections[0].get_hash(), + decrypted.sections[1].get_hash(), + ], &keypair, ))); let wrapper_in_queue = TxInQueue { @@ -1432,12 +1452,12 @@ mod test_process_proposal { let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: token::Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1445,11 +1465,11 @@ mod test_process_proposal { wrapper.header.expiration = Some(DateTimeUtc::now()); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { @@ -1475,12 +1495,12 @@ mod test_process_proposal { let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 0.into(), + amount: token::Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -1496,7 +1516,11 @@ mod test_process_proposal { has_valid_pow: false, })); decrypted.add_section(Section::Signature(Signature::new( - &decrypted.header_hash(), + vec![ + decrypted.header_hash(), + decrypted.sections[0].get_hash(), + decrypted.sections[1].get_hash(), + ], &keypair, ))); let wrapper_in_queue = TxInQueue { diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 6108cdf289..c7478fbc75 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -413,7 +413,7 @@ impl RocksDB { let batch = Mutex::new(batch); tracing::info!("Restoring previous hight subspace diffs"); - self.iter_optional_prefix(None).par_bridge().try_for_each( + self.iter_prefix(None).par_bridge().try_for_each( |(key, _value, _gas)| -> Result<()> { // Restore previous height diff if present, otherwise delete the // subspace key @@ -435,6 +435,29 @@ impl RocksDB { }, )?; + // Look for diffs in this block to find what has been deleted + let diff_new_key_prefix = Key { + segments: vec![ + last_block.height.to_db_key(), + "new".to_string().to_db_key(), + ], + }; + { + let mut batch_guard = batch.lock().unwrap(); + let subspace_cf = self.get_column_family(SUBSPACE_CF)?; + for (key, val, _) in + iter_diffs_prefix(self, last_block.height, true) + { + let key = Key::parse(key).unwrap(); + let diff_new_key = diff_new_key_prefix.join(&key); + if self.read_subspace_val(&diff_new_key)?.is_none() { + // If there is no new value, it has been deleted in this + // block and we have to restore it + batch_guard.put_cf(subspace_cf, key.to_string(), val) + } + } + } + tracing::info!("Deleting keys prepended with the last height"); let mut batch = batch.into_inner().unwrap(); let prefix = last_block.height.to_string(); @@ -1243,7 +1266,7 @@ impl DB for RocksDB { impl<'iter> DBIter<'iter> for RocksDB { type PrefixIter = PersistentPrefixIterator<'iter>; - fn iter_optional_prefix( + fn iter_prefix( &'iter self, prefix: Option<&Key>, ) -> PersistentPrefixIterator<'iter> { @@ -1289,11 +1312,8 @@ fn iter_subspace_prefix<'iter>( .get_column_family(SUBSPACE_CF) .expect("{SUBSPACE_CF} column family should exist"); let db_prefix = "".to_owned(); - let prefix_string = match prefix { - Some(prefix) => prefix.to_string(), - None => "".to_string(), - }; - iter_prefix(db, subspace_cf, db_prefix, prefix_string) + let prefix = prefix.map(|k| k.to_string()); + iter_prefix(db, subspace_cf, db_prefix, prefix) } fn iter_diffs_prefix( @@ -1307,20 +1327,23 @@ fn iter_diffs_prefix( let prefix = if is_old { "old" } else { "new" }; let db_prefix = format!("{}/{}/", height.0.raw(), prefix); // get keys without a prefix - iter_prefix(db, diffs_cf, db_prefix.clone(), db_prefix) + iter_prefix(db, diffs_cf, db_prefix.clone(), Some(db_prefix)) } fn iter_prefix<'a>( db: &'a RocksDB, cf: &'a ColumnFamily, db_prefix: String, - prefix: String, + prefix: Option, ) -> PersistentPrefixIterator<'a> { - let read_opts = make_iter_read_opts(Some(prefix.clone())); + let read_opts = make_iter_read_opts(prefix.clone()); let iter = db.0.iterator_cf_opt( cf, read_opts, - IteratorMode::From(prefix.as_bytes(), Direction::Forward), + IteratorMode::From( + prefix.unwrap_or_default().as_bytes(), + Direction::Forward, + ), ); PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) } @@ -1364,8 +1387,8 @@ fn make_iter_read_opts(prefix: Option) -> ReadOptions { let mut upper_prefix = prefix.into_bytes(); if let Some(last) = upper_prefix.pop() { upper_prefix.push(last + 1); + read_opts.set_iterate_upper_bound(upper_prefix); } - read_opts.set_iterate_upper_bound(upper_prefix); } read_opts @@ -1444,6 +1467,7 @@ mod test { use namada::types::address::EstablishedAddressGen; use namada::types::storage::{BlockHash, Epoch, Epochs}; use tempfile::tempdir; + use test_log::test; use super::*; @@ -1463,36 +1487,7 @@ mod test { ) .unwrap(); - let merkle_tree = MerkleTree::::default(); - let merkle_tree_stores = merkle_tree.stores(); - let hash = BlockHash::default(); - let time = DateTimeUtc::now(); - let epoch = Epoch::default(); - let pred_epochs = Epochs::default(); - let height = BlockHeight::default(); - let next_epoch_min_start_height = BlockHeight::default(); - let next_epoch_min_start_time = DateTimeUtc::now(); - let update_epoch_blocks_delay = None; - let address_gen = EstablishedAddressGen::new("whatever"); - let tx_queue = TxQueue::default(); - let results = BlockResults::default(); - let block = BlockStateWrite { - merkle_tree_stores, - header: None, - hash: &hash, - height, - time, - epoch, - results: &results, - pred_epochs: &pred_epochs, - next_epoch_min_start_height, - next_epoch_min_start_time, - update_epoch_blocks_delay, - address_gen: &address_gen, - tx_queue: &tx_queue, - }; - - db.write_block(block, &mut batch, true).unwrap(); + write_block(&mut db, &mut batch, BlockHeight::default()).unwrap(); db.exec_batch(batch.0).unwrap(); let _state = db @@ -1600,4 +1595,170 @@ mod test { db.read_subspace_val(&key).expect("read should succeed"); assert_eq!(latest_value, None); } + + #[test] + fn test_prefix_iter() { + let dir = tempdir().unwrap(); + let mut db = open(dir.path(), None).unwrap(); + + let prefix_0 = Key::parse("0").unwrap(); + let key_0_a = prefix_0.push(&"a".to_string()).unwrap(); + let key_0_b = prefix_0.push(&"b".to_string()).unwrap(); + let key_0_c = prefix_0.push(&"c".to_string()).unwrap(); + let prefix_1 = Key::parse("1").unwrap(); + let key_1_a = prefix_1.push(&"a".to_string()).unwrap(); + let key_1_b = prefix_1.push(&"b".to_string()).unwrap(); + let key_1_c = prefix_1.push(&"c".to_string()).unwrap(); + + let keys_0 = vec![key_0_a.clone(), key_0_b.clone(), key_0_c.clone()]; + let keys_1 = vec![key_1_a.clone(), key_1_b.clone(), key_1_c.clone()]; + let all_keys = vec![keys_0.clone(), keys_1.clone()].concat(); + + // Write the keys + let mut batch = RocksDB::batch(); + let height = BlockHeight(1); + db.batch_write_subspace_val(&mut batch, height, &key_0_a, [0_u8]) + .unwrap(); + db.batch_write_subspace_val(&mut batch, height, &key_0_b, [0_u8]) + .unwrap(); + db.batch_write_subspace_val(&mut batch, height, &key_0_c, [0_u8]) + .unwrap(); + db.batch_write_subspace_val(&mut batch, height, &key_1_a, [0_u8]) + .unwrap(); + db.batch_write_subspace_val(&mut batch, height, &key_1_b, [0_u8]) + .unwrap(); + db.batch_write_subspace_val(&mut batch, height, &key_1_c, [0_u8]) + .unwrap(); + db.exec_batch(batch.0).unwrap(); + + let itered_keys: Vec = db + .iter_prefix(Some(&prefix_0)) + .map(|(key, _val, _)| Key::parse(key).unwrap()) + .collect(); + itertools::assert_equal(keys_0, itered_keys); + + let itered_keys: Vec = db + .iter_prefix(Some(&prefix_1)) + .map(|(key, _val, _)| Key::parse(key).unwrap()) + .collect(); + itertools::assert_equal(keys_1, itered_keys); + + let itered_keys: Vec = db + .iter_prefix(None) + .map(|(key, _val, _)| Key::parse(key).unwrap()) + .collect(); + itertools::assert_equal(all_keys, itered_keys); + } + + #[test] + fn test_rollback() { + let dir = tempdir().unwrap(); + let mut db = open(dir.path(), None).unwrap(); + + // A key that's gonna be added on a second block + let add_key = Key::parse("add").unwrap(); + // A key that's gonna be deleted on a second block + let delete_key = Key::parse("delete").unwrap(); + // A key that's gonna be overwritten on a second block + let overwrite_key = Key::parse("overwrite").unwrap(); + + // Write first block + let mut batch = RocksDB::batch(); + let height_0 = BlockHeight(100); + let to_delete_val = vec![1_u8, 1, 0, 0]; + let to_overwrite_val = vec![1_u8, 1, 1, 0]; + db.batch_write_subspace_val( + &mut batch, + height_0, + &delete_key, + &to_delete_val, + ) + .unwrap(); + db.batch_write_subspace_val( + &mut batch, + height_0, + &overwrite_key, + &to_overwrite_val, + ) + .unwrap(); + + write_block(&mut db, &mut batch, height_0).unwrap(); + db.exec_batch(batch.0).unwrap(); + + // Write second block + let mut batch = RocksDB::batch(); + let height_1 = BlockHeight(101); + let add_val = vec![1_u8, 0, 0, 0]; + let overwrite_val = vec![1_u8, 1, 1, 1]; + db.batch_write_subspace_val(&mut batch, height_1, &add_key, &add_val) + .unwrap(); + db.batch_write_subspace_val( + &mut batch, + height_1, + &overwrite_key, + &overwrite_val, + ) + .unwrap(); + db.batch_delete_subspace_val(&mut batch, height_1, &delete_key) + .unwrap(); + + write_block(&mut db, &mut batch, height_1).unwrap(); + db.exec_batch(batch.0).unwrap(); + + // Check that the values are as expected from second block + let added = db.read_subspace_val(&add_key).unwrap(); + assert_eq!(added, Some(add_val)); + let overwritten = db.read_subspace_val(&overwrite_key).unwrap(); + assert_eq!(overwritten, Some(overwrite_val)); + let deleted = db.read_subspace_val(&delete_key).unwrap(); + assert_eq!(deleted, None); + + // Rollback to the first block height + db.rollback(height_0).unwrap(); + + // Check that the values are back to the state at the first block + let added = db.read_subspace_val(&add_key).unwrap(); + assert_eq!(added, None); + let overwritten = db.read_subspace_val(&overwrite_key).unwrap(); + assert_eq!(overwritten, Some(to_overwrite_val)); + let deleted = db.read_subspace_val(&delete_key).unwrap(); + assert_eq!(deleted, Some(to_delete_val)); + } + + /// A test helper to write a block + fn write_block( + db: &mut RocksDB, + batch: &mut RocksDBWriteBatch, + height: BlockHeight, + ) -> Result<()> { + let merkle_tree = MerkleTree::::default(); + let merkle_tree_stores = merkle_tree.stores(); + let hash = BlockHash::default(); + let time = DateTimeUtc::now(); + let epoch = Epoch::default(); + let pred_epochs = Epochs::default(); + let next_epoch_min_start_height = BlockHeight::default(); + let next_epoch_min_start_time = DateTimeUtc::now(); + let update_epoch_blocks_delay = None; + let address_gen = EstablishedAddressGen::new("whatever"); + let tx_queue = TxQueue::default(); + let results = BlockResults::default(); + let block = BlockStateWrite { + merkle_tree_stores, + header: None, + hash: &hash, + height, + time, + epoch, + results: &results, + pred_epochs: &pred_epochs, + next_epoch_min_start_height, + next_epoch_min_start_time, + update_epoch_blocks_delay, + address_gen: &address_gen, + tx_queue: &tx_queue, + }; + + db.write_block(block, batch, true) + } } diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index c9d37c9e86..aba935012b 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -3,8 +3,9 @@ #[cfg(any(test, feature = "dev"))] pub use dev::{ addresses, albert_address, albert_keypair, bertha_address, bertha_keypair, - christel_address, christel_keypair, daewon_address, daewon_keypair, keys, - validator_address, validator_keypair, validator_keys, + christel_address, christel_keypair, daewon_address, daewon_keypair, + ester_address, ester_keypair, keys, validator_address, validator_keypair, + validator_keys, }; use namada::ledger::wallet::alias::Alias; use namada::ledger::{eth_bridge, governance, pos}; @@ -107,6 +108,7 @@ mod dev { ("bertha".into(), bertha_keypair()), ("christel".into(), christel_keypair()), ("daewon".into(), daewon_keypair()), + ("ester".into(), ester_keypair()), ("validator".into(), validator_keypair()), ] } @@ -137,6 +139,7 @@ mod dev { ("bertha".into(), bertha_address()), ("christel".into(), christel_address()), ("daewon".into(), daewon_address()), + ("ester".into(), ester_address()), ]; let token_addresses = tokens() .into_iter() @@ -166,6 +169,11 @@ mod dev { (&daewon_keypair().ref_to()).into() } + /// An implicit user address for testing & development + pub fn ester_address() -> Address { + (&ester_keypair().ref_to()).into() + } + /// An established validator address for testing & development pub fn validator_address() -> Address { Address::decode("atest1v4ehgw36ggcnsdee8qerswph8y6ry3p5xgunvve3xaqngd3kxc6nqwz9gseyydzzg5unys3ht2n48q").expect("The token address decoding shouldn't fail") @@ -219,6 +227,18 @@ mod dev { ed_sk.try_to_sk().unwrap() } + pub fn ester_keypair() -> common::SecretKey { + // generated from + // [`namada::types::key::secp256k1::gen_keypair`] + let bytes = [ + 54, 144, 147, 226, 3, 93, 132, 247, 42, 126, 90, 23, 200, 155, 122, + 147, 139, 93, 8, 204, 135, 178, 40, 152, 5, 227, 175, 204, 102, + 239, 154, 66, + ]; + let sk = secp256k1::SecretKey::try_from_slice(&bytes).unwrap(); + sk.try_to_sk().unwrap() + } + pub fn validator_keypair() -> common::SecretKey { // generated from // [`namada::types::key::ed25519::gen_keypair`] diff --git a/core/Cargo.toml b/core/Cargo.toml index 45e1bc7976..5cc651b254 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -25,9 +25,9 @@ ferveo-tpke = [ wasm-runtime = [ "rayon", ] -# secp256k1 key signing and verification, disabled in WASM build by default as -# it bloats the build a lot -secp256k1-sign-verify = [ +# secp256k1 key signing, disabled in WASM build by default as it bloats the +# build a lot +secp256k1-sign = [ "libsecp256k1/hmac", ] @@ -62,6 +62,7 @@ chrono.workspace = true data-encoding.workspace = true derivative.workspace = true ed25519-consensus.workspace = true +eyre.workspace = true ferveo = {optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} ferveo-common = {git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo", rev = "e5abd0acc938da90140351a65a26472eb495ce4d"} @@ -69,9 +70,11 @@ tpke = {package = "group-threshold-cryptography", optional = true, git = "https: ibc = {git = "https://github.com/heliaxdev/cosmos-ibc-rs.git", rev = "e71bc2cc79f8c2b32e970d95312f251398c93d9e", default-features = false, features = ["serde"], optional = true} ibc-proto = {git = "https://github.com/heliaxdev/ibc-proto-rs.git", rev = "6f4038fcf4981f1ed70771d1cd89931267f917af", default-features = false, optional = true} ics23.workspace = true +impl-num-traits = "0.1.2" index-set.workspace = true itertools.workspace = true libsecp256k1.workspace = true +num-traits.workspace = true masp_primitives.workspace = true proptest = {version = "1.2.0", optional = true} prost.workspace = true @@ -79,8 +82,6 @@ prost-types.workspace = true rand = {version = "0.8", optional = true} rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} -rust_decimal.workspace = true -rust_decimal_macros.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true @@ -89,6 +90,7 @@ tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev thiserror.workspace = true tracing.workspace = true zeroize.workspace = true +uint = "0.9.5" [dev-dependencies] assert_matches.workspace = true @@ -99,6 +101,7 @@ rand.workspace = true rand_core.workspace = true test-log.workspace = true tracing-subscriber.workspace = true +toml.workspace = true [build-dependencies] tonic-build.workspace = true diff --git a/core/src/ledger/governance/parameters.rs b/core/src/ledger/governance/parameters.rs index 9ae820d96c..2d247bc24f 100644 --- a/core/src/ledger/governance/parameters.rs +++ b/core/src/ledger/governance/parameters.rs @@ -79,7 +79,7 @@ impl GovParams { } = self; let min_proposal_fund_key = gov_storage::get_min_proposal_fund_key(); - let amount = Amount::whole(*min_proposal_fund); + let amount = Amount::native_whole(*min_proposal_fund); storage.write(&min_proposal_fund_key, amount)?; let max_proposal_code_size_key = diff --git a/core/src/ledger/ibc/actions.rs b/core/src/ledger/ibc/actions.rs new file mode 100644 index 0000000000..e925a98d92 --- /dev/null +++ b/core/src/ledger/ibc/actions.rs @@ -0,0 +1,1605 @@ +//! Functions to handle IBC modules + +use std::str::FromStr; + +use sha2::Digest; +use thiserror::Error; + +use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; +use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; +use crate::ibc::core::ics02_client::client_consensus::{ + AnyConsensusState, ConsensusState, +}; +use crate::ibc::core::ics02_client::client_state::{ + AnyClientState, ClientState, +}; +use crate::ibc::core::ics02_client::client_type::ClientType; +use crate::ibc::core::ics02_client::events::{ + Attributes as ClientAttributes, CreateClient, UpdateClient, UpgradeClient, +}; +use crate::ibc::core::ics02_client::header::{AnyHeader, Header}; +use crate::ibc::core::ics02_client::height::Height; +use crate::ibc::core::ics02_client::msgs::create_client::MsgCreateAnyClient; +use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; +use crate::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; +use crate::ibc::core::ics02_client::msgs::ClientMsg; +use crate::ibc::core::ics03_connection::connection::{ + ConnectionEnd, Counterparty as ConnCounterparty, State as ConnState, +}; +use crate::ibc::core::ics03_connection::events::{ + Attributes as ConnectionAttributes, OpenAck as ConnOpenAck, + OpenConfirm as ConnOpenConfirm, OpenInit as ConnOpenInit, + OpenTry as ConnOpenTry, +}; +use crate::ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; +use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; +use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; +use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; +use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; +use crate::ibc::core::ics04_channel::channel::{ + ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, +}; +use crate::ibc::core::ics04_channel::commitment::PacketCommitment; +use crate::ibc::core::ics04_channel::events::{ + AcknowledgePacket, CloseConfirm as ChanCloseConfirm, + CloseInit as ChanCloseInit, OpenAck as ChanOpenAck, + OpenConfirm as ChanOpenConfirm, OpenInit as ChanOpenInit, + OpenTry as ChanOpenTry, SendPacket, TimeoutPacket, WriteAcknowledgement, +}; +use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; +use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; +use crate::ibc::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; +use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; +use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; +use crate::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; +use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; +use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; +use crate::ibc::core::ics04_channel::msgs::timeout::MsgTimeout; +use crate::ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; +use crate::ibc::core::ics04_channel::msgs::{ChannelMsg, PacketMsg}; +use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; +use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; +use crate::ibc::core::ics24_host::error::ValidationError as Ics24Error; +use crate::ibc::core::ics24_host::identifier::{ + ChannelId, ClientId, ConnectionId, PortChannelId, PortId, +}; +use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; +use crate::ibc::events::IbcEvent; +#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] +use crate::ibc::mock::client_state::{MockClientState, MockConsensusState}; +use crate::ibc::timestamp::Timestamp; +use crate::ledger::ibc::data::{ + Error as IbcDataError, FungibleTokenPacketData, IbcMessage, PacketAck, + PacketReceipt, +}; +use crate::ledger::ibc::storage; +use crate::ledger::storage_api; +use crate::tendermint::Time; +use crate::tendermint_proto::{Error as ProtoError, Protobuf}; +use crate::types::address::{Address, InternalAddress}; +use crate::types::ibc::IbcEvent as NamadaIbcEvent; +use crate::types::storage::{BlockHeight, Key}; +use crate::types::time::Rfc3339String; +use crate::types::token::{self, Amount}; + +const COMMITMENT_PREFIX: &[u8] = b"ibc"; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Invalid client error: {0}")] + ClientId(Ics24Error), + #[error("Invalid port error: {0}")] + PortId(Ics24Error), + #[error("Updating a client error: {0}")] + ClientUpdate(String), + #[error("IBC data error: {0}")] + IbcData(IbcDataError), + #[error("Decoding IBC data error: {0}")] + Decoding(ProtoError), + #[error("Client error: {0}")] + Client(String), + #[error("Connection error: {0}")] + Connection(String), + #[error("Channel error: {0}")] + Channel(String), + #[error("Counter error: {0}")] + Counter(String), + #[error("Sequence error: {0}")] + Sequence(String), + #[error("Time error: {0}")] + Time(String), + #[error("Invalid transfer message: {0}")] + TransferMessage(token::TransferError), + #[error("Sending a token error: {0}")] + SendingToken(String), + #[error("Receiving a token error: {0}")] + ReceivingToken(String), + #[error("IBC storage error: {0}")] + IbcStorage(storage::Error), +} + +// This is needed to use `ibc::Handler::Error` with `IbcActions` in +// `tx_prelude/src/ibc.rs` +impl From for storage_api::Error { + fn from(err: Error) -> Self { + storage_api::Error::new(err) + } +} + +/// for handling IBC modules +pub type Result = std::result::Result; + +/// IBC trait to be implemented in integration that can read and write +pub trait IbcActions { + /// IBC action error + type Error: From; + + /// Read IBC-related data + fn read_ibc_data( + &self, + key: &Key, + ) -> std::result::Result>, Self::Error>; + + /// Write IBC-related data + fn write_ibc_data( + &mut self, + key: &Key, + data: impl AsRef<[u8]>, + ) -> std::result::Result<(), Self::Error>; + + /// Delete IBC-related data + fn delete_ibc_data( + &mut self, + key: &Key, + ) -> std::result::Result<(), Self::Error>; + + /// Emit an IBC event + fn emit_ibc_event( + &mut self, + event: NamadaIbcEvent, + ) -> std::result::Result<(), Self::Error>; + + /// Transfer token + fn transfer_token( + &mut self, + src: &Key, + dest: &Key, + amount: Amount, + ) -> std::result::Result<(), Self::Error>; + + /// Get the current height of this chain + fn get_height(&self) -> std::result::Result; + + /// Get the current time of the tendermint header of this chain + fn get_header_time( + &self, + ) -> std::result::Result; + + /// dispatch according to ICS26 routing + fn dispatch_ibc_action( + &mut self, + tx_data: &[u8], + ) -> std::result::Result<(), Self::Error> { + let ibc_msg = IbcMessage::decode(tx_data).map_err(Error::IbcData)?; + match &ibc_msg.0 { + Ics26Envelope::Ics2Msg(ics02_msg) => match ics02_msg { + ClientMsg::CreateClient(msg) => self.create_client(msg), + ClientMsg::UpdateClient(msg) => self.update_client(msg), + ClientMsg::Misbehaviour(_msg) => todo!(), + ClientMsg::UpgradeClient(msg) => self.upgrade_client(msg), + }, + Ics26Envelope::Ics3Msg(ics03_msg) => match ics03_msg { + ConnectionMsg::ConnectionOpenInit(msg) => { + self.init_connection(msg) + } + ConnectionMsg::ConnectionOpenTry(msg) => { + self.try_connection(msg) + } + ConnectionMsg::ConnectionOpenAck(msg) => { + self.ack_connection(msg) + } + ConnectionMsg::ConnectionOpenConfirm(msg) => { + self.confirm_connection(msg) + } + }, + Ics26Envelope::Ics4ChannelMsg(ics04_msg) => match ics04_msg { + ChannelMsg::ChannelOpenInit(msg) => self.init_channel(msg), + ChannelMsg::ChannelOpenTry(msg) => self.try_channel(msg), + ChannelMsg::ChannelOpenAck(msg) => self.ack_channel(msg), + ChannelMsg::ChannelOpenConfirm(msg) => { + self.confirm_channel(msg) + } + ChannelMsg::ChannelCloseInit(msg) => { + self.close_init_channel(msg) + } + ChannelMsg::ChannelCloseConfirm(msg) => { + self.close_confirm_channel(msg) + } + }, + Ics26Envelope::Ics4PacketMsg(ics04_msg) => match ics04_msg { + PacketMsg::AckPacket(msg) => self.acknowledge_packet(msg), + PacketMsg::RecvPacket(msg) => self.receive_packet(msg), + PacketMsg::ToPacket(msg) => self.timeout_packet(msg), + PacketMsg::ToClosePacket(msg) => { + self.timeout_on_close_packet(msg) + } + }, + Ics26Envelope::Ics20Msg(msg) => self.send_token(msg), + } + } + + /// Create a new client + fn create_client( + &mut self, + msg: &MsgCreateAnyClient, + ) -> std::result::Result<(), Self::Error> { + let counter_key = storage::client_counter_key(); + let counter = self.get_and_inc_counter(&counter_key)?; + let client_type = msg.client_state.client_type(); + let client_id = client_id(client_type, counter)?; + // client type + let client_type_key = storage::client_type_key(&client_id); + self.write_ibc_data(&client_type_key, client_type.as_str().as_bytes())?; + // client state + let client_state_key = storage::client_state_key(&client_id); + self.write_ibc_data( + &client_state_key, + msg.client_state + .encode_vec() + .expect("encoding shouldn't fail"), + )?; + // consensus state + let height = msg.client_state.latest_height(); + let consensus_state_key = + storage::consensus_state_key(&client_id, height); + self.write_ibc_data( + &consensus_state_key, + msg.consensus_state + .encode_vec() + .expect("encoding shouldn't fail"), + )?; + + self.set_client_update_time(&client_id)?; + + let event = make_create_client_event(&client_id, msg) + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Update a client + fn update_client( + &mut self, + msg: &MsgUpdateAnyClient, + ) -> std::result::Result<(), Self::Error> { + // get and update the client + let client_id = msg.client_id.clone(); + let client_state_key = storage::client_state_key(&client_id); + let value = + self.read_ibc_data(&client_state_key)?.ok_or_else(|| { + Error::Client(format!( + "The client to be updated doesn't exist: ID {}", + client_id + )) + })?; + let client_state = + AnyClientState::decode_vec(&value).map_err(Error::Decoding)?; + let (new_client_state, new_consensus_state) = + update_client(client_state, msg.header.clone())?; + + let height = new_client_state.latest_height(); + self.write_ibc_data( + &client_state_key, + new_client_state + .encode_vec() + .expect("encoding shouldn't fail"), + )?; + let consensus_state_key = + storage::consensus_state_key(&client_id, height); + self.write_ibc_data( + &consensus_state_key, + new_consensus_state + .encode_vec() + .expect("encoding shouldn't fail"), + )?; + + self.set_client_update_time(&client_id)?; + + let event = make_update_client_event(&client_id, msg) + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Upgrade a client + fn upgrade_client( + &mut self, + msg: &MsgUpgradeAnyClient, + ) -> std::result::Result<(), Self::Error> { + let client_state_key = storage::client_state_key(&msg.client_id); + let height = msg.client_state.latest_height(); + let consensus_state_key = + storage::consensus_state_key(&msg.client_id, height); + self.write_ibc_data( + &client_state_key, + msg.client_state + .encode_vec() + .expect("encoding shouldn't fail"), + )?; + self.write_ibc_data( + &consensus_state_key, + msg.consensus_state + .encode_vec() + .expect("encoding shouldn't fail"), + )?; + + self.set_client_update_time(&msg.client_id)?; + + let event = make_upgrade_client_event(&msg.client_id, msg) + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Initialize a connection for ConnectionOpenInit + fn init_connection( + &mut self, + msg: &MsgConnectionOpenInit, + ) -> std::result::Result<(), Self::Error> { + let counter_key = storage::connection_counter_key(); + let counter = self.get_and_inc_counter(&counter_key)?; + // new connection + let conn_id = connection_id(counter); + let conn_key = storage::connection_key(&conn_id); + let connection = init_connection(msg); + self.write_ibc_data( + &conn_key, + connection.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_open_init_connection_event(&conn_id, msg) + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Initialize a connection for ConnectionOpenTry + fn try_connection( + &mut self, + msg: &MsgConnectionOpenTry, + ) -> std::result::Result<(), Self::Error> { + let counter_key = storage::connection_counter_key(); + let counter = self.get_and_inc_counter(&counter_key)?; + // new connection + let conn_id = connection_id(counter); + let conn_key = storage::connection_key(&conn_id); + let connection = try_connection(msg); + self.write_ibc_data( + &conn_key, + connection.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_open_try_connection_event(&conn_id, msg) + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Open the connection for ConnectionOpenAck + fn ack_connection( + &mut self, + msg: &MsgConnectionOpenAck, + ) -> std::result::Result<(), Self::Error> { + let conn_key = storage::connection_key(&msg.connection_id); + let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { + Error::Connection(format!( + "The connection to be opened doesn't exist: ID {}", + msg.connection_id + )) + })?; + let mut connection = + ConnectionEnd::decode_vec(&value).map_err(Error::Decoding)?; + open_connection(&mut connection); + let mut counterparty = connection.counterparty().clone(); + counterparty.connection_id = + Some(msg.counterparty_connection_id.clone()); + connection.set_counterparty(counterparty); + self.write_ibc_data( + &conn_key, + connection.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_open_ack_connection_event(msg).try_into().unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Open the connection for ConnectionOpenConfirm + fn confirm_connection( + &mut self, + msg: &MsgConnectionOpenConfirm, + ) -> std::result::Result<(), Self::Error> { + let conn_key = storage::connection_key(&msg.connection_id); + let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { + Error::Connection(format!( + "The connection to be opend doesn't exist: ID {}", + msg.connection_id + )) + })?; + let mut connection = + ConnectionEnd::decode_vec(&value).map_err(Error::Decoding)?; + open_connection(&mut connection); + self.write_ibc_data( + &conn_key, + connection.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_open_confirm_connection_event(msg).try_into().unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Initialize a channel for ChannelOpenInit + fn init_channel( + &mut self, + msg: &MsgChannelOpenInit, + ) -> std::result::Result<(), Self::Error> { + self.bind_port(&msg.port_id)?; + let counter_key = storage::channel_counter_key(); + let counter = self.get_and_inc_counter(&counter_key)?; + let channel_id = channel_id(counter); + let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); + let channel_key = storage::channel_key(&port_channel_id); + self.write_ibc_data( + &channel_key, + msg.channel.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_open_init_channel_event(&channel_id, msg) + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Initialize a channel for ChannelOpenTry + fn try_channel( + &mut self, + msg: &MsgChannelOpenTry, + ) -> std::result::Result<(), Self::Error> { + self.bind_port(&msg.port_id)?; + let counter_key = storage::channel_counter_key(); + let counter = self.get_and_inc_counter(&counter_key)?; + let channel_id = channel_id(counter); + let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); + let channel_key = storage::channel_key(&port_channel_id); + self.write_ibc_data( + &channel_key, + msg.channel.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_open_try_channel_event(&channel_id, msg) + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Open the channel for ChannelOpenAck + fn ack_channel( + &mut self, + msg: &MsgChannelOpenAck, + ) -> std::result::Result<(), Self::Error> { + let port_channel_id = + port_channel_id(msg.port_id.clone(), msg.channel_id); + let channel_key = storage::channel_key(&port_channel_id); + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { + Error::Channel(format!( + "The channel to be opened doesn't exist: Port/Channel {}", + port_channel_id + )) + })?; + let mut channel = + ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; + channel.set_counterparty_channel_id(msg.counterparty_channel_id); + open_channel(&mut channel); + self.write_ibc_data( + &channel_key, + channel.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_open_ack_channel_event(msg, &channel)? + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Open the channel for ChannelOpenConfirm + fn confirm_channel( + &mut self, + msg: &MsgChannelOpenConfirm, + ) -> std::result::Result<(), Self::Error> { + let port_channel_id = + port_channel_id(msg.port_id.clone(), msg.channel_id); + let channel_key = storage::channel_key(&port_channel_id); + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { + Error::Channel(format!( + "The channel to be opened doesn't exist: Port/Channel {}", + port_channel_id + )) + })?; + let mut channel = + ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; + open_channel(&mut channel); + self.write_ibc_data( + &channel_key, + channel.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_open_confirm_channel_event(msg, &channel)? + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Close the channel for ChannelCloseInit + fn close_init_channel( + &mut self, + msg: &MsgChannelCloseInit, + ) -> std::result::Result<(), Self::Error> { + let port_channel_id = + port_channel_id(msg.port_id.clone(), msg.channel_id); + let channel_key = storage::channel_key(&port_channel_id); + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { + Error::Channel(format!( + "The channel to be closed doesn't exist: Port/Channel {}", + port_channel_id + )) + })?; + let mut channel = + ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; + close_channel(&mut channel); + self.write_ibc_data( + &channel_key, + channel.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_close_init_channel_event(msg, &channel)? + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Close the channel for ChannelCloseConfirm + fn close_confirm_channel( + &mut self, + msg: &MsgChannelCloseConfirm, + ) -> std::result::Result<(), Self::Error> { + let port_channel_id = + port_channel_id(msg.port_id.clone(), msg.channel_id); + let channel_key = storage::channel_key(&port_channel_id); + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { + Error::Channel(format!( + "The channel to be closed doesn't exist: Port/Channel {}", + port_channel_id + )) + })?; + let mut channel = + ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; + close_channel(&mut channel); + self.write_ibc_data( + &channel_key, + channel.encode_vec().expect("encoding shouldn't fail"), + )?; + + let event = make_close_confirm_channel_event(msg, &channel)? + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Send a packet + fn send_packet( + &mut self, + port_channel_id: PortChannelId, + data: Vec, + timeout_height: Height, + timeout_timestamp: Timestamp, + ) -> std::result::Result<(), Self::Error> { + // get and increment the next sequence send + let seq_key = storage::next_sequence_send_key(&port_channel_id); + let sequence = self.get_and_inc_sequence(&seq_key)?; + + // get the channel for the destination info. + let channel_key = storage::channel_key(&port_channel_id); + let channel = self + .read_ibc_data(&channel_key)? + .expect("cannot get the channel to be closed"); + let channel = + ChannelEnd::decode_vec(&channel).expect("cannot get the channel"); + let counterparty = channel.counterparty(); + + // make a packet + let packet = Packet { + sequence, + source_port: port_channel_id.port_id.clone(), + source_channel: port_channel_id.channel_id, + destination_port: counterparty.port_id.clone(), + destination_channel: *counterparty + .channel_id() + .expect("the counterparty channel should exist"), + data, + timeout_height, + timeout_timestamp, + }; + // store the commitment of the packet + let commitment_key = storage::commitment_key( + &port_channel_id.port_id, + &port_channel_id.channel_id, + packet.sequence, + ); + let commitment = commitment(&packet); + self.write_ibc_data(&commitment_key, commitment.into_vec())?; + + let event = make_send_packet_event(packet).try_into().unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Receive a packet + fn receive_packet( + &mut self, + msg: &MsgRecvPacket, + ) -> std::result::Result<(), Self::Error> { + // check the packet data + let packet_ack = + if let Ok(data) = serde_json::from_slice(&msg.packet.data) { + match self.receive_token(&msg.packet, &data) { + Ok(_) => PacketAck::result_success(), + Err(_) => PacketAck::result_error( + "receiving a token failed".to_string(), + ), + } + } else { + PacketAck::result_error("unknown packet data".to_string()) + }; + + // store the receipt + let receipt_key = storage::receipt_key( + &msg.packet.destination_port, + &msg.packet.destination_channel, + msg.packet.sequence, + ); + self.write_ibc_data(&receipt_key, PacketReceipt::default().as_bytes())?; + + // store the ack + let ack_key = storage::ack_key( + &msg.packet.destination_port, + &msg.packet.destination_channel, + msg.packet.sequence, + ); + let ack = packet_ack.encode_to_vec(); + let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); + self.write_ibc_data(&ack_key, ack_commitment)?; + + // increment the next sequence receive + let port_channel_id = port_channel_id( + msg.packet.destination_port.clone(), + msg.packet.destination_channel, + ); + let seq_key = storage::next_sequence_recv_key(&port_channel_id); + self.get_and_inc_sequence(&seq_key)?; + + let event = make_write_ack_event(msg.packet.clone(), ack) + .try_into() + .unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Receive a acknowledgement + fn acknowledge_packet( + &mut self, + msg: &MsgAcknowledgement, + ) -> std::result::Result<(), Self::Error> { + let ack = PacketAck::try_from(msg.acknowledgement.clone()) + .map_err(Error::IbcData)?; + if !ack.is_success() { + if let Ok(data) = serde_json::from_slice(&msg.packet.data) { + self.refund_token(&msg.packet, &data)?; + } + } + + let commitment_key = storage::commitment_key( + &msg.packet.source_port, + &msg.packet.source_channel, + msg.packet.sequence, + ); + self.delete_ibc_data(&commitment_key)?; + + // get and increment the next sequence ack + let port_channel_id = port_channel_id( + msg.packet.source_port.clone(), + msg.packet.source_channel, + ); + let seq_key = storage::next_sequence_ack_key(&port_channel_id); + self.get_and_inc_sequence(&seq_key)?; + + let event = make_ack_event(msg.packet.clone()).try_into().unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Receive a timeout + fn timeout_packet( + &mut self, + msg: &MsgTimeout, + ) -> std::result::Result<(), Self::Error> { + // check the packet data + if let Ok(data) = serde_json::from_slice(&msg.packet.data) { + self.refund_token(&msg.packet, &data)?; + } + + // delete the commitment of the packet + let commitment_key = storage::commitment_key( + &msg.packet.source_port, + &msg.packet.source_channel, + msg.packet.sequence, + ); + self.delete_ibc_data(&commitment_key)?; + + // close the channel + let port_channel_id = port_channel_id( + msg.packet.source_port.clone(), + msg.packet.source_channel, + ); + let channel_key = storage::channel_key(&port_channel_id); + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { + Error::Channel(format!( + "The channel to be closed doesn't exist: Port/Channel {}", + port_channel_id + )) + })?; + let mut channel = + ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; + if channel.order_matches(&Order::Ordered) { + close_channel(&mut channel); + self.write_ibc_data( + &channel_key, + channel.encode_vec().expect("encoding shouldn't fail"), + )?; + } + + let event = make_timeout_event(msg.packet.clone()).try_into().unwrap(); + self.emit_ibc_event(event)?; + + Ok(()) + } + + /// Receive a timeout for TimeoutOnClose + fn timeout_on_close_packet( + &mut self, + msg: &MsgTimeoutOnClose, + ) -> std::result::Result<(), Self::Error> { + // check the packet data + if let Ok(data) = serde_json::from_slice(&msg.packet.data) { + self.refund_token(&msg.packet, &data)?; + } + + // delete the commitment of the packet + let commitment_key = storage::commitment_key( + &msg.packet.source_port, + &msg.packet.source_channel, + msg.packet.sequence, + ); + self.delete_ibc_data(&commitment_key)?; + + // close the channel + let port_channel_id = port_channel_id( + msg.packet.source_port.clone(), + msg.packet.source_channel, + ); + let channel_key = storage::channel_key(&port_channel_id); + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { + Error::Channel(format!( + "The channel to be closed doesn't exist: Port/Channel {}", + port_channel_id + )) + })?; + let mut channel = + ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; + if channel.order_matches(&Order::Ordered) { + close_channel(&mut channel); + self.write_ibc_data( + &channel_key, + channel.encode_vec().expect("encoding shouldn't fail"), + )?; + } + + Ok(()) + } + + /// Set the timestamp and the height for the client update + fn set_client_update_time( + &mut self, + client_id: &ClientId, + ) -> std::result::Result<(), Self::Error> { + let time = Time::parse_from_rfc3339(&self.get_header_time()?.0) + .map_err(|e| { + Error::Time(format!("The time of the header is invalid: {}", e)) + })?; + let key = storage::client_update_timestamp_key(client_id); + self.write_ibc_data( + &key, + time.encode_vec().expect("encoding shouldn't fail"), + )?; + + // the revision number is always 0 + let height = Height::new(0, self.get_height()?.0); + let height_key = storage::client_update_height_key(client_id); + // write the current height as u64 + self.write_ibc_data( + &height_key, + height.encode_vec().expect("Encoding shouldn't fail"), + )?; + + Ok(()) + } + + /// Get and increment the counter + fn get_and_inc_counter( + &mut self, + key: &Key, + ) -> std::result::Result { + let value = self.read_ibc_data(key)?.ok_or_else(|| { + Error::Counter(format!("The counter doesn't exist: {}", key)) + })?; + let value: [u8; 8] = value.try_into().map_err(|_| { + Error::Counter(format!("The counter value wasn't u64: Key {}", key)) + })?; + let counter = u64::from_be_bytes(value); + self.write_ibc_data(key, (counter + 1).to_be_bytes())?; + Ok(counter) + } + + /// Get and increment the sequence + fn get_and_inc_sequence( + &mut self, + key: &Key, + ) -> std::result::Result { + let index = match self.read_ibc_data(key)? { + Some(v) => { + let index: [u8; 8] = v.try_into().map_err(|_| { + Error::Sequence(format!( + "The sequence index wasn't u64: Key {}", + key + )) + })?; + u64::from_be_bytes(index) + } + // when the sequence has never been used, returns the initial value + None => 1, + }; + self.write_ibc_data(key, (index + 1).to_be_bytes())?; + Ok(index.into()) + } + + /// Bind a new port + fn bind_port( + &mut self, + port_id: &PortId, + ) -> std::result::Result<(), Self::Error> { + let port_key = storage::port_key(port_id); + match self.read_ibc_data(&port_key)? { + Some(_) => {} + None => { + // create a new capability and claim it + let index_key = storage::capability_index_key(); + let cap_index = self.get_and_inc_counter(&index_key)?; + self.write_ibc_data(&port_key, cap_index.to_be_bytes())?; + let cap_key = storage::capability_key(cap_index); + self.write_ibc_data(&cap_key, port_id.as_bytes())?; + } + } + Ok(()) + } + + /// Send the specified token by escrowing or burning + fn send_token( + &mut self, + msg: &MsgTransfer, + ) -> std::result::Result<(), Self::Error> { + let mut data = FungibleTokenPacketData::from(msg.clone()); + if let Some(hash) = storage::token_hash_from_denom(&data.denom) + .map_err(Error::IbcStorage)? + { + let denom_key = storage::ibc_denom_key(hash); + let denom_bytes = + self.read_ibc_data(&denom_key)?.ok_or_else(|| { + Error::SendingToken(format!( + "No original denom: denom_key {}", + denom_key + )) + })?; + let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { + Error::SendingToken(format!( + "Decoding the denom failed: denom_key {}, error {}", + denom_key, e + )) + })?; + data.denom = denom.to_string(); + } + let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; + let amount = Amount::from_str(&data.amount, 0).map_err(|e| { + Error::SendingToken(format!( + "Invalid amount: amount {}, error {}", + data.amount, e + )) + })?; + + let source_addr = Address::decode(&data.sender).map_err(|e| { + Error::SendingToken(format!( + "Invalid sender address: sender {}, error {}", + data.sender, e + )) + })?; + + // check the denomination field + let prefix = format!( + "{}/{}/", + msg.source_port.clone(), + msg.source_channel.clone() + ); + let (source, target) = if data.denom.starts_with(&prefix) { + // the receiver's chain was the source + // transfer from the origin-specific account of the token + let key_prefix = storage::ibc_token_prefix(&data.denom) + .map_err(Error::IbcStorage)?; + let src = token::multitoken_balance_key(&key_prefix, &source_addr); + + let key_prefix = storage::ibc_account_prefix( + &msg.source_port, + &msg.source_channel, + &token, + ); + let burn = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcBurn), + ); + (src, burn) + } else { + // this chain is the source + // escrow the amount of the token + let src = if data.denom == token.to_string() { + token::balance_key(&token, &source_addr) + } else { + let key_prefix = storage::ibc_token_prefix(&data.denom) + .map_err(Error::IbcStorage)?; + token::multitoken_balance_key(&key_prefix, &source_addr) + }; + + let key_prefix = storage::ibc_account_prefix( + &msg.source_port, + &msg.source_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); + (src, escrow) + }; + self.transfer_token(&source, &target, amount)?; + + // send a packet + let port_channel_id = + port_channel_id(msg.source_port.clone(), msg.source_channel); + let packet_data = serde_json::to_vec(&data) + .expect("encoding the packet data shouldn't fail"); + self.send_packet( + port_channel_id, + packet_data, + msg.timeout_height, + msg.timeout_timestamp, + ) + } + + /// Receive the specified token by unescrowing or minting + fn receive_token( + &mut self, + packet: &Packet, + data: &FungibleTokenPacketData, + ) -> std::result::Result<(), Self::Error> { + let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; + let amount = Amount::from_str(&data.amount, 0).map_err(|e| { + Error::ReceivingToken(format!( + "Invalid amount: amount {}, error {}", + data.amount, e + )) + })?; + // The receiver should be an address because the origin-specific account + // key should be assigned internally + let dest_addr = Address::decode(&data.receiver).map_err(|e| { + Error::ReceivingToken(format!( + "Invalid receiver address: receiver {}, error {}", + data.receiver, e + )) + })?; + + let prefix = format!( + "{}/{}/", + packet.source_port.clone(), + packet.source_channel.clone() + ); + let (source, target) = match data.denom.strip_prefix(&prefix) { + Some(denom) => { + // unescrow the token because this chain was the source + let escrow_prefix = storage::ibc_account_prefix( + &packet.destination_port, + &packet.destination_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &escrow_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); + let dest = if denom == token.to_string() { + token::balance_key(&token, &dest_addr) + } else { + let key_prefix = storage::ibc_token_prefix(denom) + .map_err(Error::IbcStorage)?; + token::multitoken_balance_key(&key_prefix, &dest_addr) + }; + (escrow, dest) + } + None => { + // mint the token because the sender chain is the source + let key_prefix = storage::ibc_account_prefix( + &packet.destination_port, + &packet.destination_channel, + &token, + ); + let mint = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcMint), + ); + + // prefix the denom with the this chain port and channel + let denom = format!( + "{}/{}/{}", + &packet.destination_port, + &packet.destination_channel, + &data.denom + ); + let key_prefix = storage::ibc_token_prefix(&denom) + .map_err(Error::IbcStorage)?; + let dest = + token::multitoken_balance_key(&key_prefix, &dest_addr); + + // store the prefixed denom + let token_hash = storage::calc_hash(&denom); + let denom_key = storage::ibc_denom_key(token_hash); + self.write_ibc_data(&denom_key, denom.as_bytes())?; + + (mint, dest) + } + }; + self.transfer_token(&source, &target, amount)?; + + Ok(()) + } + + /// Refund the specified token by unescrowing or minting + fn refund_token( + &mut self, + packet: &Packet, + data: &FungibleTokenPacketData, + ) -> std::result::Result<(), Self::Error> { + let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; + let amount = Amount::from_str(&data.amount, 0).map_err(|e| { + Error::ReceivingToken(format!( + "Invalid amount: amount {}, error {}", + data.amount, e + )) + })?; + + let dest_addr = Address::decode(&data.sender).map_err(|e| { + Error::SendingToken(format!( + "Invalid sender address: sender {}, error {}", + data.sender, e + )) + })?; + + let prefix = format!( + "{}/{}/", + packet.source_port.clone(), + packet.source_channel.clone() + ); + let (source, target) = if data.denom.starts_with(&prefix) { + // mint the token because the amount was burned + let key_prefix = storage::ibc_account_prefix( + &packet.source_port, + &packet.source_channel, + &token, + ); + let mint = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcMint), + ); + let key_prefix = storage::ibc_token_prefix(&data.denom) + .map_err(Error::IbcStorage)?; + let dest = token::multitoken_balance_key(&key_prefix, &dest_addr); + (mint, dest) + } else { + // unescrow the token because the acount was escrowed + let dest = if data.denom == token.to_string() { + token::balance_key(&token, &dest_addr) + } else { + let key_prefix = storage::ibc_token_prefix(&data.denom) + .map_err(Error::IbcStorage)?; + token::multitoken_balance_key(&key_prefix, &dest_addr) + }; + + let key_prefix = storage::ibc_account_prefix( + &packet.source_port, + &packet.source_channel, + &token, + ); + let escrow = token::multitoken_balance_key( + &key_prefix, + &Address::Internal(InternalAddress::IbcEscrow), + ); + (escrow, dest) + }; + self.transfer_token(&source, &target, amount)?; + + Ok(()) + } +} + +/// Update a client with the given state and headers +pub fn update_client( + client_state: AnyClientState, + header: AnyHeader, +) -> Result<(AnyClientState, AnyConsensusState)> { + match client_state { + AnyClientState::Tendermint(cs) => match header { + AnyHeader::Tendermint(h) => { + let new_client_state = cs.with_header(h.clone()).wrap_any(); + let new_consensus_state = TmConsensusState::from(h).wrap_any(); + Ok((new_client_state, new_consensus_state)) + } + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + _ => Err(Error::ClientUpdate( + "The header type is mismatched".to_owned(), + )), + }, + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + AnyClientState::Mock(_) => match header { + AnyHeader::Mock(h) => Ok(( + MockClientState::new(h).wrap_any(), + MockConsensusState::new(h).wrap_any(), + )), + _ => Err(Error::ClientUpdate( + "The header type is mismatched".to_owned(), + )), + }, + } +} + +/// Returns a new client ID +pub fn client_id(client_type: ClientType, counter: u64) -> Result { + ClientId::new(client_type, counter).map_err(Error::ClientId) +} + +/// Returns a new connection ID +pub fn connection_id(counter: u64) -> ConnectionId { + ConnectionId::new(counter) +} + +/// Make a connection end from the init message +pub fn init_connection(msg: &MsgConnectionOpenInit) -> ConnectionEnd { + ConnectionEnd::new( + ConnState::Init, + msg.client_id.clone(), + msg.counterparty.clone(), + vec![msg.version.clone().unwrap_or_default()], + msg.delay_period, + ) +} + +/// Make a connection end from the try message +pub fn try_connection(msg: &MsgConnectionOpenTry) -> ConnectionEnd { + ConnectionEnd::new( + ConnState::TryOpen, + msg.client_id.clone(), + msg.counterparty.clone(), + msg.counterparty_versions.clone(), + msg.delay_period, + ) +} + +/// Open the connection +pub fn open_connection(conn: &mut ConnectionEnd) { + conn.set_state(ConnState::Open); +} + +/// Returns a new channel ID +pub fn channel_id(counter: u64) -> ChannelId { + ChannelId::new(counter) +} + +/// Open the channel +pub fn open_channel(channel: &mut ChannelEnd) { + channel.set_state(ChanState::Open); +} + +/// Close the channel +pub fn close_channel(channel: &mut ChannelEnd) { + channel.set_state(ChanState::Closed); +} + +/// Returns a port ID +pub fn port_id(id: &str) -> Result { + PortId::from_str(id).map_err(Error::PortId) +} + +/// Returns a pair of port ID and channel ID +pub fn port_channel_id( + port_id: PortId, + channel_id: ChannelId, +) -> PortChannelId { + PortChannelId { + port_id, + channel_id, + } +} + +/// Returns a sequence +pub fn sequence(index: u64) -> Sequence { + Sequence::from(index) +} + +/// Make a packet from MsgTransfer +pub fn packet_from_message( + msg: &MsgTransfer, + sequence: Sequence, + counterparty: &ChanCounterparty, +) -> Packet { + Packet { + sequence, + source_port: msg.source_port.clone(), + source_channel: msg.source_channel, + destination_port: counterparty.port_id.clone(), + destination_channel: *counterparty + .channel_id() + .expect("the counterparty channel should exist"), + data: serde_json::to_vec(&FungibleTokenPacketData::from(msg.clone())) + .expect("encoding the packet data shouldn't fail"), + timeout_height: msg.timeout_height, + timeout_timestamp: msg.timeout_timestamp, + } +} + +/// Returns a commitment from the given packet +pub fn commitment(packet: &Packet) -> PacketCommitment { + let timeout = packet.timeout_timestamp.nanoseconds().to_be_bytes(); + let revision_number = packet.timeout_height.revision_number.to_be_bytes(); + let revision_height = packet.timeout_height.revision_height.to_be_bytes(); + let data = sha2::Sha256::digest(&packet.data); + let input = [ + &timeout, + &revision_number, + &revision_height, + data.as_slice(), + ] + .concat(); + sha2::Sha256::digest(&input).to_vec().into() +} + +/// Returns a counterparty of a connection +pub fn connection_counterparty( + client_id: ClientId, + conn_id: ConnectionId, +) -> ConnCounterparty { + ConnCounterparty::new(client_id, Some(conn_id), commitment_prefix()) +} + +/// Returns a counterparty of a channel +pub fn channel_counterparty( + port_id: PortId, + channel_id: ChannelId, +) -> ChanCounterparty { + ChanCounterparty::new(port_id, Some(channel_id)) +} + +/// Returns Namada commitment prefix +pub fn commitment_prefix() -> CommitmentPrefix { + CommitmentPrefix::try_from(COMMITMENT_PREFIX.to_vec()) + .expect("the conversion shouldn't fail") +} + +/// Makes CreateClient event +pub fn make_create_client_event( + client_id: &ClientId, + msg: &MsgCreateAnyClient, +) -> IbcEvent { + let attributes = ClientAttributes { + client_id: client_id.clone(), + client_type: msg.client_state.client_type(), + consensus_height: msg.client_state.latest_height(), + ..Default::default() + }; + IbcEvent::CreateClient(CreateClient::from(attributes)) +} + +/// Makes UpdateClient event +pub fn make_update_client_event( + client_id: &ClientId, + msg: &MsgUpdateAnyClient, +) -> IbcEvent { + let attributes = ClientAttributes { + client_id: client_id.clone(), + client_type: msg.header.client_type(), + consensus_height: msg.header.height(), + ..Default::default() + }; + IbcEvent::UpdateClient(UpdateClient::from(attributes)) +} + +/// Makes UpgradeClient event +pub fn make_upgrade_client_event( + client_id: &ClientId, + msg: &MsgUpgradeAnyClient, +) -> IbcEvent { + let attributes = ClientAttributes { + client_id: client_id.clone(), + client_type: msg.client_state.client_type(), + consensus_height: msg.client_state.latest_height(), + ..Default::default() + }; + IbcEvent::UpgradeClient(UpgradeClient::from(attributes)) +} + +/// Makes OpenInitConnection event +pub fn make_open_init_connection_event( + conn_id: &ConnectionId, + msg: &MsgConnectionOpenInit, +) -> IbcEvent { + let attributes = ConnectionAttributes { + connection_id: Some(conn_id.clone()), + client_id: msg.client_id.clone(), + counterparty_connection_id: msg.counterparty.connection_id().cloned(), + counterparty_client_id: msg.counterparty.client_id().clone(), + ..Default::default() + }; + ConnOpenInit::from(attributes).into() +} + +/// Makes OpenTryConnection event +pub fn make_open_try_connection_event( + conn_id: &ConnectionId, + msg: &MsgConnectionOpenTry, +) -> IbcEvent { + let attributes = ConnectionAttributes { + connection_id: Some(conn_id.clone()), + client_id: msg.client_id.clone(), + counterparty_connection_id: msg.counterparty.connection_id().cloned(), + counterparty_client_id: msg.counterparty.client_id().clone(), + ..Default::default() + }; + ConnOpenTry::from(attributes).into() +} + +/// Makes OpenAckConnection event +pub fn make_open_ack_connection_event(msg: &MsgConnectionOpenAck) -> IbcEvent { + let attributes = ConnectionAttributes { + connection_id: Some(msg.connection_id.clone()), + counterparty_connection_id: Some( + msg.counterparty_connection_id.clone(), + ), + ..Default::default() + }; + ConnOpenAck::from(attributes).into() +} + +/// Makes OpenConfirmConnection event +pub fn make_open_confirm_connection_event( + msg: &MsgConnectionOpenConfirm, +) -> IbcEvent { + let attributes = ConnectionAttributes { + connection_id: Some(msg.connection_id.clone()), + ..Default::default() + }; + ConnOpenConfirm::from(attributes).into() +} + +/// Makes OpenInitChannel event +pub fn make_open_init_channel_event( + channel_id: &ChannelId, + msg: &MsgChannelOpenInit, +) -> IbcEvent { + let connection_id = match msg.channel.connection_hops().get(0) { + Some(c) => c.clone(), + None => ConnectionId::default(), + }; + let attributes = ChanOpenInit { + height: Height::default(), + port_id: msg.port_id.clone(), + channel_id: Some(*channel_id), + connection_id, + counterparty_port_id: msg.channel.counterparty().port_id().clone(), + counterparty_channel_id: msg + .channel + .counterparty() + .channel_id() + .cloned(), + }; + attributes.into() +} + +/// Makes OpenTryChannel event +pub fn make_open_try_channel_event( + channel_id: &ChannelId, + msg: &MsgChannelOpenTry, +) -> IbcEvent { + let connection_id = match msg.channel.connection_hops().get(0) { + Some(c) => c.clone(), + None => ConnectionId::default(), + }; + let attributes = ChanOpenTry { + height: Height::default(), + port_id: msg.port_id.clone(), + channel_id: Some(*channel_id), + connection_id, + counterparty_port_id: msg.channel.counterparty().port_id().clone(), + counterparty_channel_id: msg + .channel + .counterparty() + .channel_id() + .cloned(), + }; + attributes.into() +} + +/// Makes OpenAckChannel event +pub fn make_open_ack_channel_event( + msg: &MsgChannelOpenAck, + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); + let attributes = ChanOpenAck { + height: Height::default(), + port_id: msg.port_id.clone(), + channel_id: Some(msg.channel_id), + counterparty_channel_id: Some(msg.counterparty_channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), + }; + Ok(attributes.into()) +} + +/// Makes OpenConfirmChannel event +pub fn make_open_confirm_channel_event( + msg: &MsgChannelOpenConfirm, + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); + let attributes = ChanOpenConfirm { + height: Height::default(), + port_id: msg.port_id.clone(), + channel_id: Some(msg.channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), + }; + Ok(attributes.into()) +} + +/// Makes CloseInitChannel event +pub fn make_close_init_channel_event( + msg: &MsgChannelCloseInit, + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); + let attributes = ChanCloseInit { + height: Height::default(), + port_id: msg.port_id.clone(), + channel_id: msg.channel_id, + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id().clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), + }; + Ok(attributes.into()) +} + +/// Makes CloseConfirmChannel event +pub fn make_close_confirm_channel_event( + msg: &MsgChannelCloseConfirm, + channel: &ChannelEnd, +) -> Result { + let conn_id = get_connection_id_from_channel(channel)?; + let counterparty = channel.counterparty(); + let attributes = ChanCloseConfirm { + height: Height::default(), + port_id: msg.port_id.clone(), + channel_id: Some(msg.channel_id), + connection_id: conn_id.clone(), + counterparty_port_id: counterparty.port_id.clone(), + counterparty_channel_id: counterparty.channel_id().cloned(), + }; + Ok(attributes.into()) +} + +fn get_connection_id_from_channel( + channel: &ChannelEnd, +) -> Result<&ConnectionId> { + channel.connection_hops().get(0).ok_or_else(|| { + Error::Channel("No connection for the channel".to_owned()) + }) +} + +/// Makes SendPacket event +pub fn make_send_packet_event(packet: Packet) -> IbcEvent { + IbcEvent::SendPacket(SendPacket { + height: packet.timeout_height, + packet, + }) +} + +/// Makes WriteAcknowledgement event +pub fn make_write_ack_event(packet: Packet, ack: Vec) -> IbcEvent { + IbcEvent::WriteAcknowledgement(WriteAcknowledgement { + // this height is not used + height: Height::default(), + packet, + ack, + }) +} + +/// Makes AcknowledgePacket event +pub fn make_ack_event(packet: Packet) -> IbcEvent { + IbcEvent::AcknowledgePacket(AcknowledgePacket { + // this height is not used + height: Height::default(), + packet, + }) +} + +/// Makes TimeoutPacket event +pub fn make_timeout_event(packet: Packet) -> IbcEvent { + IbcEvent::TimeoutPacket(TimeoutPacket { + // this height is not used + height: Height::default(), + packet, + }) +} diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 73961a1e28..ad0aa75800 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -480,7 +480,9 @@ where ChannelError::Other { description: format!( "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount + src, + dest, + amount.to_string_native() ), }, )) @@ -517,7 +519,9 @@ where ChannelError::Other { description: format!( "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount + src, + dest, + amount.to_string_native() ), }, )) @@ -554,7 +558,9 @@ where ChannelError::Other { description: format!( "Sending a coin failed: from {}, to {}, amount {}", - src, dest, amount + src, + dest, + amount.to_string_native() ), }, )) diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index bee874bb0e..3f0eb94d2a 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -22,7 +22,7 @@ use crate::types::storage::{self, DbKeySeg, Key, KeySeg}; const CLIENTS_COUNTER: &str = "clients/counter"; const CONNECTIONS_COUNTER: &str = "connections/counter"; const CHANNELS_COUNTER: &str = "channelEnds/counter"; -const DENOM: &str = "denom"; +const DENOM: &str = "ibc_denom"; /// Key segment for a multitoken related to IBC pub const MULTITOKEN_STORAGE_KEY: &str = "ibc"; @@ -82,7 +82,7 @@ pub fn ibc_prefix(key: &Key) -> Option { "receipts" => IbcPrefix::Receipt, "acks" => IbcPrefix::Ack, "event" => IbcPrefix::Event, - "denom" => IbcPrefix::Denom, + "ibc_denom" => IbcPrefix::Denom, _ => IbcPrefix::Unknown, }) } diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index bb9ae99577..ab72527522 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -2,7 +2,6 @@ pub mod storage; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use rust_decimal::Decimal; use thiserror::Error; use super::storage::types; @@ -10,6 +9,7 @@ use super::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::storage::{self as ledger_storage}; use crate::types::address::{Address, InternalAddress}; use crate::types::chain::ProposalBytes; +use crate::types::dec::Dec; use crate::types::hash::Hash; use crate::types::time::DurationSecs; use crate::types::token; @@ -45,13 +45,13 @@ pub struct Parameters { /// Expected number of epochs per year (read only) pub epochs_per_year: u64, /// PoS gain p (read only) - pub pos_gain_p: Decimal, + pub pos_gain_p: Dec, /// PoS gain d (read only) - pub pos_gain_d: Decimal, + pub pos_gain_d: Dec, /// PoS staked ratio (read + write for every epoch) - pub staked_ratio: Decimal, + pub staked_ratio: Dec, /// PoS inflation amount from the last epoch (read + write for every epoch) - pub pos_inflation_amount: u64, + pub pos_inflation_amount: token::Amount, #[cfg(not(feature = "mainnet"))] /// Faucet account for free token withdrawal pub faucet_account: Option
, @@ -188,7 +188,7 @@ impl Parameters { { let wrapper_tx_fees_key = storage::get_wrapper_tx_fees_key(); let wrapper_tx_fees = - wrapper_tx_fees.unwrap_or(token::Amount::whole(100)); + wrapper_tx_fees.unwrap_or(token::Amount::native_whole(100)); storage.write(&wrapper_tx_fees_key, wrapper_tx_fees)?; } Ok(()) @@ -276,7 +276,7 @@ where /// cost. pub fn update_pos_gain_p_parameter( storage: &mut S, - value: &Decimal, + value: &Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -289,7 +289,7 @@ where /// cost. pub fn update_pos_gain_d_parameter( storage: &mut S, - value: &Decimal, + value: &Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -302,7 +302,7 @@ where /// gas cost. pub fn update_staked_ratio_parameter( storage: &mut S, - value: &Decimal, + value: &Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -435,28 +435,28 @@ where // read PoS gain P let pos_gain_p_key = storage::get_pos_gain_p_key(); let value = storage.read(&pos_gain_p_key)?; - let pos_gain_p: Decimal = value + let pos_gain_p = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; // read PoS gain D let pos_gain_d_key = storage::get_pos_gain_d_key(); let value = storage.read(&pos_gain_d_key)?; - let pos_gain_d: Decimal = value + let pos_gain_d = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; // read staked ratio let staked_ratio_key = storage::get_staked_ratio_key(); let value = storage.read(&staked_ratio_key)?; - let staked_ratio: Decimal = value + let staked_ratio = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; // read PoS inflation rate let pos_inflation_key = storage::get_pos_inflation_amount_key(); let value = storage.read(&pos_inflation_key)?; - let pos_inflation_amount: u64 = value + let pos_inflation_amount = value .ok_or(ReadError::ParametersMissing) .into_storage_result()?; diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 3945ba936a..4dec02d4f5 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -9,7 +9,8 @@ use masp_primitives::merkle_tree::FrozenCommitmentTree; use masp_primitives::sapling::Node; use crate::types::address::Address; -use crate::types::storage::Epoch; +use crate::types::storage::{Epoch, Key}; +use crate::types::token::MaspDenom; /// A representation of the conversion state #[derive(Debug, Default, BorshSerialize, BorshDeserialize)] @@ -19,7 +20,16 @@ pub struct ConversionState { /// The tree currently containing all the conversions pub tree: FrozenCommitmentTree, /// Map assets to their latest conversion and position in Merkle tree - pub assets: BTreeMap, + #[allow(clippy::type_complexity)] + pub assets: BTreeMap< + AssetType, + ( + (Address, Option, MaspDenom), + Epoch, + AllowedConversion, + usize, + ), + >, } // This is only enabled when "wasm-runtime" is on, because we're using rayon @@ -49,54 +59,70 @@ where let masp_rewards = address::masp_rewards(); // The total transparent value of the rewards being distributed - let mut total_reward = token::Amount::from(0); + let mut total_reward = token::Amount::native_whole(0); // Construct MASP asset type for rewards. Always timestamp reward tokens // with the zeroth epoch to minimize the number of convert notes clients // have to use. This trick works under the assumption that reward tokens // from different epochs are exactly equivalent. - let reward_asset_bytes = (address::nam(), 0u64) - .try_to_vec() - .expect("unable to serialize address and epoch"); - let reward_asset = AssetType::new(reward_asset_bytes.as_ref()) - .expect("unable to derive asset identifier"); + let reward_asset = + encode_asset_type(address::nam(), &None, MaspDenom::Zero, Epoch(0)); // Conversions from the previous to current asset for each address - let mut current_convs = BTreeMap::::new(); + let mut current_convs = + BTreeMap::<(Address, Option, MaspDenom), AllowedConversion>::new(); // Reward all tokens according to above reward rates - for (addr, reward) in &masp_rewards { - // Dispence a transparent reward in parallel to the shielded rewards - let addr_bal: token::Amount = wl_storage - .read(&token::balance_key(addr, &masp_addr))? - .unwrap_or_default(); + for ((addr, sub_prefix), reward) in &masp_rewards { + // Dispense a transparent reward in parallel to the shielded rewards + let addr_bal: token::Amount = match sub_prefix { + None => wl_storage + .read(&token::balance_key(addr, &masp_addr))? + .unwrap_or_default(), + Some(sub) => wl_storage + .read(&token::multitoken_balance_key( + &token::multitoken_balance_prefix(addr, sub), + &masp_addr, + ))? + .unwrap_or_default(), + }; // The reward for each reward.1 units of the current asset is // reward.0 units of the reward token // Since floor(a) + floor(b) <= floor(a+b), there will always be // enough rewards to reimburse users total_reward += (addr_bal * *reward).0; - // Provide an allowed conversion from previous timestamp. The - // negative sign allows each instance of the old asset to be - // cancelled out/replaced with the new asset - let old_asset = - encode_asset_type(addr.clone(), wl_storage.storage.last_epoch); - let new_asset = - encode_asset_type(addr.clone(), wl_storage.storage.block.epoch); - current_convs.insert( - addr.clone(), - (MaspAmount::from_pair(old_asset, -(reward.1 as i64)).unwrap() - + MaspAmount::from_pair(new_asset, reward.1).unwrap() - + MaspAmount::from_pair(reward_asset, reward.0).unwrap()) - .into(), - ); - // Add a conversion from the previous asset type - wl_storage.storage.conversion_state.assets.insert( - old_asset, - ( + for denom in token::MaspDenom::iter() { + // Provide an allowed conversion from previous timestamp. The + // negative sign allows each instance of the old asset to be + // cancelled out/replaced with the new asset + let old_asset = encode_asset_type( addr.clone(), + sub_prefix, + denom, wl_storage.storage.last_epoch, - MaspAmount::zero().into(), - 0, - ), - ); + ); + let new_asset = encode_asset_type( + addr.clone(), + sub_prefix, + denom, + wl_storage.storage.block.epoch, + ); + current_convs.insert( + (addr.clone(), sub_prefix.clone(), denom), + (MaspAmount::from_pair(old_asset, -(reward.1 as i64)).unwrap() + + MaspAmount::from_pair(new_asset, reward.1).unwrap() + + MaspAmount::from_pair(reward_asset, reward.0).unwrap()) + .into(), + ); + // Add a conversion from the previous asset type + wl_storage.storage.conversion_state.assets.insert( + old_asset, + ( + (addr.clone(), sub_prefix.clone(), denom), + wl_storage.storage.last_epoch, + MaspAmount::zero().into(), + 0, + ), + ); + } } // Try to distribute Merkle leaf updating as evenly as possible across @@ -119,9 +145,9 @@ where .into_par_iter() .with_min_len(notes_per_thread_min) .with_max_len(notes_per_thread_max) - .map(|(idx, (addr, _epoch, conv, pos))| { + .map(|(idx, (asset, _epoch, conv, pos))| { // Use transitivity to update conversion - *conv += current_convs[addr].clone(); + *conv += current_convs[asset].clone(); // Update conversion position to leaf we are about to create *pos = idx; // The merkle tree need only provide the conversion commitment, @@ -162,20 +188,26 @@ where // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree - for addr in masp_rewards.keys() { - // Add the decoding entry for the new asset type. An uncommited - // node position is used since this is not a conversion. - let new_asset = - encode_asset_type(addr.clone(), wl_storage.storage.block.epoch); - wl_storage.storage.conversion_state.assets.insert( - new_asset, - ( + for (addr, sub_prefix) in masp_rewards.keys() { + for denom in token::MaspDenom::iter() { + // Add the decoding entry for the new asset type. An uncommited + // node position is used since this is not a conversion. + let new_asset = encode_asset_type( addr.clone(), + sub_prefix, + denom, wl_storage.storage.block.epoch, - MaspAmount::zero().into(), - wl_storage.storage.conversion_state.tree.size(), - ), - ); + ); + wl_storage.storage.conversion_state.assets.insert( + new_asset, + ( + (addr.clone(), sub_prefix.clone(), denom), + wl_storage.storage.block.epoch, + MaspAmount::zero().into(), + wl_storage.storage.conversion_state.tree.size(), + ), + ); + } } // Save the current conversion state in order to avoid computing @@ -195,8 +227,21 @@ where } /// Construct MASP asset type with given epoch for given token -pub fn encode_asset_type(addr: Address, epoch: Epoch) -> AssetType { - let new_asset_bytes = (addr, epoch.0) +pub fn encode_asset_type( + addr: Address, + sub_prefix: &Option, + denom: MaspDenom, + epoch: Epoch, +) -> AssetType { + let new_asset_bytes = ( + addr, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + epoch.0, + ) .try_to_vec() .expect("unable to serialize address and epoch"); AssetType::new(new_asset_bytes.as_ref()) diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index e53178b157..9955456830 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -514,21 +514,10 @@ impl DB for MockDB { impl<'iter> DBIter<'iter> for MockDB { type PrefixIter = MockPrefixIterator; - fn iter_optional_prefix( - &'iter self, - prefix: Option<&Key>, - ) -> MockPrefixIterator { + fn iter_prefix(&'iter self, prefix: Option<&Key>) -> MockPrefixIterator { let db_prefix = "subspace/".to_owned(); - let prefix = format!( - "{}{}", - db_prefix, - match prefix { - None => "".to_string(), - Some(prefix) => { - prefix.to_string() - } - } - ); + let prefix_str = prefix.map(|k| k.to_string()).unwrap_or_default(); + let prefix = format!("{}{}", db_prefix, prefix_str); let iter = self.0.borrow().clone().into_iter(); MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) } diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 137c7b9e53..b60ad4ff8c 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -340,20 +340,7 @@ pub trait DBIter<'iter> { /// /// Read account subspace key value pairs with the given prefix from the DB, /// ordered by the storage keys. - fn iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter { - self.iter_optional_prefix(Some(prefix)) - } - - /// Iterate over all subspace keys - fn iter_all(&'iter self) -> Self::PrefixIter { - self.iter_optional_prefix(None) - } - - /// Iterate over subspace keys, with optional prefix - fn iter_optional_prefix( - &'iter self, - prefix: Option<&Key>, - ) -> Self::PrefixIter; + fn iter_prefix(&'iter self, prefix: Option<&Key>) -> Self::PrefixIter; /// Read results subspace key value pairs from the DB fn iter_results(&'iter self) -> Self::PrefixIter; @@ -600,10 +587,7 @@ where &self, prefix: &Key, ) -> (>::PrefixIter, u64) { - ( - self.db.iter_optional_prefix(Some(prefix)), - prefix.len() as _, - ) + (self.db.iter_prefix(Some(prefix)), prefix.len() as _) } /// Returns a prefix iterator and the gas cost @@ -1072,11 +1056,11 @@ mod tests { use chrono::{TimeZone, Utc}; use proptest::prelude::*; use proptest::test_runner::Config; - use rust_decimal_macros::dec; use super::testing::*; use super::*; use crate::ledger::parameters::{self, Parameters}; + use crate::types::dec::Dec; use crate::types::time::{self, Duration}; prop_compose! { @@ -1155,10 +1139,10 @@ mod tests { tx_whitelist: vec![], implicit_vp_code_hash: Hash::zero(), epochs_per_year: 100, - pos_gain_p: dec!(0.1), - pos_gain_d: dec!(0.1), - staked_ratio: dec!(0.1), - pos_inflation_amount: 0, + pos_gain_p: Dec::new(1,1).expect("Cannot fail"), + pos_gain_d: Dec::new(1,1).expect("Cannot fail"), + staked_ratio: Dec::new(1,1).expect("Cannot fail"), + pos_inflation_amount: token::Amount::zero(), #[cfg(not(feature = "mainnet"))] faucet_account: None, #[cfg(not(feature = "mainnet"))] diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 4ca76aa379..cdb29668bf 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -236,7 +236,7 @@ where D: DB + for<'iter_> DBIter<'iter_>, H: StorageHasher, { - let storage_iter = storage.db.iter_optional_prefix(Some(prefix)).peekable(); + let storage_iter = storage.db.iter_prefix(Some(prefix)).peekable(); let write_log_iter = write_log.iter_prefix_pre(prefix).peekable(); ( PrefixIter { @@ -261,7 +261,7 @@ where D: DB + for<'iter_> DBIter<'iter_>, H: StorageHasher, { - let storage_iter = storage.db.iter_optional_prefix(Some(prefix)).peekable(); + let storage_iter = storage.db.iter_prefix(Some(prefix)).peekable(); let write_log_iter = write_log.iter_prefix_post(prefix).peekable(); ( PrefixIter { diff --git a/core/src/ledger/storage_api/governance.rs b/core/src/ledger/storage_api/governance.rs index b71f4a6e40..87cab4ea0f 100644 --- a/core/src/ledger/storage_api/governance.rs +++ b/core/src/ledger/storage_api/governance.rs @@ -11,6 +11,8 @@ use crate::types::transaction::governance::{ pub fn init_proposal( storage: &mut S, data: InitProposalData, + content: Vec, + code: Option>, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -23,18 +25,21 @@ where }; let content_key = storage::get_content_key(proposal_id); - storage.write_bytes(&content_key, data.content)?; + storage.write_bytes(&content_key, content)?; let author_key = storage::get_author_key(proposal_id); storage.write(&author_key, data.author.clone())?; let proposal_type_key = storage::get_proposal_type_key(proposal_id); match data.r#type { - ProposalType::Default(Some(ref code)) => { + ProposalType::Default(Some(_)) => { // Remove wasm code and write it under a different subkey storage.write(&proposal_type_key, ProposalType::Default(None))?; let proposal_code_key = storage::get_proposal_code_key(proposal_id); - storage.write_bytes(&proposal_code_key, code)? + let proposal_code = code.clone().ok_or(storage_api::Error::new_const( + "Missing proposal code", + ))?; + storage.write_bytes(&proposal_code_key, proposal_code)? } _ => storage.write(&proposal_type_key, data.r#type.clone())?, } @@ -49,8 +54,11 @@ where let grace_epoch_key = storage::get_grace_epoch_key(proposal_id); storage.write(&grace_epoch_key, data.grace_epoch)?; - if let ProposalType::Default(Some(proposal_code)) = data.r#type { + if let ProposalType::Default(Some(_)) = data.r#type { let proposal_code_key = storage::get_proposal_code_key(proposal_id); + let proposal_code = code.ok_or(storage_api::Error::new_const( + "Missing proposal code", + ))?; storage.write_bytes(&proposal_code_key, proposal_code)?; } diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 8cccc2d3a6..880d748274 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -3,6 +3,8 @@ use super::{StorageRead, StorageWrite}; use crate::ledger::storage_api; use crate::types::address::Address; +use crate::types::storage::DbKeySeg::StringSeg; +use crate::types::storage::Key; use crate::types::token; pub use crate::types::token::{ balance_key, is_balance_key, is_total_supply_key, total_supply_key, Amount, @@ -36,6 +38,45 @@ where Ok(balance) } +/// Read the denomination of a given token, if any. Note that native +/// transparent tokens do not have this set and instead use the constant +/// [`token::NATIVE_MAX_DECIMAL_PLACES`]. +pub fn read_denom( + storage: &S, + token: &Address, + sub_prefix: Option<&Key>, +) -> storage_api::Result> +where + S: StorageRead, +{ + if let Some(sub_prefix) = sub_prefix { + if sub_prefix.segments.contains(&StringSeg("ibc".to_string())) { + return Ok(Some(token::NATIVE_MAX_DECIMAL_PLACES.into())); + } + } + let key = token::denom_key(token, sub_prefix); + storage.read(&key).map(|opt_denom| { + Some( + opt_denom + .unwrap_or_else(|| token::NATIVE_MAX_DECIMAL_PLACES.into()), + ) + }) +} + +/// Write the denomination of a given token. +pub fn write_denom( + storage: &mut S, + token: &Address, + sub_prefix: Option<&Key>, + denom: token::Denomination, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = token::denom_key(token, sub_prefix); + storage.write(&key, denom) +} + /// Transfer `token` from `src` to `dest`. Returns an `Err` if `src` has /// insufficient balance or if the transfer the `dest` would overflow (This can /// only happen if the total supply does't fit in `token::Amount`). diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index e350ed4ee7..d8f020def6 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::collections::HashSet; use std::convert::TryFrom; @@ -22,8 +23,9 @@ use crate::tendermint_proto::abci::ResponseDeliverTx; use crate::types::address::Address; use crate::types::chain::ChainId; use crate::types::key::*; -use crate::types::storage::Epoch; +use crate::types::storage::{Epoch, Key}; use crate::types::time::DateTimeUtc; +use crate::types::token::MaspDenom; #[cfg(feature = "ferveo-tpke")] use crate::types::token::Transfer; #[cfg(feature = "ferveo-tpke")] @@ -205,25 +207,27 @@ pub struct Signature { /// Additional random data salt: [u8; 8], /// The hash of the section being signed - target: crate::types::hash::Hash, - /// The signature over the above has - pub signature: common::Signature, - /// The public key to verrify the above siggnature + targets: Vec, + /// The public key to verify the below signature pub_key: common::PublicKey, + /// The signature over the above hashes + pub signature: Option, } impl Signature { /// Sign the given section hash with the given key and return a section pub fn new( - target: &crate::types::hash::Hash, + targets: Vec, sec_key: &common::SecretKey, ) -> Self { - Self { + let mut sec = Self { salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), - target: *target, - signature: common::SigScheme::sign(sec_key, target), + targets, pub_key: sec_key.ref_to(), - } + signature: None, + }; + sec.signature = Some(common::SigScheme::sign(sec_key, sec.get_hash())); + sec } /// Hash this signature section @@ -234,6 +238,29 @@ impl Signature { ); hasher } + + /// Get the hash of this section + pub fn get_hash(&self) -> crate::types::hash::Hash { + crate::types::hash::Hash( + self.hash(&mut Sha256::new()).finalize_reset().into(), + ) + } + + /// Verify that the signature contained in this section is valid + pub fn verify_signature(&self) -> std::result::Result<(), VerifySigError> { + let signature = + self.signature.as_ref().ok_or(VerifySigError::MissingData)?; + common::SigScheme::verify_signature_raw( + &self.pub_key, + &Self { + signature: None, + ..self.clone() + } + .get_hash() + .0, + signature, + ) + } } /// Represents a section obtained by encrypting another section @@ -478,7 +505,7 @@ pub struct MaspBuilder { pub target: crate::types::hash::Hash, /// The decoded set of asset types used by the transaction. Useful for /// offline wallets trying to display AssetTypes. - pub asset_types: HashSet<(Address, Epoch)>, + pub asset_types: HashSet<(Address, Option, MaspDenom, Epoch)>, /// Track how Info objects map to descriptors and outputs #[serde( serialize_with = "borsh_serde::", @@ -549,6 +576,8 @@ pub enum Section { /// A section providing the auxiliary inputs used to construct a MASP /// transaction. Only send to wallet, never send to protocol. MaspBuilder(MaspBuilder), + /// Wrap a header with a section for the purposes of computing hashes + Header(Header), } impl Section { @@ -571,17 +600,14 @@ impl Section { hasher.update(tx.txid().as_ref()); hasher } + Self::Header(header) => header.hash(hasher), } } - /// Sign over the hash of this section and return a signature section that - /// can be added to the container transaction - pub fn sign(&self, sec_key: &common::SecretKey) -> Signature { - let mut hasher = Sha256::new(); - self.hash(&mut hasher); - Signature::new( - &crate::types::hash::Hash(hasher.finalize().into()), - sec_key, + /// Get the hash of this section + pub fn get_hash(&self) -> crate::types::hash::Hash { + crate::types::hash::Hash( + self.hash(&mut Sha256::new()).finalize_reset().into(), ) } @@ -802,9 +828,16 @@ impl Tx { /// Get the transaction header hash pub fn header_hash(&self) -> crate::types::hash::Hash { - crate::types::hash::Hash( - self.header.hash(&mut Sha256::new()).finalize_reset().into(), - ) + Section::Header(self.header.clone()).get_hash() + } + + /// Get hashes of all the sections in this transaction + pub fn sechashes(&self) -> Vec { + let mut hashes = vec![self.header_hash()]; + for sec in &self.sections { + hashes.push(sec.get_hash()); + } + hashes } /// Update the header whilst maintaining existing cross-references @@ -817,13 +850,13 @@ impl Tx { pub fn get_section( &self, hash: &crate::types::hash::Hash, - ) -> Option<&Section> { + ) -> Option> { + if self.header_hash() == *hash { + return Some(Cow::Owned(Section::Header(self.header.clone()))); + } for section in &self.sections { - let sechash = crate::types::hash::Hash( - section.hash(&mut Sha256::new()).finalize_reset().into(), - ); - if sechash == *hash { - return Some(section); + if section.get_hash() == *hash { + return Some(Cow::Borrowed(section)); } } None @@ -847,7 +880,11 @@ impl Tx { /// Get the code designated by the transaction code hash in the header pub fn code(&self) -> Option> { - match self.get_section(self.code_sechash()) { + match self + .get_section(self.code_sechash()) + .as_ref() + .map(Cow::as_ref) + { Some(Section::Code(section)) => section.code.id(), _ => None, } @@ -856,10 +893,7 @@ impl Tx { /// Add the given code to the transaction and set code hash in the header pub fn set_code(&mut self, code: Code) -> &mut Section { let sec = Section::Code(code); - let mut hasher = Sha256::new(); - sec.hash(&mut hasher); - let hash = crate::types::hash::Hash(hasher.finalize().into()); - self.set_code_sechash(hash); + self.set_code_sechash(sec.get_hash()); self.sections.push(sec); self.sections.last_mut().unwrap() } @@ -877,17 +911,18 @@ impl Tx { /// Add the given code to the transaction and set the hash in the header pub fn set_data(&mut self, data: Data) -> &mut Section { let sec = Section::Data(data); - let mut hasher = Sha256::new(); - sec.hash(&mut hasher); - let hash = crate::types::hash::Hash(hasher.finalize().into()); - self.set_data_sechash(hash); + self.set_data_sechash(sec.get_hash()); self.sections.push(sec); self.sections.last_mut().unwrap() } /// Get the data designated by the transaction data hash in the header pub fn data(&self) -> Option> { - match self.get_section(self.data_sechash()) { + match self + .get_section(self.data_sechash()) + .as_ref() + .map(Cow::as_ref) + { Some(Section::Data(data)) => Some(data.data.clone()), _ => None, } @@ -904,21 +939,32 @@ impl Tx { bytes } - /// Verify that the section with the given hash has been signed by the given - /// public key + /// Verify that the sections with the given hashes have been signed together + /// by the given public key. I.e. this function looks for one signature that + /// covers over the given slice of hashes. pub fn verify_signature( &self, pk: &common::PublicKey, - hash: &crate::types::hash::Hash, - ) -> std::result::Result<(), VerifySigError> { + hashes: &[crate::types::hash::Hash], + ) -> std::result::Result<&Signature, VerifySigError> { for section in &self.sections { if let Section::Signature(sig_sec) = section { - if sig_sec.pub_key == *pk && sig_sec.target == *hash { - return common::SigScheme::verify_signature_raw( - pk, - &hash.0, - &sig_sec.signature, - ); + // Check that the signer is matched and that the hashes being + // checked are a subset of those in this section + if sig_sec.pub_key == *pk + && hashes.iter().all(|x| { + sig_sec.targets.contains(x) || section.get_hash() == *x + }) + { + // Ensure that all the sections the signature signs over are + // present + for target in &sig_sec.targets { + if self.get_section(target).is_none() { + return Err(VerifySigError::MissingData); + } + } + // Finally verify that the signature itself is valid + return sig_sec.verify_signature().map(|_| sig_sec); } } } @@ -974,7 +1020,8 @@ impl Tx { // Iterate backwrds to sidestep the effects of deletion on indexing for i in (0..self.sections.len()).rev() { match &self.sections[i] { - Section::Signature(sig) if sig.target == header_hash => {} + Section::Signature(sig) + if sig.targets.contains(&header_hash) => {} // Add eligible section to the list of sections to encrypt _ => plaintexts.push(self.sections.remove(i)), } @@ -998,35 +1045,35 @@ impl Tx { /// the Tx and verify it is of the appropriate form. This means /// 1. The wrapper tx is indeed signed /// 2. The signature is valid - pub fn validate_header(&self) -> std::result::Result<(), TxError> { + pub fn validate_tx( + &self, + ) -> std::result::Result, TxError> { match &self.header.tx_type { // verify signature and extract signed data - TxType::Wrapper(wrapper) => { - self.verify_signature(&wrapper.pk, &self.header_hash()) - .map_err(|err| { - TxError::SigError(format!( - "WrapperTx signature verification failed: {}", - err - )) - })?; - Ok(()) - } + TxType::Wrapper(wrapper) => self + .verify_signature(&wrapper.pk, &self.sechashes()) + .map(Option::Some) + .map_err(|err| { + TxError::SigError(format!( + "WrapperTx signature verification failed: {}", + err + )) + }), // verify signature and extract signed data #[cfg(feature = "ferveo-tpke")] - TxType::Protocol(protocol) => { - self.verify_signature(&protocol.pk, &self.header_hash()) - .map_err(|err| { - TxError::SigError(format!( - "ProtocolTx signature verification failed: {}", - err - )) - })?; - Ok(()) - } + TxType::Protocol(protocol) => self + .verify_signature(&protocol.pk, &self.sechashes()) + .map(Option::Some) + .map_err(|err| { + TxError::SigError(format!( + "ProtocolTx signature verification failed: {}", + err + )) + }), // we extract the signed data, but don't check the signature - TxType::Decrypted(_) => Ok(()), + TxType::Decrypted(_) => Ok(None), // return as is - TxType::Raw => Ok(()), + TxType::Raw => Ok(None), } } diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 439adddc2d..32a5d1c6cd 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -16,6 +16,8 @@ use thiserror::Error; use crate::ibc::signer::Signer; use crate::types::key; use crate::types::key::PublicKeyHash; +use crate::types::storage::Key; +use crate::types::token::Denomination; /// The length of an established [`Address`] encoded with Borsh. pub const ESTABLISHED_ADDRESS_BYTES_LEN: usize = 21; @@ -627,18 +629,34 @@ pub fn masp_tx_key() -> crate::types::key::common::SecretKey { common::SecretKey::try_from_slice(bytes.as_ref()).unwrap() } +/// Temporary helper for testing, a hash map of tokens addresses with their +/// informal currency codes and number of decimal places. +pub fn tokens() -> HashMap { + vec![ + (nam(), ("NAM", 6.into())), + (btc(), ("BTC", 8.into())), + (eth(), ("ETH", 18.into())), + (dot(), ("DOT", 10.into())), + (schnitzel(), ("Schnitzel", 6.into())), + (apfel(), ("Apfel", 6.into())), + (kartoffel(), ("Kartoffel", 6.into())), + ] + .into_iter() + .collect() +} + /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. -pub fn masp_rewards() -> HashMap { +pub fn masp_rewards() -> HashMap<(Address, Option), (u64, u64)> { vec![ - (nam(), (0, 100)), - (btc(), (1, 100)), - (eth(), (2, 100)), - (dot(), (3, 100)), - (schnitzel(), (4, 100)), - (apfel(), (5, 100)), - (kartoffel(), (6, 100)), + ((nam(), None), (0, 100)), + ((btc(), None), (1, 100)), + ((eth(), None), (2, 100)), + ((dot(), None), (3, 100)), + ((schnitzel(), None), (4, 100)), + ((apfel(), None), (5, 100)), + ((kartoffel(), None), (6, 100)), ] .into_iter() .collect() diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs new file mode 100644 index 0000000000..6870641bda --- /dev/null +++ b/core/src/types/dec.rs @@ -0,0 +1,625 @@ +//! A non-negative fixed precision decimal type for computation primarily in the +//! PoS module. For rounding, any computation that exceeds the specified +//! precision is truncated down to the closest value with the specified +//! precision. + +use std::fmt::{Debug, Display, Formatter}; +use std::ops::{Add, AddAssign, Div, Mul, Sub}; +use std::str::FromStr; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use eyre::eyre; +use serde::{Deserialize, Serialize}; + +use super::token::NATIVE_MAX_DECIMAL_PLACES; +use crate::types::token::{Amount, Change}; +use crate::types::uint::{Uint, I256}; + +/// The number of Dec places for PoS rational calculations +pub const POS_DECIMAL_PRECISION: u8 = 12; + +#[derive(thiserror::Error, Debug)] +#[error(transparent)] +/// Generic error [`Dec`] operations can return +pub struct Error(#[from] eyre::Error); + +/// Generic result type for fallible [`Dec`] operations +pub type Result = std::result::Result; + +/// A 256 bit number with [`POS_DECIMAL_PRECISION`] number of Dec places. +/// +/// To be precise, an instance X of this type should be interpeted as the Dec +/// X * 10 ^ (-[`POS_DECIMAL_PRECISION`]) +#[derive( + Clone, + Copy, + Default, + BorshSerialize, + BorshDeserialize, + BorshSchema, + PartialEq, + Serialize, + Deserialize, + Eq, + PartialOrd, + Ord, + Hash, +)] +#[serde(try_from = "String")] +#[serde(into = "String")] +pub struct Dec(pub I256); + +impl Dec { + /// Division with truncation (TODO: better description) + pub fn trunc_div(&self, rhs: &Self) -> Option { + let is_neg = self.0.is_negative() ^ rhs.0.is_negative(); + let inner_uint = self.0.abs(); + let inner_rhs_uint = rhs.0.abs(); + match inner_uint + .fixed_precision_div(&inner_rhs_uint, POS_DECIMAL_PRECISION) + { + Some(res) => { + let res = I256::try_from(res).ok()?; + if is_neg { + Some(Self(-res)) + } else { + Some(Self(res)) + } + } + None => None, + } + } + + /// The representation of 0 + pub fn zero() -> Self { + Self(I256::zero()) + } + + /// Check if value is zero + pub fn is_zero(&self) -> bool { + *self == Self::zero() + } + + /// The representation of 1 + pub fn one() -> Self { + Self(I256( + Uint::one() * Uint::exp10(POS_DECIMAL_PRECISION as usize), + )) + } + + /// The representation of 2 + pub fn two() -> Self { + Self::one() + Self::one() + } + + /// Create a new [`Dec`] using a mantissa and a scale. + pub fn new(mantissa: i128, scale: u8) -> Option { + if scale > POS_DECIMAL_PRECISION { + None + } else { + let abs = u64::try_from(mantissa.abs()).ok()?; + match Uint::exp10((POS_DECIMAL_PRECISION - scale) as usize) + .checked_mul(Uint::from(abs)) + { + Some(res) => { + if mantissa.is_negative() { + Some(Self(-I256(res))) + } else { + Some(Self(I256(res))) + } + } + None => None, + } + } + } + + /// Get the non-negative difference between two [`Dec`]s. + pub fn abs_diff(&self, other: &Self) -> Self { + if self > other { + *self - *other + } else { + *other - *self + } + } + + /// Get the absolute value of self as integer + pub fn abs(&self) -> Uint { + self.0.abs() + } + + /// Convert the Dec type into a I256 with truncation + pub fn to_i256(&self) -> I256 { + self.0 / Uint::exp10(POS_DECIMAL_PRECISION as usize) + } + + /// Convert the Dec type into a Uint with truncation + pub fn to_uint(&self) -> Option { + if self.is_negative() { + None + } else { + Some(self.0.abs() / Uint::exp10(POS_DECIMAL_PRECISION as usize)) + } + } + + /// Do subtraction of two [`Dec`]s If and only if the value is + /// greater + pub fn checked_sub(&self, other: &Self) -> Option { + if self > other { + Some(*self - *other) + } else { + None + } + } + + /// Return if the [`Dec`] is negative + pub fn is_negative(&self) -> bool { + self.0.is_negative() + } +} + +impl FromStr for Dec { + type Err = Error; + + fn from_str(s: &str) -> Result { + let ((large, small), is_neg) = if let Some(strip) = s.strip_prefix('-') + { + (strip.split_once('.').unwrap_or((s, "0")), true) + } else { + (s.split_once('.').unwrap_or((s, "0")), false) + }; + + let num_large = Uint::from_str_radix(large, 10).map_err(|e| { + eyre!("Could not parse {} as an integer: {}", large, e) + })?; + + // In theory we could allow this, but it is aesthetically offensive. + // Thus we don't. + if small.is_empty() { + return Err(eyre!( + "Failed to parse Dec from string as there were no numbers \ + following the decimal point." + ) + .into()); + } + + let trimmed = small + .trim_end_matches('0') + .chars() + .take(POS_DECIMAL_PRECISION as usize) + .collect::(); + let decimal_part = if trimmed.is_empty() { + Uint::zero() + } else { + Uint::from_str_radix(&trimmed, 10).map_err(|e| { + eyre!("Could not parse .{} as decimals: {}", small, e) + })? * Uint::exp10(POS_DECIMAL_PRECISION as usize - trimmed.len()) + }; + let int_part = Uint::exp10(POS_DECIMAL_PRECISION as usize) + .checked_mul(num_large) + .ok_or_else(|| { + eyre!( + "The number {} is too large to fit in the Dec type.", + num_large + ) + })?; + let inner = I256::try_from(int_part + decimal_part) + .map_err(|e| eyre!("Could not convert Uint to I256: {}", e))?; + if is_neg { + Ok(Dec(-inner)) + } else { + Ok(Dec(inner)) + } + } +} + +impl TryFrom for Dec { + type Error = Error; + + fn try_from(value: String) -> Result { + Self::from_str(&value) + } +} + +impl From for Dec { + fn from(amt: Amount) -> Self { + match I256::try_from(amt.raw_amount()).ok() { + Some(raw) => Self( + raw * Uint::exp10( + (POS_DECIMAL_PRECISION - NATIVE_MAX_DECIMAL_PLACES) + as usize, + ), + ), + None => Self::zero(), + } + } +} + +impl TryFrom for Dec { + type Error = Error; + + fn try_from(value: Uint) -> std::result::Result { + let i256 = I256::try_from(value) + .map_err(|e| eyre!("Could not convert Uint to I256: {}", e))?; + Ok(Self(i256 * Uint::exp10(POS_DECIMAL_PRECISION as usize))) + } +} + +impl From for Dec { + fn from(num: u64) -> Self { + Self(I256::from(num) * Uint::exp10(POS_DECIMAL_PRECISION as usize)) + } +} + +impl From for Dec { + fn from(num: usize) -> Self { + Self::from(num as u64) + } +} + +impl From for Dec { + fn from(num: i128) -> Self { + Self(I256::from(num) * Uint::exp10(POS_DECIMAL_PRECISION as usize)) + } +} + +impl TryFrom for Dec { + type Error = Box; + + fn try_from(num: u128) -> std::result::Result { + Ok(Self( + I256::try_from(Uint::from(num))? + * Uint::exp10(POS_DECIMAL_PRECISION as usize), + )) + } +} + +impl TryFrom for i128 { + type Error = std::io::Error; + + fn try_from(value: Dec) -> std::result::Result { + value.0.try_into() + } +} + +// Is error handling needed for this? +impl From for Dec { + fn from(num: I256) -> Self { + Self(num * Uint::exp10(POS_DECIMAL_PRECISION as usize)) + } +} + +impl From for String { + fn from(value: Dec) -> String { + value.to_string() + } +} + +impl Add for Dec { + type Output = Self; + + fn add(self, rhs: Dec) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Add for Dec { + type Output = Self; + + fn add(self, rhs: u64) -> Self::Output { + Self(self.0 + I256::from(rhs)) + } +} + +impl AddAssign for Dec { + fn add_assign(&mut self, rhs: Dec) { + *self = *self + rhs; + } +} + +impl Sub for Dec { + type Output = Self; + + fn sub(self, rhs: Dec) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl Mul for Dec { + type Output = Dec; + + fn mul(self, rhs: u64) -> Self::Output { + Self(self.0 * Uint::from(rhs)) + } +} + +impl Mul for Dec { + type Output = Dec; + + fn mul(self, rhs: u128) -> Self::Output { + Self(self.0 * Uint::from(rhs)) + } +} + +impl Mul for Dec { + type Output = Amount; + + fn mul(self, rhs: Amount) -> Self::Output { + if !self.is_negative() { + (rhs * self.0.abs()) / 10u64.pow(POS_DECIMAL_PRECISION as u32) + } else { + panic!("aaa"); + } + } +} + +impl Mul for Dec { + type Output = Change; + + fn mul(self, rhs: Change) -> Self::Output { + let tot = rhs * self.0; + let denom = Uint::from(10u64.pow(POS_DECIMAL_PRECISION as u32)); + tot / denom + } +} + +// TODO: is some checked arithmetic needed here to prevent overflows? +impl Mul for Dec { + type Output = Self; + + fn mul(self, rhs: Dec) -> Self::Output { + let prod = self.0 * rhs.0; + Self(prod / Uint::exp10(POS_DECIMAL_PRECISION as usize)) + } +} + +impl Div for Dec { + type Output = Self; + + /// Unchecked fixed precision division. + /// + /// # Panics: + /// + /// * Denominator is zero + /// * Scaling the left hand side by 10^([`POS_DECIMAL_PRECISION`]) + /// overflows 256 bits + fn div(self, rhs: Dec) -> Self::Output { + self.trunc_div(&rhs).unwrap() + } +} + +impl Div for Dec { + type Output = Self; + + fn div(self, rhs: u64) -> Self::Output { + Self(self.0 / Uint::from(rhs)) + } +} + +impl Display for Dec { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let is_neg = self.is_negative(); + let mut string = self.0.abs().to_string(); + if string.len() > POS_DECIMAL_PRECISION as usize { + let idx = string.len() - POS_DECIMAL_PRECISION as usize; + string.insert(idx, '.'); + } else { + let mut str_pre = "0.".to_string(); + for _ in 0..(POS_DECIMAL_PRECISION as usize - string.len()) { + str_pre.push('0'); + } + str_pre.push_str(string.as_str()); + string = str_pre; + }; + let stripped_string = string.trim_end_matches(['.', '0']); + if stripped_string.is_empty() { + f.write_str("0") + } else if is_neg { + let stripped_string = format!("-{}", stripped_string); + f.write_str(stripped_string.as_str()) + } else { + f.write_str(stripped_string) + } + } +} + +impl Debug for Dec { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string()) + } +} + +#[cfg(test)] +mod test_dec { + use super::*; + use crate::types::token::{Amount, Change}; + + #[derive(Debug, Serialize, Deserialize)] + struct SerializerTest { + dec: Dec, + } + + #[test] + fn dump_toml() { + let serializer = SerializerTest { + dec: Dec::new(3, 0).unwrap(), + }; + println!("{:?}", toml::to_string(&serializer)); + } + + /// Fill in tests later + #[test] + fn test_dec_basics() { + assert_eq!( + Dec::one() + Dec::new(3, 0).unwrap() / Dec::new(5, 0).unwrap(), + Dec::new(16, 1).unwrap() + ); + assert_eq!(Dec::new(1, 0).expect("Test failed"), Dec::one()); + assert_eq!(Dec::new(2, 0).expect("Test failed"), Dec::two()); + assert_eq!( + Dec(I256(Uint::from(1653))), + Dec::new(1653, POS_DECIMAL_PRECISION).expect("Test failed") + ); + assert_eq!( + Dec(I256::from(-48756)), + Dec::new(-48756, POS_DECIMAL_PRECISION).expect("Test failed") + ); + assert_eq!( + Dec::new(123456789, 4) + .expect("Test failed") + .to_uint() + .unwrap(), + Uint::from(12345) + ); + assert_eq!( + Dec::new(-123456789, 4).expect("Test failed").to_i256(), + I256::from(-12345) + ); + assert_eq!( + Dec::new(123, 4).expect("Test failed").to_uint().unwrap(), + Uint::zero() + ); + assert_eq!( + Dec::new(123, 4).expect("Test failed").to_i256(), + I256::zero() + ); + assert_eq!( + Dec::from_str("4876.3855") + .expect("Test failed") + .to_uint() + .unwrap(), + Uint::from(4876) + ); + assert_eq!( + Dec::from_str("4876.3855").expect("Test failed").to_i256(), + I256::from(4876) + ); + + // Fixed precision division is more thoroughly tested for the `Uint` + // type. These are sanity checks that the precision is correct. + assert_eq!( + Dec::new(1, POS_DECIMAL_PRECISION).expect("Test failed") + / Dec::new(1, POS_DECIMAL_PRECISION).expect("Test failed"), + Dec::one(), + ); + assert_eq!( + Dec::new(1, POS_DECIMAL_PRECISION).expect("Test failed") + / (Dec::new(1, 0).expect("Test failed") + Dec::one()), + Dec::zero(), + ); + assert_eq!( + Dec::new(1, POS_DECIMAL_PRECISION).expect("Test failed") + / Dec::two(), + Dec::zero(), + ); + + // Test Dec * Dec multiplication + assert!(Dec::new(32353, POS_DECIMAL_PRECISION + 1u8).is_none()); + let dec1 = Dec::new(12345654321, 12).expect("Test failed"); + let dec2 = Dec::new(9876789, 12).expect("Test failed"); + let exp_prod = Dec::new(121935, 12).expect("Test failed"); + let exp_quot = Dec::new(1249966393025101, 12).expect("Test failed"); + assert_eq!(dec1 * dec2, exp_prod); + assert_eq!(dec1 / dec2, exp_quot); + } + + /// Test the `Dec` and `Amount` interplay + #[test] + fn test_dec_and_amount() { + let amt = Amount::from(1018u64); + let dec = Dec::from_str("2.76").unwrap(); + + debug_assert_eq!( + Dec::from(amt), + Dec::new(1018, 6).expect("Test failed") + ); + debug_assert_eq!(dec * amt, Amount::from(2809u64)); + + let chg = -amt.change(); + debug_assert_eq!(dec * chg, Change::from(-2809i64)); + } + + #[test] + fn test_into() { + assert_eq!( + Dec::from(u64::MAX), + Dec::from_str("18446744073709551615.000000000000") + .expect("only 104 bits") + ) + } + + /// Test that parsing from string is correct. + #[test] + fn test_dec_from_string() { + // Fewer than six decimal places and non-zero integer part + assert_eq!( + Dec::from_str("3.14").expect("Test failed"), + Dec::new(314, 2).expect("Test failed"), + ); + + // more than 12 decimal places and zero integer part + assert_eq!( + Dec::from_str("0.1234567654321").expect("Test failed"), + Dec::new(123456765432, 12).expect("Test failed"), + ); + + // No zero before the decimal + assert_eq!( + Dec::from_str(".333333").expect("Test failed"), + Dec::new(333333, 6).expect("Test failed"), + ); + + // No decimal places + assert_eq!( + Dec::from_str("50").expect("Test failed"), + Dec::new(50, 0).expect("Test failed"), + ); + + // Test zero representations + assert_eq!(Dec::from_str("0").expect("Test failed"), Dec::zero()); + assert_eq!(Dec::from_str("0.0").expect("Test failed"), Dec::zero()); + assert_eq!(Dec::from_str(".0").expect("Test failed"), Dec::zero()); + + // Error conditions + + // Test that a decimal point must be followed by numbers + assert!(Dec::from_str("0.").is_err()); + // Test that multiple decimal points get caught + assert!(Dec::from_str("1.2.3").is_err()); + // Test that negative numbers are rejected + assert!(Dec::from_str("-1").is_err()); + // Test that non-numerics are caught + assert!(Dec::from_str("DEADBEEF.12").is_err()); + assert!(Dec::from_str("23.DEADBEEF").is_err()); + // Test that we catch strings overflowing 256 bits + let mut yuge = String::from("1"); + for _ in 0..80 { + yuge.push('0'); + } + assert!(Dec::from_str(&yuge).is_err()); + } + + /// Test that parsing from string is correct. + #[test] + fn test_dec_from_serde() { + assert_eq!( + serde_json::from_str::(r#""0.667""#).expect("all good"), + Dec::from_str("0.667").expect("should work") + ); + + let dec = Dec::from_str("0.667").unwrap(); + assert_eq!( + dec, + serde_json::from_str::(&serde_json::to_string(&dec).unwrap()) + .unwrap() + ); + } + + #[test] + fn test_dec_to_string() { + let one = Dec::one(); + let string = one.to_string(); + assert_eq!(&string, "1"); + let one_third = Dec::new(6667, 4).expect("Test failed"); + assert_eq!(&one_third.to_string(), "0.6667") + } +} diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs index dc17d07e22..d3450dccd1 100644 --- a/core/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -4,7 +4,6 @@ use std::collections::{BTreeMap, HashSet}; use std::fmt::{self, Display}; use borsh::{BorshDeserialize, BorshSerialize}; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -13,10 +12,13 @@ use crate::types::hash::Hash; use crate::types::key::common::{self, Signature}; use crate::types::key::SigScheme; use crate::types::storage::Epoch; -use crate::types::token::{Amount, SCALE}; +use crate::types::token::{ + Amount, DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES, +}; +use crate::types::uint::Uint; /// Type alias for vote power -pub type VotePower = u128; +pub type VotePower = Uint; /// A PGF cocuncil composed of the address and spending cap pub type Council = (Address, Amount); @@ -85,7 +87,8 @@ impl Display for ProposalVote { writeln!( f, "Council: {}, spending cap: {}", - address, spending_cap + address, + spending_cap.to_string_native() )? } @@ -109,6 +112,7 @@ pub enum ProposalVoteParseError { } /// The type of the tally +#[derive(Clone, Debug)] pub enum Tally { /// Default proposal Default, @@ -119,6 +123,7 @@ pub enum Tally { } /// The result of a proposal +#[derive(Clone, Debug)] pub enum TallyResult { /// Proposal was accepted with the associated value Passed(Tally), @@ -140,19 +145,30 @@ pub struct ProposalResult { impl Display for ProposalResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let percentage = Decimal::checked_div( - self.total_yay_power.into(), - self.total_voting_power.into(), - ) - .unwrap_or_default(); + let percentage = DenominatedAmount { + amount: Amount::from_uint( + self.total_yay_power + .fixed_precision_div(&self.total_voting_power, 4) + .unwrap_or_default(), + 0, + ) + .unwrap(), + denom: 2.into(), + }; write!( f, - "{} with {} yay votes over {} ({:.2}%)", + "{} with {} yay votes over {} ({}%)", self.result, - self.total_yay_power / SCALE as u128, - self.total_voting_power / SCALE as u128, - percentage.checked_mul(100.into()).unwrap_or_default() + DenominatedAmount { + amount: Amount::from_uint(self.total_yay_power, 0).unwrap(), + denom: NATIVE_MAX_DECIMAL_PLACES.into() + }, + DenominatedAmount { + amount: Amount::from_uint(self.total_voting_power, 0).unwrap(), + denom: NATIVE_MAX_DECIMAL_PLACES.into() + }, + percentage ) } } @@ -165,7 +181,8 @@ impl Display for TallyResult { Tally::PGFCouncil((council, cap)) => write!( f, "passed with PGF council address: {}, spending cap: {}", - council, cap + council, + cap.to_string_native() ), }, TallyResult::Rejected => write!(f, "rejected"), diff --git a/core/src/types/key/secp256k1.rs b/core/src/types/key/secp256k1.rs index b40ceb3e50..af40220a69 100644 --- a/core/src/types/key/secp256k1.rs +++ b/core/src/types/key/secp256k1.rs @@ -477,14 +477,14 @@ impl super::SigScheme for SigScheme { /// Sign the data with a key fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { - #[cfg(not(any(test, feature = "secp256k1-sign-verify")))] + #[cfg(not(any(test, feature = "secp256k1-sign")))] { // to avoid `unused-variables` warn let _ = (keypair, data); - panic!("\"secp256k1-sign-verify\" feature must be enabled"); + panic!("\"secp256k1-sign\" feature must be enabled"); } - #[cfg(any(test, feature = "secp256k1-sign-verify"))] + #[cfg(any(test, feature = "secp256k1-sign"))] { use sha2::{Digest, Sha256}; let hash = Sha256::digest(data.as_ref()); @@ -500,31 +500,21 @@ impl super::SigScheme for SigScheme { data: &T, sig: &Self::Signature, ) -> Result<(), VerifySigError> { - #[cfg(not(any(test, feature = "secp256k1-sign-verify")))] - { - // to avoid `unused-variables` warn - let _ = (pk, data, sig); - panic!("\"secp256k1-sign-verify\" feature must be enabled"); - } - - #[cfg(any(test, feature = "secp256k1-sign-verify"))] - { - use sha2::{Digest, Sha256}; - let bytes = &data - .try_to_vec() - .map_err(VerifySigError::DataEncodingError)?[..]; - let hash = Sha256::digest(bytes); - let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) - .expect("Error parsing given data"); - let is_valid = libsecp256k1::verify(message, &sig.0, &pk.0); - if is_valid { - Ok(()) - } else { - Err(VerifySigError::SigVerifyError(format!( - "Error verifying secp256k1 signature: {}", - libsecp256k1::Error::InvalidSignature - ))) - } + use sha2::{Digest, Sha256}; + let bytes = &data + .try_to_vec() + .map_err(VerifySigError::DataEncodingError)?[..]; + let hash = Sha256::digest(bytes); + let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) + .expect("Error parsing given data"); + let is_valid = libsecp256k1::verify(message, &sig.0, &pk.0); + if is_valid { + Ok(()) + } else { + Err(VerifySigError::SigVerifyError(format!( + "Error verifying secp256k1 signature: {}", + libsecp256k1::Error::InvalidSignature + ))) } } @@ -533,28 +523,18 @@ impl super::SigScheme for SigScheme { data: &[u8], sig: &Self::Signature, ) -> Result<(), VerifySigError> { - #[cfg(not(any(test, feature = "secp256k1-sign-verify")))] - { - // to avoid `unused-variables` warn - let _ = (pk, data, sig); - panic!("\"secp256k1-sign-verify\" feature must be enabled"); - } - - #[cfg(any(test, feature = "secp256k1-sign-verify"))] - { - use sha2::{Digest, Sha256}; - let hash = Sha256::digest(data); - let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) - .expect("Error parsing raw data"); - let is_valid = libsecp256k1::verify(message, &sig.0, &pk.0); - if is_valid { - Ok(()) - } else { - Err(VerifySigError::SigVerifyError(format!( - "Error verifying secp256k1 signature: {}", - libsecp256k1::Error::InvalidSignature - ))) - } + use sha2::{Digest, Sha256}; + let hash = Sha256::digest(data); + let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) + .expect("Error parsing raw data"); + let is_valid = libsecp256k1::verify(message, &sig.0, &pk.0); + if is_valid { + Ok(()) + } else { + Err(VerifySigError::SigVerifyError(format!( + "Error verifying secp256k1 signature: {}", + libsecp256k1::Error::InvalidSignature + ))) } } } diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index 0550060498..8bbd73eaf2 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -2,6 +2,7 @@ pub mod address; pub mod chain; +pub mod dec; pub mod governance; pub mod hash; pub mod ibc; @@ -12,4 +13,5 @@ pub mod storage; pub mod time; pub mod token; pub mod transaction; +pub mod uint; pub mod validity_predicate; diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 68ba40552d..c30319dedd 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1,19 +1,25 @@ //! A basic fungible token -use std::fmt::Display; +use std::fmt::{Display, Formatter}; use std::iter::Sum; -use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use rust_decimal::prelude::{Decimal, ToPrimitive}; +use data_encoding::BASE32HEX_NOPAD; use serde::{Deserialize, Serialize}; use thiserror::Error; +use super::dec::POS_DECIMAL_PRECISION; use crate::ibc::applications::transfer::Amount as IbcAmount; +use crate::ledger::storage_api::token::read_denom; +use crate::ledger::storage_api::StorageRead; use crate::types::address::{masp, Address, DecodeError as AddressError}; +use crate::types::dec::Dec; use crate::types::hash::Hash; +use crate::types::storage; use crate::types::storage::{DbKeySeg, Key, KeySeg}; +use crate::types::uint::{self, Uint, I256}; /// Amount in micro units. For different granularity another representation /// might be more appropriate. @@ -32,95 +38,341 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; Hash, )] pub struct Amount { - micro: u64, + raw: Uint, } -/// Maximum decimal places in a token [`Amount`] and [`Change`]. -pub const MAX_DECIMAL_PLACES: u32 = 6; -/// Decimal scale of token [`Amount`] and [`Change`]. -pub const SCALE: u64 = 1_000_000; +/// Maximum decimal places in a native token [`Amount`] and [`Change`]. +/// For non-native (e.g. ERC20 tokens) one must read the `denom_key` storage +/// key. +pub const NATIVE_MAX_DECIMAL_PLACES: u8 = 6; -/// The largest value that can be represented by this integer type -pub const MAX_AMOUNT: Amount = Amount { micro: u64::MAX }; +/// Decimal scale of a native token [`Amount`] and [`Change`]. +/// For non-native (e.g. ERC20 tokens) one must read the `denom_key` storage +/// key. +pub const NATIVE_SCALE: u64 = 1_000_000; /// A change in tokens amount -pub type Change = i128; +pub type Change = I256; impl Amount { - /// Returns whether an amount is zero. - pub fn is_zero(&self) -> bool { - self.micro == 0 - } - /// Get the amount as a [`Change`] pub fn change(&self) -> Change { - self.micro as Change + self.raw.try_into().unwrap() } /// Spend a given amount. - /// Panics when given `amount` > `self.micro` amount. + /// Panics when given `amount` > `self.raw` amount. pub fn spend(&mut self, amount: &Amount) { - self.micro = self.micro.checked_sub(amount.micro).unwrap(); + self.raw = self.raw.checked_sub(amount.raw).unwrap(); } /// Receive a given amount. - /// Panics on overflow. + /// Panics on overflow and when [`uint::MAX_SIGNED_VALUE`] is exceeded. pub fn receive(&mut self, amount: &Amount) { - self.micro = self.micro.checked_add(amount.micro).unwrap(); + self.raw = self.raw.checked_add(amount.raw).unwrap(); } - /// Create a new amount from whole number of tokens - pub const fn whole(amount: u64) -> Self { + /// Create a new amount of native token from whole number of tokens + pub fn native_whole(amount: u64) -> Self { Self { - micro: amount * SCALE, + raw: Uint::from(amount) * NATIVE_SCALE, } } + /// Get the raw [`Uint`] value, which represents namnam + pub fn raw_amount(&self) -> Uint { + self.raw + } + /// Create a new amount with the maximum value pub fn max() -> Self { - Self { micro: u64::MAX } + Self { + raw: uint::MAX_VALUE, + } + } + + /// Create a new amount with the maximum signed value + pub fn max_signed() -> Self { + Self { + raw: uint::MAX_SIGNED_VALUE, + } + } + + /// Zero [`Amount`]. + pub fn zero() -> Self { + Self::default() + } + + /// Check if [`Amount`] is zero. + pub fn is_zero(&self) -> bool { + self.raw == Uint::from(0) } - /// Checked addition. Returns `None` on overflow. + /// Checked addition. Returns `None` on overflow or if + /// the amount exceed [`uint::MAX_VALUE`] pub fn checked_add(&self, amount: Amount) -> Option { - self.micro - .checked_add(amount.micro) - .map(|result| Self { micro: result }) + self.raw.checked_add(amount.raw).and_then(|result| { + if result <= uint::MAX_VALUE { + Some(Self { raw: result }) + } else { + None + } + }) + } + + /// Checked addition. Returns `None` on overflow or if + /// the amount exceed [`uint::MAX_SIGNED_VALUE`] + pub fn checked_signed_add(&self, amount: Amount) -> Option { + self.raw.checked_add(amount.raw).and_then(|result| { + if result <= uint::MAX_SIGNED_VALUE { + Some(Self { raw: result }) + } else { + None + } + }) } /// Checked subtraction. Returns `None` on underflow pub fn checked_sub(&self, amount: Amount) -> Option { - self.micro - .checked_sub(amount.micro) - .map(|result| Self { micro: result }) + self.raw + .checked_sub(amount.raw) + .map(|result| Self { raw: result }) } - /// Create amount from Change - /// - /// # Panics - /// - /// Panics if the change is negative or overflows `u64`. + /// Create amount from the absolute value of `Change`. pub fn from_change(change: Change) -> Self { + Self { raw: change.abs() } + } + + /// Given a string and a denomination, parse an amount from string. + pub fn from_str( + string: impl AsRef, + denom: impl Into, + ) -> Result { + DenominatedAmount::from_str(string.as_ref())? + .increase_precision(denom.into().into()) + .map(Into::into) + } + + /// Attempt to convert an unsigned integer to an `Amount` with the + /// specified precision. + pub fn from_uint( + uint: impl Into, + denom: impl Into, + ) -> Result { + let denom = denom.into(); + match Uint::from(10) + .checked_pow(Uint::from(denom)) + .and_then(|scaling| scaling.checked_mul(uint.into())) + { + Some(amount) => Ok(Self { raw: amount }), + None => Err(AmountParseError::ConvertToDecimal), + } + } + + /// Given a u64 and [`MaspDenom`], construct the corresponding + /// amount. + pub fn from_masp_denominated(val: u64, denom: MaspDenom) -> Self { + let mut raw = [0u64; 4]; + raw[denom as usize] = val; + Self { raw: Uint(raw) } + } + + /// Get a string representation of a native token amount. + pub fn to_string_native(&self) -> String { + DenominatedAmount { + amount: *self, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + } + .to_string_precise() + } + + /// Add denomination info if it exists in storage. + pub fn denominated( + &self, + token: &Address, + sub_prefix: Option<&Key>, + storage: &impl StorageRead, + ) -> Option { + let denom = read_denom(storage, token, sub_prefix) + .expect("Should be able to read storage"); + denom.map(|denom| DenominatedAmount { + amount: *self, + denom, + }) + } + + /// Convert to an [`Amount`] under the assumption that the input + /// string encodes all necessary decimal places. + pub fn from_string_precise(string: &str) -> Result { + DenominatedAmount::from_str(string).map(|den| den.amount) + } +} + +/// Given a number represented as `M*B^D`, then +/// `M` is the matissa, `B` is the base and `D` +/// is the denomination, represented by this stuct. +#[derive( + Debug, + Copy, + Clone, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +#[serde(transparent)] +pub struct Denomination(pub u8); + +impl From for Denomination { + fn from(denom: u8) -> Self { + Self(denom) + } +} + +impl From for u8 { + fn from(denom: Denomination) -> Self { + denom.0 + } +} + +/// An amount with its denomination. +#[derive( + Debug, + Copy, + Clone, + Hash, + PartialEq, + Eq, + PartialOrd, + Ord, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +pub struct DenominatedAmount { + /// The mantissa + pub amount: Amount, + /// The number of decimal places in base ten. + pub denom: Denomination, +} + +impl DenominatedAmount { + /// A precise string representation. The number of + /// decimal places in this string gives the denomination. + /// This not true of the string produced by the `Display` + /// trait. + pub fn to_string_precise(&self) -> String { + let decimals = self.denom.0 as usize; + let mut string = self.amount.raw.to_string(); + if string.len() > decimals { + string.insert(string.len() - decimals, '.'); + } else { + for _ in string.len()..decimals { + string.insert(0, '0'); + } + string.insert(0, '.'); + string.insert(0, '0'); + } + string + } + + /// Find the minimal precision that holds this value losslessly. + /// This equates to stripping trailing zeros after the decimal + /// place. + pub fn canonical(self) -> Self { + let mut value = self.amount.raw; + let ten = Uint::from(10); + let mut denom = self.denom.0; + for _ in 0..self.denom.0 { + let (div, rem) = value.div_mod(ten); + if rem == Uint::zero() { + value = div; + denom -= 1; + } + } Self { - micro: change as u64, + amount: Amount { raw: value }, + denom: denom.into(), + } + } + + /// Attempt to increase the precision of an amount. Can fail + /// if the resulting amount does not fit into 256 bits. + pub fn increase_precision( + self, + denom: Denomination, + ) -> Result { + if denom.0 < self.denom.0 { + return Err(AmountParseError::PrecisionDecrease); } + Uint::from(10) + .checked_pow(Uint::from(denom.0 - self.denom.0)) + .and_then(|scaling| self.amount.raw.checked_mul(scaling)) + .map(|amount| Self { + amount: Amount { raw: amount }, + denom, + }) + .ok_or(AmountParseError::PrecisionOverflow) } +} - /// Convert the amount to [`Decimal`] ignoring its scale (i.e. as an integer - /// in micro units). - pub fn as_dec_unscaled(&self) -> Decimal { - Into::::into(self.micro) +impl Display for DenominatedAmount { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let string = self.to_string_precise(); + let string = string.trim_end_matches(&['0']); + let string = string.trim_end_matches(&['.']); + f.write_str(string) } +} - /// Convert from a [`Decimal`] that's not scaled (i.e. an integer - /// in micro units). - /// - /// # Panics - /// - /// Panics if the given decimal is not an integer that fits `u64`. - pub fn from_dec_unscaled(micro: Decimal) -> Self { - let res = micro.to_u64().unwrap(); - Self { micro: res } +impl FromStr for DenominatedAmount { + type Err = AmountParseError; + + fn from_str(s: &str) -> Result { + let precision = s.find('.').map(|pos| s.len() - pos - 1); + let digits = s + .chars() + .filter_map(|c| { + if c.is_numeric() { + c.to_digit(10).map(Uint::from) + } else { + None + } + }) + .rev() + .collect::>(); + if digits.len() != s.len() && precision.is_none() + || digits.len() != s.len() - 1 && precision.is_some() + { + return Err(AmountParseError::NotNumeric); + } + if digits.len() > 77 { + return Err(AmountParseError::ScaleTooLarge( + digits.len() as u32, + 77, + )); + } + let mut value = Uint::default(); + let ten = Uint::from(10); + for (pow, digit) in digits.into_iter().enumerate() { + value = ten + .checked_pow(Uint::from(pow)) + .and_then(|scaling| scaling.checked_mul(digit)) + .and_then(|scaled| value.checked_add(scaled)) + .ok_or(AmountParseError::InvalidRange)?; + } + let denom = Denomination(precision.unwrap_or_default() as u8); + Ok(Self { + amount: Amount { raw: value }, + denom, + }) } } @@ -132,12 +384,37 @@ impl serde::Serialize for Amount { where S: serde::Serializer, { - let amount_string = self.to_string(); + let amount_string = self.raw.to_string(); serde::Serialize::serialize(&amount_string, serializer) } } impl<'de> serde::Deserialize<'de> for Amount { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + let amount_string: String = + serde::Deserialize::deserialize(deserializer)?; + let amt = DenominatedAmount::from_str(&amount_string).unwrap(); + Ok(amt.amount) + } +} + +impl serde::Serialize for DenominatedAmount { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let amount_string = self.to_string_precise(); + serde::Serialize::serialize(&amount_string, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for DenominatedAmount { fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, @@ -149,34 +426,56 @@ impl<'de> serde::Deserialize<'de> for Amount { } } -impl From for Decimal { - fn from(amount: Amount) -> Self { - Into::::into(amount.micro) +impl<'a> From<&'a DenominatedAmount> for &'a Amount { + fn from(denom: &'a DenominatedAmount) -> Self { + &denom.amount } } -impl From for Amount { - fn from(micro: Decimal) -> Self { - let res = micro.to_u64().unwrap(); - Self { micro: res } +impl From for Amount { + fn from(denom: DenominatedAmount) -> Self { + denom.amount } } +// Treats the u64 as a value of the raw amount (namnam) impl From for Amount { - fn from(micro: u64) -> Self { - Self { micro } + fn from(val: u64) -> Amount { + Amount { + raw: Uint::from(val), + } } } -impl From for u64 { - fn from(amount: Amount) -> Self { - amount.micro +impl From for Amount { + fn from(dec: Dec) -> Amount { + if !dec.is_negative() { + Amount { + raw: dec.0.abs() / Uint::exp10(POS_DECIMAL_PRECISION as usize), + } + } else { + panic!( + "The Dec value is negative and cannot be multiplied by an \ + Amount" + ) + } } } -impl From for u128 { - fn from(amount: Amount) -> Self { - u128::from(amount.micro) +impl TryFrom for u128 { + type Error = std::io::Error; + + fn try_from(value: Amount) -> Result { + let Uint(arr) = value.raw; + for word in arr.iter().skip(2) { + if *word != 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Integer overflow when casting to u128", + )); + } + } + Ok(value.raw.low_u128()) } } @@ -184,44 +483,84 @@ impl Add for Amount { type Output = Amount; fn add(mut self, rhs: Self) -> Self::Output { - self.micro += rhs.micro; + self.raw += rhs.raw; self } } +impl Add for Amount { + type Output = Self; + + fn add(self, rhs: u64) -> Self::Output { + Self { + raw: self.raw + Uint::from(rhs), + } + } +} + impl Mul for Amount { type Output = Amount; fn mul(mut self, rhs: u64) -> Self::Output { - self.micro *= rhs; + self.raw *= rhs; + self + } +} + +impl Mul for u64 { + type Output = Amount; + + fn mul(self, mut rhs: Amount) -> Self::Output { + rhs.raw *= self; + rhs + } +} + +impl Mul for Amount { + type Output = Amount; + + fn mul(mut self, rhs: Uint) -> Self::Output { + self.raw *= rhs; + self + } +} + +impl Mul for Amount { + type Output = Amount; + + fn mul(mut self, rhs: Amount) -> Self::Output { + self.raw *= rhs.raw; self } } /// A combination of Euclidean division and fractions: -/// x*(a,b) = (a*(x//b), x%b) +/// x*(a,b) = (a*(x//b), x%b). impl Mul<(u64, u64)> for Amount { type Output = (Amount, Amount); fn mul(mut self, rhs: (u64, u64)) -> Self::Output { - let ant = Amount::from((self.micro / rhs.1) * rhs.0); - self.micro %= rhs.1; - (ant, self) + let amt = Amount { + raw: (self.raw / rhs.1) * rhs.0, + }; + self.raw %= rhs.1; + (amt, self) } } -impl Mul for u64 { - type Output = Amount; +impl Div for Amount { + type Output = Self; - fn mul(mut self, rhs: Amount) -> Self::Output { - self *= rhs.micro; - Self::Output::from(self) + fn div(self, rhs: u64) -> Self::Output { + Self { + raw: self.raw / Uint::from(rhs), + } } } impl AddAssign for Amount { fn add_assign(&mut self, rhs: Self) { - self.micro += rhs.micro + self.raw += rhs.raw } } @@ -229,14 +568,14 @@ impl Sub for Amount { type Output = Amount; fn sub(mut self, rhs: Self) -> Self::Output { - self.micro -= rhs.micro; + self.raw -= rhs.raw; self } } impl SubAssign for Amount { fn sub_assign(&mut self, rhs: Self) { - self.micro -= rhs.micro + self.raw -= rhs.raw } } @@ -251,69 +590,130 @@ impl KeySeg for Amount { where Self: Sized, { - let micro = u64::parse(string)?; - Ok(Self { micro }) + let bytes = BASE32HEX_NOPAD.decode(string.as_ref()).map_err(|err| { + storage::Error::ParseKeySeg(format!( + "Failed parsing {} with {}", + string, err + )) + })?; + Ok(Amount { + raw: Uint::from_big_endian(&bytes), + }) } fn raw(&self) -> String { - self.micro.raw() + let mut buf = [0u8; 32]; + self.raw.to_big_endian(&mut buf); + BASE32HEX_NOPAD.encode(&buf) } fn to_db_key(&self) -> DbKeySeg { - self.micro.to_db_key() + DbKeySeg::StringSeg(self.raw()) } } #[allow(missing_docs)] #[derive(Error, Debug)] pub enum AmountParseError { - #[error("Error decoding token amount: {0}")] - InvalidDecimal(rust_decimal::Error), #[error( "Error decoding token amount, too many decimal places: {0}. Maximum \ - {MAX_DECIMAL_PLACES}" + {1}" + )] + ScaleTooLarge(u32, u8), + #[error( + "Error decoding token amount, the value is not within invalid range." )] - ScaleTooLarge(u32), - #[error("Error decoding token amount, the value is within invalid range.")] InvalidRange, + #[error("Error converting amount to decimal, number too large.")] + ConvertToDecimal, + #[error( + "Could not convert from string, expected an unsigned 256-bit integer." + )] + FromString, + #[error("Could not parse string as a correctly formatted number.")] + NotNumeric, + #[error("This amount cannot handle the requested precision in 256 bits.")] + PrecisionOverflow, + #[error("More precision given in the amount than requested.")] + PrecisionDecrease, } -impl FromStr for Amount { - type Err = AmountParseError; - - fn from_str(s: &str) -> Result { - match rust_decimal::Decimal::from_str(s) { - Ok(decimal) => { - let scale = decimal.scale(); - if scale > MAX_DECIMAL_PLACES { - return Err(AmountParseError::ScaleTooLarge(scale)); - } - let whole = - decimal * rust_decimal::Decimal::new(SCALE as i64, 0); - let micro: u64 = - rust_decimal::prelude::ToPrimitive::to_u64(&whole) - .ok_or(AmountParseError::InvalidRange)?; - Ok(Self { micro }) - } - Err(err) => Err(AmountParseError::InvalidDecimal(err)), - } +impl From for Change { + fn from(amount: Amount) -> Self { + amount.raw.try_into().unwrap() } } -impl Display for Amount { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let decimal = rust_decimal::Decimal::from_i128_with_scale( - self.micro as i128, - MAX_DECIMAL_PLACES, - ) - .normalize(); - write!(f, "{}", decimal) +impl From for Amount { + fn from(change: Change) -> Self { + Amount { raw: change.abs() } } } -impl From for Change { +impl From for Uint { fn from(amount: Amount) -> Self { - amount.micro as i128 + amount.raw + } +} + +/// The four possible u64 words in a [`Uint`]. +/// Used for converting to MASP amounts. +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +#[repr(u8)] +#[allow(missing_docs)] +pub enum MaspDenom { + Zero = 0, + One, + Two, + Three, +} + +impl From for MaspDenom { + fn from(denom: u8) -> Self { + match denom { + 0 => Self::Zero, + 1 => Self::One, + 2 => Self::Two, + 3 => Self::Three, + _ => panic!("Possible MASP denominations must be between 0 and 3"), + } + } +} + +impl MaspDenom { + /// Iterator over the possible denominations + pub fn iter() -> impl Iterator { + // 0, 1, 2, 3 + (0u8..4).map(Self::from) + } + + /// Get the corresponding u64 word from the input uint256. + pub fn denominate<'a>(&self, amount: impl Into<&'a Amount>) -> u64 { + let amount = amount.into(); + amount.raw.0[*self as usize] + } + + /// Get the corresponding u64 word from the input uint256. + pub fn denominate_i128(&self, amount: &Change) -> i128 { + let val = amount.abs().0[*self as usize] as i128; + if Change::is_negative(amount) { + -val + } else { + val + } } } @@ -322,16 +722,20 @@ impl TryFrom for Amount { fn try_from(amount: IbcAmount) -> Result { // TODO: https://github.com/anoma/namada/issues/1089 - if amount > u64::MAX.into() { - return Err(AmountParseError::InvalidRange); - } - Self::from_str(&amount.to_string()) + // TODO: OVERFLOW CHECK PLEASE (PATCH IBC TO ALLOW GETTING + // IBCAMOUNT::MAX OR SIMILAR) if amount > u64::MAX.into() { + // return Err(AmountParseError::InvalidRange); + //} + DenominatedAmount::from_str(&amount.to_string()) + .map(|a| a.amount * NATIVE_SCALE) } } /// Key segment for a balance key pub const BALANCE_STORAGE_KEY: &str = "balance"; -/// Key segment for head shielded transaction pointer key +/// Key segment for a denomination key +pub const DENOM_STORAGE_KEY: &str = "denomination"; +/// Key segment for head shielded transaction pointer keys pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key pub const TX_KEY_PREFIX: &str = "tx-"; @@ -341,6 +745,54 @@ pub const CONVERSION_KEY_PREFIX: &str = "conv"; pub const PIN_KEY_PREFIX: &str = "pin-"; const TOTAL_SUPPLY_STORAGE_KEY: &str = "total_supply"; +/// A fully qualified (multi-) token address. +#[derive( + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Hash, + BorshSerialize, + BorshDeserialize, +)] +pub struct TokenAddress { + /// The address of the (multi-) token + pub address: Address, + /// If it is a mutli-token, this indicates the sub-token. + pub sub_prefix: Option, +} + +impl TokenAddress { + /// A function for displaying a [`TokenAddress`]. Takes a + /// human readable name of the token as input. + pub fn format_with_alias(&self, alias: &str) -> String { + format!( + "{}{}", + alias, + self.sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default() + ) + } +} + +impl Display for TokenAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let formatted = format!( + "{}{}", + self.address, + self.sub_prefix + .as_ref() + .map(|k| format!("/{}", k)) + .unwrap_or_default() + ); + f.write_str(&formatted) + } +} + /// Obtain a storage key for user's balance. pub fn balance_key(token_addr: &Address, owner: &Address) -> Key { Key::from(token_addr.to_db_key()) @@ -391,18 +843,41 @@ pub fn is_balance_key<'a>( } /// Check if the given storage key is balance key for unspecified token. If it -/// is, returns the owner. -pub fn is_any_token_balance_key(key: &Key) -> Option<&Address> { +/// is, returns the token and owner address. +pub fn is_any_token_balance_key(key: &Key) -> Option<[&Address; 2]> { match &key.segments[..] { [ - DbKeySeg::AddressSeg(_), + DbKeySeg::AddressSeg(token), DbKeySeg::StringSeg(key), DbKeySeg::AddressSeg(owner), - ] if key == BALANCE_STORAGE_KEY => Some(owner), + ] if key == BALANCE_STORAGE_KEY => Some([token, owner]), _ => None, } } +/// Obtain a storage key denomination of a token. +pub fn denom_key(token_addr: &Address, sub_prefix: Option<&Key>) -> Key { + match sub_prefix { + Some(sub) => Key::from(token_addr.to_db_key()) + .join(sub) + .push(&DENOM_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key"), + None => Key::from(token_addr.to_db_key()) + .push(&DENOM_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key"), + } +} + +/// Check if the given storage key is a denomination key for the given token. +pub fn is_denom_key(token_addr: &Address, key: &Key) -> bool { + matches!(&key.segments[..], + [ + DbKeySeg::AddressSeg(addr), + .., + DbKeySeg::StringSeg(key), + ] if key == DENOM_STORAGE_KEY && addr == token_addr) +} + /// Check if the given storage key is a masp key pub fn is_masp_key(key: &Key) -> bool { matches!(&key.segments[..], @@ -440,14 +915,27 @@ pub fn is_multitoken_balance_key<'a>( } /// Check if the given storage key is multitoken balance key for unspecified -/// token. If it is, returns the sub prefix and the owner. -pub fn is_any_multitoken_balance_key(key: &Key) -> Option<(Key, &Address)> { +/// token. If it is, returns the sub prefix and the token and owner addresses. +pub fn is_any_multitoken_balance_key( + key: &Key, +) -> Option<(Key, [&Address; 2])> { match key.segments.first() { - Some(DbKeySeg::AddressSeg(_)) => multitoken_balance_owner(key), + Some(DbKeySeg::AddressSeg(token)) => multitoken_balance_owner(key) + .map(|(sub, owner)| (sub, [token, owner])), _ => None, } } +/// Check if the given storage key is token or multitoken balance key for +/// unspecified token. If it is, returns the token and owner addresses. +pub fn is_any_token_or_multitoken_balance_key( + key: &Key, +) -> Option<[&Address; 2]> { + is_any_multitoken_balance_key(key) + .map(|a| a.1) + .or_else(|| is_any_token_balance_key(key)) +} + fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { let len = key.segments.len(); if len < 4 { @@ -494,7 +982,7 @@ pub struct Transfer { /// Source token's sub prefix pub sub_prefix: Option, /// The amount of tokens - pub amount: Amount, + pub amount: DenominatedAmount, /// The unused storage location at which to place TxId pub key: Option, /// Shielded transaction part @@ -514,43 +1002,65 @@ pub enum TransferError { #[cfg(test)] mod tests { - use proptest::prelude::*; - use super::*; - proptest! { - /// The upper limit is set to `2^51`, because then the float is - /// starting to lose precision. - #[test] - fn test_token_amount_decimal_conversion(raw_amount in 0..2_u64.pow(51)) { - let amount = Amount::from(raw_amount); - // A round-trip conversion to and from Decimal should be an identity - let decimal = Decimal::from(amount); - let identity = Amount::from(decimal); - assert_eq!(amount, identity); - } - } - #[test] fn test_token_display() { - let max = Amount::from(u64::MAX); + let max = Amount::from_uint(u64::MAX, 0).expect("Test failed"); + assert_eq!("18446744073709.551615", max.to_string_native()); + let max = DenominatedAmount { + amount: max, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; assert_eq!("18446744073709.551615", max.to_string()); - let whole = Amount::from(u64::MAX / SCALE * SCALE); + let whole = + Amount::from_uint(u64::MAX / NATIVE_SCALE * NATIVE_SCALE, 0) + .expect("Test failed"); + assert_eq!("18446744073709.000000", whole.to_string_native()); + let whole = DenominatedAmount { + amount: whole, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; assert_eq!("18446744073709", whole.to_string()); - let trailing_zeroes = Amount::from(123000); + let trailing_zeroes = + Amount::from_uint(123000, 0).expect("Test failed"); + assert_eq!("0.123000", trailing_zeroes.to_string_native()); + let trailing_zeroes = DenominatedAmount { + amount: trailing_zeroes, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; assert_eq!("0.123", trailing_zeroes.to_string()); - let zero = Amount::from(0); + let zero = Amount::default(); + assert_eq!("0.000000", zero.to_string_native()); + let zero = DenominatedAmount { + amount: zero, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; assert_eq!("0", zero.to_string()); + + let amount = DenominatedAmount { + amount: Amount::from_uint(1120, 0).expect("Test failed"), + denom: 3u8.into(), + }; + assert_eq!("1.12", amount.to_string()); + assert_eq!("1.120", amount.to_string_precise()); + + let amount = DenominatedAmount { + amount: Amount::from_uint(1120, 0).expect("Test failed"), + denom: 5u8.into(), + }; + assert_eq!("0.0112", amount.to_string()); + assert_eq!("0.01120", amount.to_string_precise()); } #[test] fn test_amount_checked_sub() { - let max = Amount::from(u64::MAX); - let one = Amount::from(1); - let zero = Amount::from(0); + let max = Amount::native_whole(u64::MAX); + let one = Amount::native_whole(1); + let zero = Amount::native_whole(0); assert_eq!(zero.checked_sub(zero), Some(zero)); assert_eq!(zero.checked_sub(one), None); @@ -561,28 +1071,102 @@ mod tests { assert_eq!(max.checked_sub(max), Some(zero)); } + #[test] + fn test_serialization_round_trip() { + let amount: Amount = serde_json::from_str(r#""1000000000""#).unwrap(); + assert_eq!( + amount, + Amount { + raw: Uint::from(1000000000) + } + ); + let serialized = serde_json::to_string(&amount).unwrap(); + assert_eq!(serialized, r#""1000000000""#); + } + #[test] fn test_amount_checked_add() { - let max = Amount::from(u64::MAX); - let one = Amount::from(1); - let zero = Amount::from(0); + let max = Amount::max(); + let max_signed = Amount::max_signed(); + let one = Amount::native_whole(1); + let zero = Amount::native_whole(0); assert_eq!(zero.checked_add(zero), Some(zero)); + assert_eq!(zero.checked_signed_add(zero), Some(zero)); assert_eq!(zero.checked_add(one), Some(one)); assert_eq!(zero.checked_add(max - one), Some(max - one)); + assert_eq!( + zero.checked_signed_add(max_signed - one), + Some(max_signed - one) + ); assert_eq!(zero.checked_add(max), Some(max)); + assert_eq!(zero.checked_signed_add(max_signed), Some(max_signed)); assert_eq!(max.checked_add(zero), Some(max)); + assert_eq!(max.checked_signed_add(zero), None); assert_eq!(max.checked_add(one), None); assert_eq!(max.checked_add(max), None); + + assert_eq!(max_signed.checked_add(zero), Some(max_signed)); + assert_eq!(max_signed.checked_add(one), Some(max_signed + one)); + assert_eq!(max_signed.checked_signed_add(max_signed), None); + } + + #[test] + fn test_amount_from_string() { + assert!(Amount::from_str("1.12", 1).is_err()); + assert!(Amount::from_str("0.0", 0).is_err()); + assert!(Amount::from_str("1.12", 80).is_err()); + assert!(Amount::from_str("1.12.1", 3).is_err()); + assert!(Amount::from_str("1.1a", 3).is_err()); + assert_eq!( + Amount::zero(), + Amount::from_str("0.0", 1).expect("Test failed") + ); + assert_eq!( + Amount::zero(), + Amount::from_str(".0", 1).expect("Test failed") + ); + + let amount = Amount::from_str("1.12", 3).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(1120, 0).expect("Test failed")); + let amount = Amount::from_str(".34", 3).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(340, 0).expect("Test failed")); + let amount = Amount::from_str("0.34", 3).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(340, 0).expect("Test failed")); + let amount = Amount::from_str("34", 1).expect("Test failed"); + assert_eq!(amount, Amount::from_uint(340, 0).expect("Test failed")); + } + + #[test] + fn test_from_masp_denominated() { + let uint = Uint([15u64, 16, 17, 18]); + let original = Amount::from_uint(uint, 0).expect("Test failed"); + for denom in MaspDenom::iter() { + let word = denom.denominate(&original); + assert_eq!(word, denom as u64 + 15u64); + let amount = Amount::from_masp_denominated(word, denom); + let raw = Uint::from(amount).0; + let mut expected = [0u64; 4]; + expected[denom as usize] = word; + assert_eq!(raw, expected); + } + } + + #[test] + fn test_key_seg() { + let original = Amount::from_uint(1234560000, 0).expect("Test failed"); + let key = original.raw(); + let amount = Amount::parse(key).expect("Test failed"); + assert_eq!(amount, original); } #[test] fn test_amount_is_zero() { - let zero = Amount::from(0); + let zero = Amount::zero(); assert!(zero.is_zero()); - let non_zero = Amount::from(1); + let non_zero = Amount::from_uint(1, 0).expect("Test failed"); assert!(!non_zero.is_zero()); } } @@ -596,12 +1180,12 @@ pub mod testing { /// Generate an arbitrary token amount pub fn arb_amount() -> impl Strategy { - any::().prop_map(Amount::from) + any::().prop_map(|val| Amount::from_uint(val, 0).unwrap()) } /// Generate an arbitrary token amount up to and including given `max` value pub fn arb_amount_ceiled(max: u64) -> impl Strategy { - (0..=max).prop_map(Amount::from) + (0..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) } /// Generate an arbitrary non-zero token amount up to and including given @@ -609,6 +1193,6 @@ pub mod testing { pub fn arb_amount_non_zero_ceiled( max: u64, ) -> impl Strategy { - (1..=max).prop_map(Amount::from) + (1..=max).prop_map(|val| Amount::from_uint(val, 0).unwrap()) } } diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index 3b1f183eaa..bfc17eeaef 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -7,6 +7,7 @@ use crate::types::address::Address; use crate::types::governance::{ self, Proposal, ProposalError, ProposalVote, VoteType, }; +use crate::types::hash::Hash; use crate::types::storage::Epoch; /// The type of a Proposal @@ -21,7 +22,7 @@ use crate::types::storage::Epoch; )] pub enum ProposalType { /// Default governance proposal with the optional wasm code - Default(Option>), + Default(Option), /// PGF council proposal PGFCouncil, /// ETH proposal @@ -54,7 +55,7 @@ impl PartialEq for ProposalType { } } -impl TryFrom for ProposalType { +impl TryFrom for (ProposalType, Option>) { type Error = ProposalError; fn try_from(value: governance::ProposalType) -> Result { @@ -62,15 +63,22 @@ impl TryFrom for ProposalType { governance::ProposalType::Default(path) => { if let Some(p) = path { match std::fs::read(p) { - Ok(code) => Ok(Self::Default(Some(code))), + Ok(code) => Ok(( + ProposalType::Default(Some(Hash::default())), + Some(code), + )), Err(_) => Err(Self::Error::InvalidProposalData), } } else { - Ok(Self::Default(None)) + Ok((ProposalType::Default(None), None)) } } - governance::ProposalType::PGFCouncil => Ok(Self::PGFCouncil), - governance::ProposalType::ETHBridge => Ok(Self::ETHBridge), + governance::ProposalType::PGFCouncil => { + Ok((ProposalType::PGFCouncil, None)) + } + governance::ProposalType::ETHBridge => { + Ok((ProposalType::ETHBridge, None)) + } } } } @@ -89,7 +97,7 @@ pub struct InitProposalData { /// The proposal id pub id: Option, /// The proposal content - pub content: Vec, + pub content: Hash, /// The proposal author address pub author: Address, /// The proposal type @@ -123,18 +131,23 @@ pub struct VoteProposalData { pub delegations: Vec
, } -impl TryFrom for InitProposalData { +impl TryFrom for (InitProposalData, Vec, Option>) { type Error = ProposalError; fn try_from(proposal: Proposal) -> Result { - Ok(InitProposalData { - id: proposal.id, - content: proposal.content.try_to_vec().unwrap(), - author: proposal.author, - r#type: proposal.r#type.try_into()?, - voting_start_epoch: proposal.voting_start_epoch, - voting_end_epoch: proposal.voting_end_epoch, - grace_epoch: proposal.grace_epoch, - }) + let (r#type, code) = proposal.r#type.try_into()?; + Ok(( + InitProposalData { + id: proposal.id, + content: Hash::default(), + author: proposal.author, + r#type, + voting_start_epoch: proposal.voting_start_epoch, + voting_end_epoch: proposal.voting_end_epoch, + grace_epoch: proposal.grace_epoch, + }, + proposal.content.try_to_vec().unwrap(), + code, + )) } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 5b9069b731..099c3a2018 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -21,13 +21,13 @@ pub use decrypted::*; #[cfg(feature = "ferveo-tpke")] pub use encrypted::EncryptionKey; pub use protocol::UpdateDkgSessionKey; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; pub use wrapper::*; use crate::ledger::gas::VpsGas; use crate::types::address::Address; +use crate::types::dec::Dec; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::key::*; @@ -192,10 +192,10 @@ pub struct InitValidator { /// Serialization of the public session key used in the DKG pub dkg_key: crate::types::key::dkg_session_keys::DkgPublicKey, /// The initial commission rate charged for delegation rewards - pub commission_rate: Decimal, + pub commission_rate: Dec, /// The maximum change allowed per epoch to the commission rate. This is /// immutable once set here. - pub max_commission_rate_change: Decimal, + pub max_commission_rate_change: Dec, /// The VP code for validator account pub validator_vp_code_hash: Hash, } @@ -254,12 +254,11 @@ mod test_process_tx { let code_sec = outer_tx .set_code(Code::new("wasm code".as_bytes().to_owned())) .clone(); - outer_tx.validate_header().expect("Test failed"); + outer_tx.validate_tx().expect("Test failed"); match outer_tx.header().tx_type { - TxType::Raw => assert_eq!( - Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), - outer_tx.header.code_hash, - ), + TxType::Raw => { + assert_eq!(code_sec.get_hash(), outer_tx.header.code_hash,) + } _ => panic!("Test failed: Expected Raw Tx"), } } @@ -277,27 +276,11 @@ mod test_process_tx { .set_data(Data::new("transaction data".as_bytes().to_owned())) .clone(); - tx.validate_header().expect("Test failed"); + tx.validate_tx().expect("Test failed"); match tx.header().tx_type { TxType::Raw => { - assert_eq!( - Hash( - code_sec - .hash(&mut Sha256::new()) - .finalize_reset() - .into() - ), - tx.header().code_hash, - ); - assert_eq!( - Hash( - data_sec - .hash(&mut Sha256::new()) - .finalize_reset() - .into() - ), - tx.header().data_hash, - ); + assert_eq!(code_sec.get_hash(), tx.header().code_hash,); + assert_eq!(data_sec.get_hash(), tx.header().data_hash,); } _ => panic!("Test failed: Expected Raw Tx"), } @@ -315,35 +298,15 @@ mod test_process_tx { .set_data(Data::new("transaction data".as_bytes().to_owned())) .clone(); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &gen_keypair(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &gen_keypair(), ))); - tx.validate_header().expect("Test failed"); + tx.validate_tx().expect("Test failed"); match tx.header().tx_type { TxType::Raw => { - assert_eq!( - Hash( - code_sec - .hash(&mut Sha256::new()) - .finalize_reset() - .into() - ), - tx.header().code_hash, - ); - assert_eq!( - Hash( - data_sec - .hash(&mut Sha256::new()) - .finalize_reset() - .into() - ), - tx.header().data_hash, - ); + assert_eq!(code_sec.get_hash(), tx.header().code_hash,); + assert_eq!(data_sec.get_hash(), tx.header().data_hash,); } _ => panic!("Test failed: Expected Raw Tx"), } @@ -362,19 +325,19 @@ mod test_process_tx { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); tx.set_code(Code::new("wasm code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.encrypt(&Default::default()); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + vec![tx.header_hash(), tx.sections[0].get_hash()], &keypair, ))); - tx.encrypt(&Default::default()); - tx.validate_header().expect("Test failed"); + tx.validate_tx().expect("Test failed"); match tx.header().tx_type { TxType::Wrapper(_) => { tx.decrypt(::G2Affine::prime_subgroup_generator()) @@ -398,14 +361,14 @@ mod test_process_tx { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); tx.set_code(Code::new("wasm code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); tx.encrypt(&Default::default()); - let result = tx.validate_header().expect_err("Test failed"); + let result = tx.validate_tx().expect_err("Test failed"); assert_matches!(result, TxError::SigError(_)); } } @@ -425,20 +388,14 @@ fn test_process_tx_decrypted_unsigned() { let data_sec = tx .set_data(Data::new("transaction data".as_bytes().to_owned())) .clone(); - tx.validate_header().expect("Test failed"); + tx.validate_tx().expect("Test failed"); match tx.header().tx_type { TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: _, }) => { - assert_eq!( - tx.header().code_hash, - Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), - ); - assert_eq!( - tx.header().data_hash, - Hash(data_sec.hash(&mut Sha256::new()).finalize_reset().into()), - ); + assert_eq!(tx.header().code_hash, code_sec.get_hash(),); + assert_eq!(tx.header().data_hash, data_sec.get_hash(),); } _ => panic!("Test failed"), } @@ -466,8 +423,9 @@ fn test_process_tx_decrypted_signed() { // Invalid signed data let ed_sig = ed25519::Signature::try_from_slice([0u8; 64].as_ref()).unwrap(); - let mut sig_sec = Signature::new(&decrypted.header_hash(), &gen_keypair()); - sig_sec.signature = common::Signature::try_from_sig(&ed_sig).unwrap(); + let mut sig_sec = + Signature::new(vec![decrypted.header_hash()], &gen_keypair()); + sig_sec.signature = Some(common::Signature::try_from_sig(&ed_sig).unwrap()); decrypted.add_section(Section::Signature(sig_sec)); // create the tx with signed decrypted data let code_sec = decrypted @@ -476,20 +434,14 @@ fn test_process_tx_decrypted_signed() { let data_sec = decrypted .set_data(Data::new("transaction data".as_bytes().to_owned())) .clone(); - decrypted.validate_header().expect("Test failed"); + decrypted.validate_tx().expect("Test failed"); match decrypted.header().tx_type { TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: _, }) => { - assert_eq!( - decrypted.header.code_hash, - Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), - ); - assert_eq!( - decrypted.header.data_hash, - Hash(data_sec.hash(&mut Sha256::new()).finalize_reset().into()), - ); + assert_eq!(decrypted.header.code_hash, code_sec.get_hash()); + assert_eq!(decrypted.header.data_hash, data_sec.get_hash()); } _ => panic!("Test failed"), } diff --git a/core/src/types/transaction/pos.rs b/core/src/types/transaction/pos.rs index 8119eb2310..d334047ffe 100644 --- a/core/src/types/transaction/pos.rs +++ b/core/src/types/transaction/pos.rs @@ -1,10 +1,10 @@ //! Types used for PoS system transactions use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use crate::types::address::Address; +use crate::types::dec::Dec; use crate::types::token; /// A bond is a validator's self-bond or a delegation from non-validator to a @@ -72,5 +72,5 @@ pub struct CommissionChange { /// Validator address pub validator: Address, /// The new commission rate - pub new_rate: Decimal, + pub new_rate: Dec, } diff --git a/core/src/types/transaction/protocol.rs b/core/src/types/transaction/protocol.rs index 910f401a66..e1edcf2417 100644 --- a/core/src/types/transaction/protocol.rs +++ b/core/src/types/transaction/protocol.rs @@ -136,7 +136,11 @@ mod protocol_txs { .expect("Serializing request should not fail"), )); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + vec![ + outer_tx.header_hash(), + *outer_tx.code_sechash(), + *outer_tx.data_sechash(), + ], signing_key, ))); outer_tx diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index a92d5720ee..d1427a0c76 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -2,11 +2,14 @@ /// to enable encrypted txs inside of normal txs. /// *Not wasm compatible* pub mod wrapper_tx { + use std::fmt::Formatter; + pub use ark_bls12_381::Bls12_381 as EllipticCurve; #[cfg(feature = "ferveo-tpke")] pub use ark_ec::{AffineCurve, PairingEngine}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; - use serde::{Deserialize, Serialize}; + use serde::de::Error; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -14,6 +17,7 @@ pub mod wrapper_tx { use crate::types::key::*; use crate::types::storage::Epoch; use crate::types::token::Amount; + use crate::types::uint::Uint; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -56,6 +60,7 @@ pub mod wrapper_tx { /// amount of the fee pub amount: Amount, /// address of the token + /// TODO: This should support multi-tokens pub token: Address, } @@ -67,52 +72,95 @@ pub mod wrapper_tx { /// This struct only stores the multiple of GAS_LIMIT_RESOLUTION, /// not the raw amount #[derive( + Default, Debug, Clone, PartialEq, - Serialize, - Deserialize, BorshSerialize, BorshDeserialize, BorshSchema, Eq, )] - #[serde(from = "u64")] - #[serde(into = "u64")] pub struct GasLimit { - multiplier: u64, + multiplier: Uint, + } + + impl Serialize for GasLimit { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let limit = Uint::from(self).to_string(); + Serialize::serialize(&limit, serializer) + } + } + + impl<'de> Deserialize<'de> for GasLimit { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct GasLimitVisitor; + + impl<'a> serde::de::Visitor<'a> for GasLimitVisitor { + type Value = GasLimit; + + fn expecting( + &self, + formatter: &mut Formatter, + ) -> std::fmt::Result { + formatter.write_str( + "A string representing 256-bit unsigned integer", + ) + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + let uint = Uint::from_dec_str(v) + .map_err(|e| E::custom(e.to_string()))?; + Ok(GasLimit::from(uint)) + } + } + deserializer.deserialize_any(GasLimitVisitor) + } } impl GasLimit { /// We refund unused gas up to GAS_LIMIT_RESOLUTION - pub fn refund_amount(&self, used_gas: u64) -> Amount { - if used_gas < (u64::from(self) - GAS_LIMIT_RESOLUTION) { - // we refund only up to GAS_LIMIT_RESOLUTION - GAS_LIMIT_RESOLUTION - } else if used_gas >= u64::from(self) { - // Gas limit was under estimated, no refund - 0 - } else { - // compute refund - u64::from(self) - used_gas - } - .into() + pub fn refund_amount(&self, used_gas: Uint) -> Amount { + Amount::from_uint( + if used_gas + < (Uint::from(self) - Uint::from(GAS_LIMIT_RESOLUTION)) + { + // we refund only up to GAS_LIMIT_RESOLUTION + Uint::from(GAS_LIMIT_RESOLUTION) + } else if used_gas >= Uint::from(self) { + // Gas limit was under estimated, no refund + Uint::from(0) + } else { + // compute refund + Uint::from(self) - used_gas + }, + 0, + ) + .unwrap() } } /// Round the input number up to the next highest multiple /// of GAS_LIMIT_RESOLUTION - impl From for GasLimit { - fn from(amount: u64) -> GasLimit { - // we could use the ceiling function but this way avoids casts to - // floats - if GAS_LIMIT_RESOLUTION * (amount / GAS_LIMIT_RESOLUTION) < amount { + impl From for GasLimit { + fn from(amount: Uint) -> GasLimit { + let gas_limit_resolution = Uint::from(GAS_LIMIT_RESOLUTION); + if gas_limit_resolution * (amount / gas_limit_resolution) < amount { GasLimit { - multiplier: (amount / GAS_LIMIT_RESOLUTION) + 1, + multiplier: (amount / gas_limit_resolution) + 1, } } else { GasLimit { - multiplier: (amount / GAS_LIMIT_RESOLUTION), + multiplier: (amount / gas_limit_resolution), } } } @@ -122,20 +170,20 @@ pub mod wrapper_tx { /// of GAS_LIMIT_RESOLUTION impl From for GasLimit { fn from(amount: Amount) -> GasLimit { - GasLimit::from(u64::from(amount)) + GasLimit::from(Uint::from(amount)) } } /// Get back the gas limit as a raw number - impl From<&GasLimit> for u64 { - fn from(limit: &GasLimit) -> u64 { + impl From<&GasLimit> for Uint { + fn from(limit: &GasLimit) -> Uint { limit.multiplier * GAS_LIMIT_RESOLUTION } } /// Get back the gas limit as a raw number - impl From for u64 { - fn from(limit: GasLimit) -> u64 { + impl From for Uint { + fn from(limit: GasLimit) -> Uint { limit.multiplier * GAS_LIMIT_RESOLUTION } } @@ -143,7 +191,8 @@ pub mod wrapper_tx { /// Get back the gas limit as a raw number, viewed as an Amount impl From for Amount { fn from(limit: GasLimit) -> Amount { - Amount::from(limit.multiplier * GAS_LIMIT_RESOLUTION) + Amount::from_uint(limit.multiplier * GAS_LIMIT_RESOLUTION, 0) + .unwrap() } } @@ -221,10 +270,12 @@ pub mod wrapper_tx { /// Test that serializing converts GasLimit to u64 correctly #[test] fn test_gas_limit_roundtrip() { - let limit = GasLimit { multiplier: 1 }; + let limit = GasLimit { + multiplier: 1.into(), + }; // Test serde roundtrip let js = serde_json::to_string(&limit).expect("Test failed"); - assert_eq!(js, format!("{}", GAS_LIMIT_RESOLUTION)); + assert_eq!(js, format!(r#""{}""#, GAS_LIMIT_RESOLUTION)); let new_limit: GasLimit = serde_json::from_str(&js).expect("Test failed"); assert_eq!(new_limit, limit); @@ -243,35 +294,52 @@ pub mod wrapper_tx { /// multiple #[test] fn test_deserialize_not_multiple_of_resolution() { - let js = serde_json::to_string(&(GAS_LIMIT_RESOLUTION + 1)) - .expect("Test failed"); + let js = format!(r#""{}""#, &(GAS_LIMIT_RESOLUTION + 1)); let limit: GasLimit = serde_json::from_str(&js).expect("Test failed"); - assert_eq!(limit, GasLimit { multiplier: 2 }); + assert_eq!( + limit, + GasLimit { + multiplier: 2.into() + } + ); } /// Test that refund is calculated correctly #[test] fn test_gas_limit_refund() { - let limit = GasLimit { multiplier: 1 }; - let refund = limit.refund_amount(GAS_LIMIT_RESOLUTION - 1); - assert_eq!(refund, Amount::from(1u64)); + let limit = GasLimit { + multiplier: 1.into(), + }; + let refund = + limit.refund_amount(Uint::from(GAS_LIMIT_RESOLUTION - 1)); + assert_eq!(refund, Amount::from_uint(1, 0).expect("Test failed")); } /// Test that we don't refund more than GAS_LIMIT_RESOLUTION #[test] fn test_gas_limit_too_high_no_refund() { - let limit = GasLimit { multiplier: 2 }; - let refund = limit.refund_amount(GAS_LIMIT_RESOLUTION - 1); - assert_eq!(refund, Amount::from(GAS_LIMIT_RESOLUTION)); + let limit = GasLimit { + multiplier: 2.into(), + }; + let refund = + limit.refund_amount(Uint::from(GAS_LIMIT_RESOLUTION - 1)); + assert_eq!( + refund, + Amount::from_uint(GAS_LIMIT_RESOLUTION, 0) + .expect("Test failed") + ); } /// Test that if gas usage was underestimated, we issue no refund #[test] fn test_gas_limit_too_low_no_refund() { - let limit = GasLimit { multiplier: 1 }; - let refund = limit.refund_amount(GAS_LIMIT_RESOLUTION + 1); - assert_eq!(refund, Amount::from(0u64)); + let limit = GasLimit { + multiplier: 1.into(), + }; + let refund = + limit.refund_amount(Uint::from(GAS_LIMIT_RESOLUTION + 1)); + assert_eq!(refund, Amount::default()); } } @@ -303,19 +371,19 @@ pub mod wrapper_tx { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); wrapper.set_code(Code::new("wasm code".as_bytes().to_owned())); wrapper .set_data(Data::new("transaction data".as_bytes().to_owned())); + let mut encrypted_tx = wrapper.clone(); + encrypted_tx.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - let mut encrypted_tx = wrapper.clone(); - encrypted_tx.encrypt(&Default::default()); assert!(encrypted_tx.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); encrypted_tx.decrypt(privkey).expect("Test failed"); @@ -336,7 +404,7 @@ pub mod wrapper_tx { }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); @@ -346,11 +414,11 @@ pub mod wrapper_tx { // give a incorrect commitment to the decrypted contents of the tx wrapper.set_code_sechash(Hash([0u8; 32])); wrapper.set_data_sechash(Hash([0u8; 32])); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); assert!(wrapper.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); let err = wrapper.decrypt(privkey).expect_err("Test failed"); @@ -366,22 +434,18 @@ pub mod wrapper_tx { // the signed tx let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { - amount: 10.into(), + amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, &keypair, Epoch(0), - 0.into(), + Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); tx.set_code(Code::new("wasm code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); - tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), - &keypair, - ))); // we now try to alter the inner tx maliciously // malicious transaction @@ -391,6 +455,11 @@ pub mod wrapper_tx { tx.set_data(Data::new(malicious.clone())); tx.encrypt(&Default::default()); + tx.add_section(Section::Signature(Signature::new( + vec![tx.header_hash(), tx.sections[0].get_hash()], + &keypair, + ))); + // we check ciphertext validity still passes assert!(tx.validate_ciphertext()); // we check that decryption still succeeds @@ -401,10 +470,10 @@ pub mod wrapper_tx { assert_eq!(tx.data(), Some(malicious)); // check that the signature is not valid - tx.verify_signature(&keypair.ref_to(), &tx.header_hash()) + tx.verify_signature(&keypair.ref_to(), &tx.sechashes()) .expect_err("Test failed"); // check that the try from method also fails - let err = tx.validate_header().expect_err("Test failed"); + let err = tx.validate_tx().expect_err("Test failed"); assert_matches!(err, TxError::SigError(_)); } } diff --git a/core/src/types/uint.rs b/core/src/types/uint.rs new file mode 100644 index 0000000000..a3e147ca7c --- /dev/null +++ b/core/src/types/uint.rs @@ -0,0 +1,547 @@ +#![allow(clippy::assign_op_pattern)] +//! An unsigned 256 integer type. Used for, among other things, +//! the backing type of token amounts. +use std::cmp::Ordering; +use std::fmt::Debug; +use std::ops::{Add, AddAssign, BitXor, Div, Mul, Neg, Rem, Sub, SubAssign}; + +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use impl_num_traits::impl_uint_num_traits; +use serde::{Deserialize, Serialize}; +use uint::construct_uint; + +use crate::types::token; +use crate::types::token::{Amount, AmountParseError, MaspDenom}; + +construct_uint! { + /// Namada native type to replace for unsigned 256 bit + /// integers. + #[derive( + Serialize, + Deserialize, + BorshSerialize, + BorshDeserialize, + BorshSchema, + )] + + pub struct Uint(4); +} + +impl_uint_num_traits!(Uint, 4); + +/// The maximum 256 bit integer +pub const MAX_VALUE: Uint = Uint([u64::MAX; 4]); + +impl Uint { + /// Divide two [`Uint`]s with scaled to allow the `denom` number + /// of decimal places. + /// + /// This method is checked and will return `None` if + /// * `self` * 10^(`denom`) overflows 256 bits + /// * `other` is zero (`checked_div` will return `None`). + pub fn fixed_precision_div(&self, rhs: &Self, denom: u8) -> Option { + let lhs = Uint::from(10) + .checked_pow(Uint::from(denom)) + .and_then(|res| res.checked_mul(*self))?; + lhs.checked_div(*rhs) + } + + /// Compute the two's complement of a number. + fn negate(&self) -> Self { + Self( + self.0 + .into_iter() + .map(|byte| byte.bitxor(u64::MAX)) + .collect::>() + .try_into() + .expect("This cannot fail"), + ) + .overflowing_add(Uint::from(1u64)) + .0 + .canonical() + } + + /// There are two valid representations of zero: plus and + /// minus. We only allow the positive representation. + fn canonical(self) -> Self { + if self == MINUS_ZERO { + Self::zero() + } else { + self + } + } +} + +/// The maximum absolute value a [`I256`] may have. +/// Note the the last digit is 2^63 - 1. We add this cap so +/// we can use two's complement. +pub const MAX_SIGNED_VALUE: Uint = + Uint([u64::MAX, u64::MAX, u64::MAX, 9223372036854775807]); + +const MINUS_ZERO: Uint = Uint([0u64, 0u64, 0u64, 9223372036854775808]); + +/// A signed 256 big integer. +#[derive( + Copy, + Clone, + Debug, + Default, + PartialEq, + Eq, + Hash, + BorshSerialize, + BorshDeserialize, + BorshSchema, +)] +pub struct I256(pub Uint); + +impl I256 { + /// Check if the amount is not negative (greater + /// than or equal to zero) + pub fn non_negative(&self) -> bool { + self.0.0[3].leading_zeros() > 0 + } + + /// Check if the amount is negative (less than zero) + pub fn is_negative(&self) -> bool { + !self.non_negative() + } + + /// Check if the amount is positive (greater than zero) + pub fn is_positive(&self) -> bool { + self.non_negative() && !self.is_zero() + } + + /// Get the absolute value + pub fn abs(&self) -> Uint { + if self.non_negative() { + self.0 + } else { + self.0.negate() + } + } + + /// Check if this value is zero + pub fn is_zero(&self) -> bool { + self.0 == Uint::zero() + } + + /// Gives the zero value of an I256 + pub fn zero() -> I256 { + Self(Uint::zero()) + } + + /// Get a string representation of `self` as a + /// native token amount. + pub fn to_string_native(&self) -> String { + let mut sign = if !self.non_negative() { + String::from("-") + } else { + String::new() + }; + sign.push_str(&token::Amount::from(*self).to_string_native()); + sign + } + + /// Adds two [`I256`]'s if the absolute value does + /// not exceed [`MAX_SIGNED_VALUE`], else returns `None`. + pub fn checked_add(&self, other: &Self) -> Option { + if self.non_negative() == other.non_negative() { + self.abs().checked_add(other.abs()).and_then(|val| { + Self::try_from(val) + .ok() + .map(|val| if !self.non_negative() { -val } else { val }) + }) + } else { + Some(*self + *other) + } + } + + /// Subtracts two [`I256`]'s if the absolute value does + /// not exceed [`MAX_SIGNED_VALUE`], else returns `None`. + pub fn checked_sub(&self, other: &Self) -> Option { + self.checked_add(&other.neg()) + } + + /// Changed the inner Uint into a canonical representation. + fn canonical(self) -> Self { + Self(self.0.canonical()) + } + + /// the maximum I256 value + pub fn maximum() -> Self { + Self(MAX_SIGNED_VALUE) + } + + /// Attempt to convert a MASP-denominated integer to an I256 + /// using the given denomination. + pub fn from_masp_denominated( + value: impl Into, + denom: MaspDenom, + ) -> Result { + let value = value.into(); + let is_negative = value < 0; + let value = value.unsigned_abs(); + let mut result = [0u64; 4]; + result[denom as usize] = value as u64; + let result = Uint(result); + if result <= MAX_SIGNED_VALUE { + if is_negative { + Ok(Self(result.negate()).canonical()) + } else { + Ok(Self(result).canonical()) + } + } else { + Err(AmountParseError::InvalidRange) + } + } +} + +impl From for I256 { + fn from(val: u64) -> Self { + I256::try_from(Uint::from(val)) + .expect("A u64 will always fit in this type") + } +} + +impl TryFrom for I256 { + type Error = Box; + + fn try_from(value: Uint) -> Result { + if value <= MAX_SIGNED_VALUE { + Ok(Self(value)) + } else { + Err("The given integer is too large to be represented asa \ + SignedUint" + .into()) + } + } +} + +impl Neg for I256 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(self.0.negate()) + } +} + +impl PartialOrd for I256 { + fn partial_cmp(&self, other: &Self) -> Option { + match (self.non_negative(), other.non_negative()) { + (true, false) => Some(Ordering::Greater), + (false, true) => Some(Ordering::Less), + (true, true) => { + let this = self.abs(); + let that = other.abs(); + this.0.partial_cmp(&that.0) + } + (false, false) => { + let this = self.abs(); + let that = other.abs(); + that.0.partial_cmp(&this.0) + } + } + } +} + +impl Ord for I256 { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl Add for I256 { + type Output = Self; + + fn add(self, rhs: I256) -> Self::Output { + match (self.non_negative(), rhs.non_negative()) { + (true, true) => Self(self.0 + rhs.0), + (false, false) => -Self(self.abs() + rhs.abs()), + (true, false) => { + if self.0 >= rhs.abs() { + Self(self.0 - rhs.abs()) + } else { + -Self(rhs.abs() - self.0) + } + } + (false, true) => { + if rhs.0 >= self.abs() { + Self(rhs.0 - self.abs()) + } else { + -Self(self.abs() - rhs.0) + } + } + } + .canonical() + } +} + +impl AddAssign for I256 { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl Sub for I256 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + self + (-rhs) + } +} + +impl SubAssign for I256 { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +// NOTE: watch the overflow +impl Mul for I256 { + type Output = Self; + + fn mul(self, rhs: Uint) -> Self::Output { + let is_neg = self.is_negative(); + let prod = self.abs() * rhs; + if is_neg { -Self(prod) } else { Self(prod) } + } +} + +impl Mul for I256 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + if rhs.is_negative() { + -self * rhs.abs() + } else { + self * rhs.abs() + } + } +} + +impl Div for I256 { + type Output = Self; + + fn div(self, rhs: Uint) -> Self::Output { + let is_neg = self.is_negative(); + let quot = self + .abs() + .fixed_precision_div(&rhs, 0u8) + .unwrap_or_default(); + if is_neg { -Self(quot) } else { Self(quot) } + } +} + +impl Div for I256 { + type Output = Self; + + fn div(self, rhs: I256) -> Self::Output { + if rhs.is_negative() { + -(self / rhs.abs()) + } else { + self / rhs.abs() + } + } +} + +impl Rem for I256 { + type Output = Self; + + fn rem(self, rhs: Self) -> Self::Output { + if self.is_negative() { + -(Self(self.abs() % rhs.abs())) + } else { + Self(self.abs() % rhs.abs()) + } + } +} +impl From for I256 { + fn from(val: i128) -> Self { + if val < 0 { + let abs = Self((-val).into()); + -abs + } else { + Self(val.into()) + } + } +} + +impl From for I256 { + fn from(val: i64) -> Self { + Self::from(val as i128) + } +} + +impl From for I256 { + fn from(val: i32) -> Self { + Self::from(val as i128) + } +} + +impl std::iter::Sum for I256 { + fn sum>(iter: I) -> Self { + iter.fold(I256::zero(), |acc, amt| acc + amt) + } +} + +impl TryFrom for i128 { + type Error = std::io::Error; + + fn try_from(value: I256) -> Result { + if !value.non_negative() { + Ok(-(u128::try_from(Amount::from_change(value))? as i128)) + } else { + Ok(u128::try_from(Amount::from_change(value))? as i128) + } + } +} + +#[cfg(test)] +mod test_uint { + use super::*; + + /// Test that dividing two [`Uint`]s with the specified precision + /// works correctly and performs correct checks. + #[test] + fn test_fixed_precision_div() { + let zero = Uint::zero(); + let two = Uint::from(2); + let three = Uint::from(3); + + assert_eq!( + zero.fixed_precision_div(&two, 10).expect("Test failed"), + zero + ); + assert!(two.fixed_precision_div(&zero, 3).is_none()); + assert_eq!( + three.fixed_precision_div(&two, 1).expect("Test failed"), + Uint::from(15) + ); + assert_eq!( + two.fixed_precision_div(&three, 2).expect("Test failed"), + Uint::from(66) + ); + assert_eq!( + two.fixed_precision_div(&three, 3).expect("Satan lives"), + Uint::from(666) + ); + assert!(two.fixed_precision_div(&three, 77).is_none()); + assert!(Uint::from(20).fixed_precision_div(&three, 76).is_none()); + } + + /// Test that adding one to the max signed + /// value gives zero. + #[test] + fn test_max_signed_value() { + let signed = I256::try_from(MAX_SIGNED_VALUE).expect("Test failed"); + let one = I256::try_from(Uint::from(1u64)).expect("Test failed"); + let overflow = signed + one; + assert_eq!( + overflow, + I256::try_from(Uint::zero()).expect("Test failed") + ); + assert!(signed.checked_add(&one).is_none()); + assert!((-signed).checked_sub(&one).is_none()); + } + + /// Sanity on our constants and that the minus zero representation + /// is not allowed. + #[test] + fn test_minus_zero_not_allowed() { + let larger = Uint([0, 0, 0, 2u64.pow(63)]); + let smaller = Uint([u64::MAX, u64::MAX, u64::MAX, 2u64.pow(63) - 1]); + assert!(larger > smaller); + assert_eq!(smaller, MAX_SIGNED_VALUE); + assert_eq!(larger, MINUS_ZERO); + assert!(I256::try_from(MINUS_ZERO).is_err()); + let zero = Uint::zero(); + assert_eq!(zero, zero.negate()); + } + + /// Test that we correctly reserve the right bit for indicating the + /// sign. + #[test] + fn test_non_negative() { + let zero = I256::try_from(Uint::zero()).expect("Test failed"); + assert!(zero.non_negative()); + assert!((-zero).non_negative()); + let negative = I256(Uint([1u64, 0, 0, 2u64.pow(63)])); + assert!(!negative.non_negative()); + assert!((-negative).non_negative()); + let positive = I256(MAX_SIGNED_VALUE); + assert!(positive.non_negative()); + assert!(!(-positive).non_negative()); + } + + /// Test that the absolute value is computed correctly. + #[test] + fn test_abs() { + let zero = I256::try_from(Uint::zero()).expect("Test failed"); + let neg_one = I256(Uint::max_value()); + let neg_eight = I256(Uint::max_value() - Uint::from(7)); + let two = I256(Uint::from(2)); + let ten = I256(Uint::from(10)); + + assert_eq!(zero.abs(), Uint::zero()); + assert_eq!(neg_one.abs(), Uint::from(1)); + assert_eq!(neg_eight.abs(), Uint::from(8)); + assert_eq!(two.abs(), Uint::from(2)); + assert_eq!(ten.abs(), Uint::from(10)); + } + + /// Test that the string representation is created correctly. + #[test] + fn test_to_string_native() { + let native_scaling = Uint::exp10(6); + let zero = I256::try_from(Uint::zero()).expect("Test failed"); + let neg_one = -I256(native_scaling); + let neg_eight = -I256(Uint::from(8) * native_scaling); + let two = I256(Uint::from(2) * native_scaling); + let ten = I256(Uint::from(10) * native_scaling); + + assert_eq!(zero.to_string_native(), "0.000000"); + assert_eq!(neg_one.to_string_native(), "-1.000000"); + assert_eq!(neg_eight.to_string_native(), "-8.000000"); + assert_eq!(two.to_string_native(), "2.000000"); + assert_eq!(ten.to_string_native(), "10.000000"); + } + + /// Test that we correctly handle arithmetic with two's complement + #[test] + fn test_arithmetic() { + let zero = I256::try_from(Uint::zero()).expect("Test failed"); + let neg_one = I256(Uint::max_value()); + let neg_eight = I256(Uint::max_value() - Uint::from(7)); + let two = I256(Uint::from(2)); + let ten = I256(Uint::from(10)); + + assert_eq!(zero + neg_one, neg_one); + assert_eq!(neg_one - zero, neg_one); + assert_eq!(zero - neg_one, I256(Uint::one())); + assert_eq!(two - neg_eight, ten); + assert_eq!(two + ten, I256(Uint::from(12))); + assert_eq!(ten - two, -neg_eight); + assert_eq!(two - ten, neg_eight); + assert_eq!(neg_eight + neg_one, -I256(Uint::from(9))); + assert_eq!(neg_one - neg_eight, I256(Uint::from(7))); + assert_eq!(neg_eight - neg_one, -I256(Uint::from(7))); + assert_eq!(neg_eight - two, -ten); + assert!((two - two).is_zero()); + } + + /// Test that ordering is correctly implemented + #[test] + fn test_ord() { + let this = Amount::from_uint(1, 0).unwrap().change(); + let that = Amount::native_whole(1000).change(); + assert!(this <= that); + assert!(-this <= that); + assert!(-this >= -that); + assert!(this >= -that); + assert!(that >= this); + assert!(that >= -this); + assert!(-that <= -this); + assert!(-that <= this); + } +} diff --git a/genesis/dev.toml b/genesis/dev.toml index 80748ec403..b6eb070b42 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -17,9 +17,9 @@ non_staked_balance = 100000 # VP for the validator account validator_vp = "vp_validator" # Commission rate for rewards -commission_rate = 0.05 +commission_rate = "0.05" # Maximum change per epoch in the commission rate -max_commission_rate_change = 0.01 +max_commission_rate_change = "0.01" # Public IP:port address net_address = "127.0.0.1:26656" @@ -27,6 +27,7 @@ net_address = "127.0.0.1:26656" [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" +denom = 8 vp = "vp_token" [token.NAM.balances] # In token balances, we can use: @@ -35,7 +36,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 # 2. An alias of any account -bertha = 1000000 +Bertha = "1000000" # 3. A public key of a validator or an established account from which the # address of the implicit account is derived) "bertha.public_key" = 100 @@ -43,6 +44,7 @@ bertha = 1000000 [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" +denom = 8 vp = "vp_token" [token.BTC.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -52,6 +54,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" +denom = 18 vp = "vp_token" [token.ETH.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -61,6 +64,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" +denom = 10 vp = "vp_token" [token.DOT.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -70,6 +74,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" +denom = 6 vp = "vp_token" [token.schnitzel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -79,6 +84,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" +denom = 6 vp = "vp_token" [token.apfel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 @@ -88,6 +94,7 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.kartoffel] address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" +denom = 6 public_key = "" vp = "vp_token" [token.kartoffel.balances] @@ -160,21 +167,21 @@ pipeline_len = 2 # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 21 # Votes per fundamental staking token (namnam) -tm_votes_per_token = 1 +tm_votes_per_token = "1" # Reward for proposing a block. -block_proposer_reward = 0.125 +block_proposer_reward = "0.125" # Reward for voting on a block. -block_vote_reward = 0.1 +block_vote_reward = "0.1" # Maximum inflation rate per annum (10%) -max_inflation_rate = 0.1 +max_inflation_rate = "0.1" # Targeted ratio of staked tokens to total tokens in the supply -target_staked_ratio = 0.6667 +target_staked_ratio = "0.6667" # Portion of a validator's stake that should be slashed on a duplicate # vote. -duplicate_vote_min_slash_rate = 0.001 +duplicate_vote_min_slash_rate = "0.001" # Portion of a validator's stake that should be slashed on a light # client attack. -light_client_attack_min_slash_rate = 0.001 +light_client_attack_min_slash_rate = "0.001" # Number of epochs above and below (separately) the current epoch to # consider when doing cubic slashing cubic_slashing_window_length = 1 diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index f9860e6529..7def249d15 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -5,7 +5,7 @@ genesis_time = "2021-09-30T10:00:00Z" native_token = "NAM" faucet_pow_difficulty = 1 -faucet_withdrawal_limit = "1_000" +faucet_withdrawal_limit = "1000000000" [validator.validator-0] # Validator's staked NAM at genesis. @@ -15,11 +15,11 @@ non_staked_balance = 1000000000000 # VP for the validator account validator_vp = "vp_validator" # Commission rate for rewards -commission_rate = 0.05 +commission_rate = "0.05" # Maximum change per epoch in the commission rate -max_commission_rate_change = 0.01 +max_commission_rate_change = "0.01" # Public IP:port address. -# We set the port to be the default+1000, so that if a local node was running at +# We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. net_address = "127.0.0.1:27656" @@ -27,79 +27,93 @@ net_address = "127.0.0.1:27656" [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" +denom = 6 vp = "vp_token" [token.NAM.balances] -Albert = 1000000 -"Albert.public_key" = 100 -Bertha = 1000000 -"Bertha.public_key" = 2000 -Christel = 1000000 -"Christel.public_key" = 100 -Daewon = 1000000 -faucet = 9223372036 -"faucet.public_key" = 100 -"validator-0.public_key" = 100 +Albert = "1000000" +"Albert.public_key" = "100" +Bertha = "1000000" +"Bertha.public_key" = "2000" +Christel = "1000000" +"Christel.public_key" = "100" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" +"faucet.public_key" = "100" +"validator-0.public_key" = "100" [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" +denom = 8 vp = "vp_token" [token.BTC.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" +denom = 18 vp = "vp_token" [token.ETH.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" +denom = 10 vp = "vp_token" -[token.Dot.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +[token.DOT.balances] +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" [token.Schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" +denom = 6 vp = "vp_token" [token.Schnitzel.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" [token.Apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" +denom = 6 vp = "vp_token" [token.Apfel.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" [token.Kartoffel] address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" public_key = "" +denom = 6 vp = "vp_token" [token.Kartoffel.balances] -Albert = 1000000 -Bertha = 1000000 -Christel = 1000000 -Daewon = 1000000 -faucet = 9223372036854 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" # Some established accounts present at genesis. [established.faucet] @@ -120,6 +134,8 @@ vp = "vp_masp" [implicit.Daewon] +[implicit.Ester] + # Wasm VP definitions # Implicit VP @@ -164,9 +180,9 @@ implicit_vp = "vp_implicit" # Expected number of epochs per year (also sets the min duration of an epoch in seconds) epochs_per_year = 31_536_000 # The P gain factor in the Proof of Stake rewards controller -pos_gain_p = 0.1 +pos_gain_p = "0.1" # The D gain factor in the Proof of Stake rewards controller -pos_gain_d = 0.1 +pos_gain_d = "0.1" # Proof of stake parameters. [pos_params] @@ -179,24 +195,27 @@ pipeline_len = 2 # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 3 # Votes per fundamental staking token (namnam) -tm_votes_per_token = 0.1 +tm_votes_per_token = "0.1" # Reward for proposing a block. -block_proposer_reward = 0.125 +block_proposer_reward = "0.125" # Reward for voting on a block. -block_vote_reward = 0.1 +block_vote_reward = "0.1" # Maximum inflation rate per annum (10%) -max_inflation_rate = 0.1 +max_inflation_rate = "0.1" # Targeted ratio of staked tokens to total tokens in the supply -target_staked_ratio = 0.6667 +target_staked_ratio = "0.6667" # Portion of a validator's stake that should be slashed on a duplicate # vote. -duplicate_vote_min_slash_rate = 0.001 +duplicate_vote_min_slash_rate = "0.001" # Portion of a validator's stake that should be slashed on a light # client attack. -light_client_attack_min_slash_rate = 0.001 +light_client_attack_min_slash_rate = "0.001" # Number of epochs above and below (separately) the current epoch to # consider when doing cubic slashing cubic_slashing_window_length = 1 +# The minimum amount of bonded tokens that a validator needs to be in +# either the `consensus` or `below_capacity` validator sets +validator_stake_threshold = "1" # Governance parameters. [gov_params] diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 2e9bd22757..4394425f17 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -27,8 +27,7 @@ data-encoding.workspace = true derivative.workspace = true once_cell.workspace = true proptest = {version = "1.2.0", optional = true} -rust_decimal_macros.workspace = true -rust_decimal.workspace = true +rand = "0.8.5" thiserror.workspace = true tracing.workspace = true diff --git a/proof_of_stake/proptest-regressions/tests/state_machine.txt b/proof_of_stake/proptest-regressions/tests/state_machine.txt index fd37a6f64a..4c02bc0ede 100644 --- a/proof_of_stake/proptest-regressions/tests/state_machine.txt +++ b/proof_of_stake/proptest-regressions/tests/state_machine.txt @@ -5,3 +5,4 @@ # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc 3076c8509d56c546d5915febcf429f218ab79a7bac34c75c288f531b88110bc3 # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 4, pipeline_len: 2, unbonding_len: 4, tm_votes_per_token: 0.0614, block_proposer_reward: 0.125, block_vote_reward: 0.1, max_inflation_rate: 0.1, target_staked_ratio: 0.6667, duplicate_vote_min_slash_rate: 0.001, light_client_attack_min_slash_rate: 0.001, cubic_slashing_window_length: 1 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { micro: 9185807 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: 0.05, max_commission_rate_change: 0.01 }, GenesisValidator { address: Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6, tokens: Amount { micro: 5025206 }, consensus_key: Ed25519(PublicKey(VerificationKey("17888c2ca502371245e5e35d5bcf35246c3bc36878e859938c9ead3c54db174f"))), commission_rate: 0.05, max_commission_rate_change: 0.01 }, GenesisValidator { address: Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc, tokens: Amount { micro: 4424807 }, consensus_key: Ed25519(PublicKey(VerificationKey("478243aed376da313d7cf3a60637c264cb36acc936efb341ff8d3d712092d244"))), commission_rate: 0.05, max_commission_rate_change: 0.01 }, GenesisValidator { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, tokens: Amount { micro: 4119410 }, consensus_key: Ed25519(PublicKey(VerificationKey("c5bbbb60e412879bbec7bb769804fa8e36e68af10d5477280b63deeaca931bed"))), commission_rate: 0.05, max_commission_rate_change: 0.01 }, GenesisValidator { address: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, tokens: Amount { micro: 3619078 }, consensus_key: Ed25519(PublicKey(VerificationKey("4f44e6c7bdfed3d9f48d86149ee3d29382cae8c83ca253e06a70be54a301828b"))), commission_rate: 0.05, max_commission_rate_change: 0.01 }, GenesisValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, tokens: Amount { micro: 2691447 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: 0.05, max_commission_rate_change: 0.01 }, GenesisValidator { address: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, tokens: Amount { micro: 224944 }, consensus_key: Ed25519(PublicKey(VerificationKey("191fc38f134aaf1b7fdb1f86330b9d03e94bd4ba884f490389de964448e89b3f"))), commission_rate: 0.05, max_commission_rate_change: 0.01 }, GenesisValidator { address: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, tokens: Amount { micro: 142614 }, consensus_key: Ed25519(PublicKey(VerificationKey("e2e8aa145e1ec5cb01ebfaa40e10e12f0230c832fd8135470c001cb86d77de00"))), commission_rate: 0.05, max_commission_rate_change: 0.01 }], bonds: {BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }: {Epoch(0): 142614}, BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }: {Epoch(0): 4119410}, BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: {Epoch(0): 9185807}, BondId { source: Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6, validator: Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6 }: {Epoch(0): 5025206}, BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }: {Epoch(0): 2691447}, BondId { source: Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc, validator: Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc }: {Epoch(0): 4424807}, BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }: {Epoch(0): 224944}, BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }: {Epoch(0): 3619078}}, validator_stakes: {Epoch(0): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: 142614, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 4119410, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 9185807, Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6: 5025206, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 2691447, Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc: 4424807, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 224944, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: 3619078}, Epoch(1): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: 142614, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 4119410, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 9185807, Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6: 5025206, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 2691447, Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc: 4424807, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 224944, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: 3619078}, Epoch(2): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: 142614, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 4119410, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 9185807, Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6: 5025206, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 2691447, Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc: 4424807, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 224944, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: 3619078}}, consensus_set: {Epoch(0): {Amount { micro: 4119410 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 4424807 }: [Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc], Amount { micro: 5025206 }: [Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6], Amount { micro: 9185807 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(1): {Amount { micro: 4119410 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 4424807 }: [Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc], Amount { micro: 5025206 }: [Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6], Amount { micro: 9185807 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(2): {Amount { micro: 4119410 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { micro: 4424807 }: [Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc], Amount { micro: 5025206 }: [Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6], Amount { micro: 9185807 }: [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}}, below_capacity_set: {Epoch(0): {ReverseOrdTokenAmount(Amount { micro: 142614 }): [Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6], ReverseOrdTokenAmount(Amount { micro: 224944 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { micro: 2691447 }): [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], ReverseOrdTokenAmount(Amount { micro: 3619078 }): [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd]}, Epoch(1): {ReverseOrdTokenAmount(Amount { micro: 142614 }): [Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6], ReverseOrdTokenAmount(Amount { micro: 224944 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { micro: 2691447 }): [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], ReverseOrdTokenAmount(Amount { micro: 3619078 }): [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd]}, Epoch(2): {ReverseOrdTokenAmount(Amount { micro: 142614 }): [Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6], ReverseOrdTokenAmount(Amount { micro: 224944 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { micro: 2691447 }): [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], ReverseOrdTokenAmount(Amount { micro: 3619078 }): [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd]}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: BelowCapacity, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: BelowCapacity, Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc: Consensus, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: BelowCapacity}, Epoch(1): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: BelowCapacity, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: BelowCapacity, Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc: Consensus, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: BelowCapacity}, Epoch(2): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: BelowCapacity, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: Consensus, Established: atest1v4ehgw36gfzrydfsx9zryv6pxcmng32xg9zyvve3xveyxvf58pzyzd2p8qmr23fsggensve3v7a7y6: Consensus, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: BelowCapacity, Established: atest1v4ehgw36gvcn23zyx3zngw2pgv6nxvfjx9pyyv2p8ye5vvpjxcenvv3ng3przvpnxqur2vzpkrazgc: Consensus, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: BelowCapacity}}, unbonds: {}, validator_slashes: {}, enqueued_slashes: {}, validator_last_slash_epochs: {}, unbond_records: {} }, [InitValidator { address: Established: atest1v4ehgw36xgunxvj9xqmny3jyxycnzdzxxqeng33ngvunqsfsx5mnwdfjgvenvwfk89prwdpjd0cjrk, consensus_key: Ed25519(PublicKey(VerificationKey("bea04de1e5be8ca0ae27be8ad935df8d757e96c1e067e96aedeba0ded0df997d"))), commission_rate: 0.39428, max_commission_rate_change: 0.12485 }]) +cc c0ffe7b368967ea0c456da20046f7d8a78c232c066ea116d3a123c945b7882fb # shrinks to (initial_state, transitions) = (AbstractPosState { epoch: Epoch(0), params: PosParams { max_validator_slots: 4, pipeline_len: 2, unbonding_len: 7, tm_votes_per_token: Dec(900700.000000), block_proposer_reward: Dec(125000.000000), block_vote_reward: Dec(100000.000000), max_inflation_rate: Dec(100000.000000), target_staked_ratio: Dec(666700.000000), duplicate_vote_min_slash_rate: Dec(1000.000000), light_client_attack_min_slash_rate: Dec(1000.000000), cubic_slashing_window_length: 1 }, genesis_validators: [GenesisValidator { address: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, tokens: Amount { raw: 8937727 }, consensus_key: Ed25519(PublicKey(VerificationKey("e2e8aa145e1ec5cb01ebfaa40e10e12f0230c832fd8135470c001cb86d77de00"))), commission_rate: Dec(50000.000000), max_commission_rate_change: Dec(10000.000000) }, GenesisValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, tokens: Amount { raw: 8738693 }, consensus_key: Ed25519(PublicKey(VerificationKey("ff87a0b0a3c7c0ce827e9cada5ff79e75a44a0633bfcb5b50f99307ddb26b337"))), commission_rate: Dec(50000.000000), max_commission_rate_change: Dec(10000.000000) }, GenesisValidator { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, tokens: Amount { raw: 8373784 }, consensus_key: Ed25519(PublicKey(VerificationKey("c5bbbb60e412879bbec7bb769804fa8e36e68af10d5477280b63deeaca931bed"))), commission_rate: Dec(50000.000000), max_commission_rate_change: Dec(10000.000000) }, GenesisValidator { address: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, tokens: Amount { raw: 3584214 }, consensus_key: Ed25519(PublicKey(VerificationKey("4f44e6c7bdfed3d9f48d86149ee3d29382cae8c83ca253e06a70be54a301828b"))), commission_rate: Dec(50000.000000), max_commission_rate_change: Dec(10000.000000) }, GenesisValidator { address: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, tokens: Amount { raw: 553863 }, consensus_key: Ed25519(PublicKey(VerificationKey("ee1aa49a4459dfe813a3cf6eb882041230c7b2558469de81f87c9bf23bf10a03"))), commission_rate: Dec(50000.000000), max_commission_rate_change: Dec(10000.000000) }, GenesisValidator { address: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, tokens: Amount { raw: 218044 }, consensus_key: Ed25519(PublicKey(VerificationKey("191fc38f134aaf1b7fdb1f86330b9d03e94bd4ba884f490389de964448e89b3f"))), commission_rate: Dec(50000.000000), max_commission_rate_change: Dec(10000.000000) }], bonds: {BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }: {Epoch(0): 8.937727}, BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }: {Epoch(0): 8.373784}, BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }: {Epoch(0): 0.553863}, BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }: {Epoch(0): 8.738693}, BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }: {Epoch(0): 0.218044}, BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }: {Epoch(0): 3.584214}}, validator_stakes: {Epoch(0): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: 8.937727, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 8.373784, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 0.553863, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 8.738693, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 0.218044, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: 3.584214}, Epoch(1): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: 8.937727, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 8.373784, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 0.553863, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 8.738693, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 0.218044, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: 3.584214}, Epoch(2): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: 8.937727, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: 8.373784, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: 0.553863, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: 8.738693, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: 0.218044, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: 3.584214}}, consensus_set: {Epoch(0): {Amount { raw: 3584214 }: [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd], Amount { raw: 8373784 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { raw: 8738693 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { raw: 8937727 }: [Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6]}, Epoch(1): {Amount { raw: 3584214 }: [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd], Amount { raw: 8373784 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { raw: 8738693 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { raw: 8937727 }: [Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6]}, Epoch(2): {Amount { raw: 3584214 }: [Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd], Amount { raw: 8373784 }: [Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3], Amount { raw: 8738693 }: [Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk], Amount { raw: 8937727 }: [Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6]}}, below_capacity_set: {Epoch(0): {ReverseOrdTokenAmount(Amount { raw: 218044 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { raw: 553863 }): [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(1): {ReverseOrdTokenAmount(Amount { raw: 218044 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { raw: 553863 }): [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}, Epoch(2): {ReverseOrdTokenAmount(Amount { raw: 218044 }): [Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv], ReverseOrdTokenAmount(Amount { raw: 553863 }): [Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6]}}, validator_states: {Epoch(0): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: BelowCapacity, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: Consensus}, Epoch(1): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: BelowCapacity, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: Consensus}, Epoch(2): {Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6: Consensus, Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3: Consensus, Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6: BelowCapacity, Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk: Consensus, Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv: BelowCapacity, Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd: Consensus}}, unbonds: {}, validator_slashes: {}, enqueued_slashes: {}, validator_last_slash_epochs: {}, unbond_records: {} }, [Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 267 } }, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 7610143 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 9863718 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 7102818 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 63132 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 9663084 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 2694963 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 7453740 } }, NextEpoch, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 14974324 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 2628172 } }, NextEpoch, NextEpoch, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 282055 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 11228090 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 2027105 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 2034080 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { raw: 3329590 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 854661 } }, Misbehavior { address: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, slash_type: DuplicateVote, infraction_epoch: Epoch(1), height: 0 }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 227931 } }, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 2701887 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 1776100 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { raw: 3717491 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { raw: 5281559 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { raw: 2426117 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 2005749 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 7883312 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 7300122 } }, Bond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 3388459 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { raw: 195542 } }, NextEpoch, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 2251455 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 1237777 } }, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 691613 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 1244599 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 2645543 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 8384136 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 590662 } }, NextEpoch, InitValidator { address: Established: atest1v4ehgw368qcrqd2ygvmyyvf4g9qnvv3kxucrwv3hxg6ryve4x56r233cxucnysjrxsmygdj9yer4pz, consensus_key: Ed25519(PublicKey(VerificationKey("afa2335747c0249f66eca84e88fba1a0e3ccec6a8f6f97f3177a42ffbb216492"))), commission_rate: Dec(195450.000000), max_commission_rate_change: Dec(954460.000000) }, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 1687952 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 12754717 } }, Misbehavior { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, slash_type: LightClientAttack, infraction_epoch: Epoch(4), height: 0 }, Bond { id: BondId { source: Implicit: atest1d9khqw36xqunjdeegge5xdpcg5mnqwzp8yerzde58pq5g3pcxu6yvvphg3zr23z9gg6yvs3cmzdz9u, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 8952712 } }, NextEpoch, Withdraw { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv } }, Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 519835 } }, UnjailValidator { address: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, Unbond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 2207493 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 236124 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 71122 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw36xqunjdeegge5xdpcg5mnqwzp8yerzde58pq5g3pcxu6yvvphg3zr23z9gg6yvs3cmzdz9u, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 1158688 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 267618 } }, InitValidator { address: Established: atest1v4ehgw36xucy2dfcxdzrxvpjx5uygwzrxpzrjs3jx4p5vvjrxdq5yvpjx5e5zs3jxdqng3pcplv2ch, consensus_key: Ed25519(PublicKey(VerificationKey("822cfec1ec829a50306424ac3d11115e880b952f5f54ac9a624277898991ee70"))), commission_rate: Dec(614520.000000), max_commission_rate_change: Dec(369920.000000) }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 8634884 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 8660668 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g9rryv3sx5c5v33sgsmrsd3egerrgdenx3zy2sfex4prvsehxcurydjx8qu5zdz9f2npes, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 8436873 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 515615 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 46481 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 4153966 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 2272563 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g5eyzwf3xqc5gwzxg3pnq3jpgsenxwp3x56rjvz9x5crwsf3gerrgwphxqen2sjz4hscvd, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 7491749 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 1921487 } }, Bond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 8316111 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 11873152 } }, NextEpoch, Withdraw { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv } }, Withdraw { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk } }, Withdraw { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 } }, Bond { id: BondId { source: Implicit: atest1d9khqw368yenjvpjxcu5vv33x3zrqw2zgg6nsvzrx9prxd2pgsmyxwfjxgunvs3exerrydp3csdkvr, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 4728535 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g5eyzwf3xqc5gwzxg3pnq3jpgsenxwp3x56rjvz9x5crwsf3gerrgwphxqen2sjz4hscvd, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 2828807 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g5eyzwf3xqc5gwzxg3pnq3jpgsenxwp3x56rjvz9x5crwsf3gerrgwphxqen2sjz4hscvd, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 655500 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 234416 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 330322 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 222600 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw36xqunjdeegge5xdpcg5mnqwzp8yerzde58pq5g3pcxu6yvvphg3zr23z9gg6yvs3cmzdz9u, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 2538059 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 168498 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw368yenjvpjxcu5vv33x3zrqw2zgg6nsvzrx9prxd2pgsmyxwfjxgunvs3exerrydp3csdkvr, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 510701 } }, Misbehavior { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk, slash_type: DuplicateVote, infraction_epoch: Epoch(8), height: 0 }, InitValidator { address: Established: atest1v4ehgw36ggcrz3zygyunqsfjggmnq33h8ycnsdphxepnsve4gerrss2pgfp5z3psgccrj33klenl5r, consensus_key: Ed25519(PublicKey(VerificationKey("afc853489cf37abedeb6a97d036f3dc60934194af7169a2cc15fb3f85e4e287c"))), commission_rate: Dec(52690.000000), max_commission_rate_change: Dec(56470.000000) }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 7098849 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw36xqunjdeegge5xdpcg5mnqwzp8yerzde58pq5g3pcxu6yvvphg3zr23z9gg6yvs3cmzdz9u, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 2180088 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 243441 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw36xqunjdeegge5xdpcg5mnqwzp8yerzde58pq5g3pcxu6yvvphg3zr23z9gg6yvs3cmzdz9u, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 1621261 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 7650954 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 1201023 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36xqunjdeegge5xdpcg5mnqwzp8yerzde58pq5g3pcxu6yvvphg3zr23z9gg6yvs3cmzdz9u, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 9702706 } }, InitValidator { address: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu, consensus_key: Ed25519(PublicKey(VerificationKey("f8506f129faaf3bac1397ad0ab3bfa6d1a00d5c1064c4fafe740f2844be8fb04"))), commission_rate: Dec(575190.000000), max_commission_rate_change: Dec(602710.000000) }, Unbond { id: BondId { source: Implicit: atest1d9khqw368yenjvpjxcu5vv33x3zrqw2zgg6nsvzrx9prxd2pgsmyxwfjxgunvs3exerrydp3csdkvr, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 347187 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 5536481 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36xc6nvvf4g9znxvf3xdrrgvfexuen2dek8qmnqse58q6ygdpkxeznz3j9xyeyydfht747xe, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 1859243 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 1907757 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw368yenjvpjxcu5vv33x3zrqw2zgg6nsvzrx9prxd2pgsmyxwfjxgunvs3exerrydp3csdkvr, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 3007741 } }, Misbehavior { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, slash_type: DuplicateVote, infraction_epoch: Epoch(9), height: 0 }, Bond { id: BondId { source: Established: atest1v4ehgw36g9rryv3sx5c5v33sgsmrsd3egerrgdenx3zy2sfex4prvsehxcurydjx8qu5zdz9f2npes, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 8226972 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw368yenjvpjxcu5vv33x3zrqw2zgg6nsvzrx9prxd2pgsmyxwfjxgunvs3exerrydp3csdkvr, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 602759 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw36xqunjdeegge5xdpcg5mnqwzp8yerzde58pq5g3pcxu6yvvphg3zr23z9gg6yvs3cmzdz9u, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 8350223 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 3787232 } }, InitValidator { address: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595, consensus_key: Ed25519(PublicKey(VerificationKey("0b88c50c1b9b5b1e83c89110e388908dc3cc18ce0551494ab1c82bece24b2714"))), commission_rate: Dec(674000.000000), max_commission_rate_change: Dec(247230.000000) }, Bond { id: BondId { source: Established: atest1v4ehgw36gdp52wp4xv6yyd3nx9pnysfn89znjsen8quyvwfkgycnjs29x9ryxveh8prygsfecye5dj, validator: Established: atest1v4ehgw36ggcrz3zygyunqsfjggmnq33h8ycnsdphxepnsve4gerrss2pgfp5z3psgccrj33klenl5r }, amount: Amount { raw: 1391049 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36gve5zdf4gccygv6zxgcnxwzrgv65x32rg4zrxv34g9prvs2pxqmnzve5xvuns33czq9awp, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 4008194 } }, Bond { id: BondId { source: Implicit: atest1d9khqw368pq5g3f3gceygvpjxuenyveexary2wzx8ycnw3zpg9zrvvp4xger2dzyxuunwvjz4n93ww, validator: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595 }, amount: Amount { raw: 9368360 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { raw: 9140634 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 600383 } }, Misbehavior { address: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, slash_type: DuplicateVote, infraction_epoch: Epoch(7), height: 0 }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 8599835 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 345454 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g9rryv3sx5c5v33sgsmrsd3egerrgdenx3zy2sfex4prvsehxcurydjx8qu5zdz9f2npes, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 12448069 } }, NextEpoch, Withdraw { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 5151682 } }, Bond { id: BondId { source: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 1862578 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 10904134 } }, Bond { id: BondId { source: Implicit: atest1d9khqw368pq5g3f3gceygvpjxuenyveexary2wzx8ycnw3zpg9zrvvp4xger2dzyxuunwvjz4n93ww, validator: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595 }, amount: Amount { raw: 773655 } }, Bond { id: BondId { source: Implicit: atest1d9khqw3689rrqdp58pznydecgyu5xs3cxdznvd6xxsmng32zxumrxvpj8qenydejgfzygwzxlu6r7s, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 8927299 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 1288039 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 2861830 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 445593 } }, Bond { id: BondId { source: Implicit: atest1d9khqw368pq5g3f3gceygvpjxuenyveexary2wzx8ycnw3zpg9zrvvp4xger2dzyxuunwvjz4n93ww, validator: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595 }, amount: Amount { raw: 8204875 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 602527 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 5812026 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw3689rrqdp58pznydecgyu5xs3cxdznvd6xxsmng32zxumrxvpj8qenydejgfzygwzxlu6r7s, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 211165 } }, NextEpoch, Bond { id: BondId { source: Implicit: atest1d9khqw36xsun2decx9p52v2xg5cr2vphxym5vve58yerqve5x5c5yve3gepyzs3ngycy233eufckzz, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 350302 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 4560437 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36xqunjdeegge5xdpcg5mnqwzp8yerzde58pq5g3pcxu6yvvphg3zr23z9gg6yvs3cmzdz9u, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 3515009 } }, Bond { id: BondId { source: Established: atest1v4ehgw36xucy2dfcxdzrxvpjx5uygwzrxpzrjs3jx4p5vvjrxdq5yvpjx5e5zs3jxdqng3pcplv2ch, validator: Established: atest1v4ehgw36xucy2dfcxdzrxvpjx5uygwzrxpzrjs3jx4p5vvjrxdq5yvpjx5e5zs3jxdqng3pcplv2ch }, amount: Amount { raw: 4956849 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw36xsun2decx9p52v2xg5cr2vphxym5vve58yerqve5x5c5yve3gepyzs3ngycy233eufckzz, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 290427 } }, NextEpoch, Unbond { id: BondId { source: Implicit: atest1d9khqw36gve5zdf4gccygv6zxgcnxwzrgv65x32rg4zrxv34g9prvs2pxqmnzve5xvuns33czq9awp, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 3261985 } }, Bond { id: BondId { source: Established: atest1v4ehgw36xucy2dfcxdzrxvpjx5uygwzrxpzrjs3jx4p5vvjrxdq5yvpjx5e5zs3jxdqng3pcplv2ch, validator: Established: atest1v4ehgw36xucy2dfcxdzrxvpjx5uygwzrxpzrjs3jx4p5vvjrxdq5yvpjx5e5zs3jxdqng3pcplv2ch }, amount: Amount { raw: 8946479 } }, Withdraw { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 } }, NextEpoch, InitValidator { address: Established: atest1v4ehgw36gvcrgdeex5ensvfkgccyxve3x3pnys6xxpzr2s6rxuurv3j9g4pyysjzxq6ygdzyt2wxa3, consensus_key: Ed25519(PublicKey(VerificationKey("a856fc650a2404e2d0c152d89c1c221bd9056a6103980e1d821b0cbae213ff44"))), commission_rate: Dec(324920.000000), max_commission_rate_change: Dec(512260.000000) }, Withdraw { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 82795 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 128956 } }, Bond { id: BondId { source: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 2043203 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 6764953 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g5eyzwf3xqc5gwzxg3pnq3jpgsenxwp3x56rjvz9x5crwsf3gerrgwphxqen2sjz4hscvd, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 6413168 } }, Bond { id: BondId { source: Implicit: atest1d9khqw368pq5g3f3gceygvpjxuenyveexary2wzx8ycnw3zpg9zrvvp4xger2dzyxuunwvjz4n93ww, validator: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595 }, amount: Amount { raw: 6384185 } }, Misbehavior { address: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595, slash_type: LightClientAttack, infraction_epoch: Epoch(13), height: 0 }, Bond { id: BondId { source: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595, validator: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595 }, amount: Amount { raw: 8314982 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36xscrsve3geqnwd2x8qmrzwpe89z5zsekgvenqwp5x4p5ydzp8qmrz3zpgcmnydjptyfc40, validator: Established: atest1v4ehgw36gvcrgdeex5ensvfkgccyxve3x3pnys6xxpzr2s6rxuurv3j9g4pyysjzxq6ygdzyt2wxa3 }, amount: Amount { raw: 9139532 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 34693 } }, Bond { id: BondId { source: Implicit: atest1d9khqw3689rrqdp58pznydecgyu5xs3cxdznvd6xxsmng32zxumrxvpj8qenydejgfzygwzxlu6r7s, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 9487215 } }, NextEpoch, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 799953 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw36xscrsve3geqnwd2x8qmrzwpe89z5zsekgvenqwp5x4p5ydzp8qmrz3zpgcmnydjptyfc40, validator: Established: atest1v4ehgw36gvcrgdeex5ensvfkgccyxve3x3pnys6xxpzr2s6rxuurv3j9g4pyysjzxq6ygdzyt2wxa3 }, amount: Amount { raw: 3334636 } }, NextEpoch, Withdraw { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xucy2dfcxdzrxvpjx5uygwzrxpzrjs3jx4p5vvjrxdq5yvpjx5e5zs3jxdqng3pcplv2ch, validator: Established: atest1v4ehgw36xucy2dfcxdzrxvpjx5uygwzrxpzrjs3jx4p5vvjrxdq5yvpjx5e5zs3jxdqng3pcplv2ch }, amount: Amount { raw: 7942329 } }, NextEpoch, Unbond { id: BondId { source: Established: atest1v4ehgw36gdp52wp4xv6yyd3nx9pnysfn89znjsen8quyvwfkgycnjs29x9ryxveh8prygsfecye5dj, validator: Established: atest1v4ehgw36ggcrz3zygyunqsfjggmnq33h8ycnsdphxepnsve4gerrss2pgfp5z3psgccrj33klenl5r }, amount: Amount { raw: 878389 } }, Withdraw { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 } }, UnjailValidator { address: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, UnjailValidator { address: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, Bond { id: BondId { source: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 5376602 } }, UnjailValidator { address: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, Unbond { id: BondId { source: Implicit: atest1d9khqw36xc6nvvf4g9znxvf3xdrrgvfexuen2dek8qmnqse58q6ygdpkxeznz3j9xyeyydfht747xe, validator: Established: atest1v4ehgw36gsm5xvzygg65zvjpxpprw32z89q5y334gvenzdf5x5e5zsjpgfrygwpc8qcnswf32ad0uk }, amount: Amount { raw: 1118174 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 286221 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 73579 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g9rryv3sx5c5v33sgsmrsd3egerrgdenx3zy2sfex4prvsehxcurydjx8qu5zdz9f2npes, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 2010212 } }, Bond { id: BondId { source: Implicit: atest1d9khqw3689rrqdp58pznydecgyu5xs3cxdznvd6xxsmng32zxumrxvpj8qenydejgfzygwzxlu6r7s, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 4276553 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw368yenjvpjxcu5vv33x3zrqw2zgg6nsvzrx9prxd2pgsmyxwfjxgunvs3exerrydp3csdkvr, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 54860 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gdp52wp4xv6yyd3nx9pnysfn89znjsen8quyvwfkgycnjs29x9ryxveh8prygsfecye5dj, validator: Established: atest1v4ehgw36ggcrz3zygyunqsfjggmnq33h8ycnsdphxepnsve4gerrss2pgfp5z3psgccrj33klenl5r }, amount: Amount { raw: 145154 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3, validator: Established: atest1v4ehgw36g3qnv3fnxvu5z3jpx5urjsesxs6ny3pcgs652333x3pn2wzyx4rrqwpngveny32p9qxcv3 }, amount: Amount { raw: 1941194 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 93 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw3689rrqdp58pznydecgyu5xs3cxdznvd6xxsmng32zxumrxvpj8qenydejgfzygwzxlu6r7s, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 }, amount: Amount { raw: 9992596 } }, Bond { id: BondId { source: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, validator: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv }, amount: Amount { raw: 504024 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 5640962 } }, InitValidator { address: Established: atest1v4ehgw368qmnzsfeg5urqw2p8pq5gsf4ggcnqdz9xvc5vsfjxc6nvsekgsmyv3jp8ym52wph0hm33r, consensus_key: Ed25519(PublicKey(VerificationKey("2bccbdf7490f98b2e258a399b75c74bd1b71e9f6f4cc2160edbe3186e23d30e4"))), commission_rate: Dec(427420.000000), max_commission_rate_change: Dec(574220.000000) }, Misbehavior { address: Established: atest1v4ehgw36x5unyvphgc6yx32rgvcyvd35g3p5y3zx89znzd6zxgerqsjp89qnqvzyxsenyvehtufkzv, slash_type: DuplicateVote, infraction_epoch: Epoch(12), height: 0 }, Bond { id: BondId { source: Implicit: atest1d9khqw368pq5g3f3gceygvpjxuenyveexary2wzx8ycnw3zpg9zrvvp4xger2dzyxuunwvjz4n93ww, validator: Established: atest1v4ehgw36gc6njdpcxycnwv2zx9zrsdjxg9zrqvjzxuurxve5x3rryde48pqnjsekg3przs2z8dz595 }, amount: Amount { raw: 4019468 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36xscrsve3geqnwd2x8qmrzwpe89z5zsekgvenqwp5x4p5ydzp8qmrz3zpgcmnydjptyfc40, validator: Established: atest1v4ehgw36gvcrgdeex5ensvfkgccyxve3x3pnys6xxpzr2s6rxuurv3j9g4pyysjzxq6ygdzyt2wxa3 }, amount: Amount { raw: 5683219 } }, Bond { id: BondId { source: Implicit: atest1d9khqw368pz5zd3sgeqnxve4g9ryv3zzggerqdf3xqmrywfng4zrs3pkx5enydesg5mr2v6p4v8rst, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 6886837 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g9rryv3sx5c5v33sgsmrsd3egerrgdenx3zy2sfex4prvsehxcurydjx8qu5zdz9f2npes, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 7852494 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 749047 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gdp52wp4xv6yyd3nx9pnysfn89znjsen8quyvwfkgycnjs29x9ryxveh8prygsfecye5dj, validator: Established: atest1v4ehgw36ggcrz3zygyunqsfjggmnq33h8ycnsdphxepnsve4gerrss2pgfp5z3psgccrj33klenl5r }, amount: Amount { raw: 9097957 } }, Bond { id: BondId { source: Established: atest1v4ehgw36g9rryv3sx5c5v33sgsmrsd3egerrgdenx3zy2sfex4prvsehxcurydjx8qu5zdz9f2npes, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 6781624 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw36gve5zdf4gccygv6zxgcnxwzrgv65x32rg4zrxv34g9prvs2pxqmnzve5xvuns33czq9awp, validator: Established: atest1v4ehgw36xsuy2vzx89pygd35gsurs3f3xsenz3pnxgmnws29xfrrzvp3xeq5yvjygsmnz33crlu8uu }, amount: Amount { raw: 123577 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gvmrzsf58yurxsjxgfqnqv6yg56nwv69xv6yv3zpx9znv3jpg4p5zdpnxpznzv3hq7q2az, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 1515359 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 9136180 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw368yenjvpjxcu5vv33x3zrqw2zgg6nsvzrx9prxd2pgsmyxwfjxgunvs3exerrydp3csdkvr, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 190090 } }, Unbond { id: BondId { source: Implicit: atest1d9khqw368pz5zd3sgeqnxve4g9ryv3zzggerqdf3xqmrywfng4zrs3pkx5enydesg5mr2v6p4v8rst, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 2817512 } }, NextEpoch, Bond { id: BondId { source: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6, validator: Established: atest1v4ehgw36g3pyvvekx3q52dzr8q6ngvee8pzrzv2xgscr2sfh8ymyzwfjxdzrwv3jxuur2s2ydfjhs6 }, amount: Amount { raw: 5207922 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36x5uyvv2px4pr2d3cgdpry3zzxq6nsd6yg5mnwsjzgcervdpegsunqd3kgy6ygvpjyvyhzj, validator: Established: atest1v4ehgw368qcrqd2ygvmyyvf4g9qnvv3kxucrwv3hxg6ryve4x56r233cxucnysjrxsmygdj9yer4pz }, amount: Amount { raw: 70961 } }, Bond { id: BondId { source: Established: atest1v4ehgw36gdzns33sgsmr2wz9x4rrxdenx3zyysfcxcmry32pgeznjw2zx4zrysjxgeryxsfc2etu33, validator: Established: atest1v4ehgw36ggcrz3zygyunqsfjggmnq33h8ycnsdphxepnsve4gerrss2pgfp5z3psgccrj33klenl5r }, amount: Amount { raw: 9056961 } }, Unbond { id: BondId { source: Established: atest1v4ehgw36gvmrzsf58yurxsjxgfqnqv6yg56nwv69xv6yv3zpx9znv3jpg4p5zdpnxpznzv3hq7q2az, validator: Established: atest1v4ehgw36xgm5ydpkxq6nxdzxxveyg3jygceyzwpnx4prvwpnx5ey2wpnx9zrj3phxvcnjwzpn29wcd }, amount: Amount { raw: 1451932 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36gcunwdzyxpz5xs2rxuuyxvfcgfznzd3hg9zrzdfnx5crwv69ggcnvsjpgc65gd33uuymj8, validator: Established: atest1v4ehgw36xucy2dfcxdzrxvpjx5uygwzrxpzrjs3jx4p5vvjrxdq5yvpjx5e5zs3jxdqng3pcplv2ch }, amount: Amount { raw: 1463719 } }, Withdraw { id: BondId { source: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6, validator: Established: atest1v4ehgw36gcur2v2p89z5ys6xgdqngvjxxuu52v3excm52sejx9znwdpjgfq5vv6rxgurwvzxn85ca6 } }, Bond { id: BondId { source: Implicit: atest1d9khqw36x5uyvv2px4pr2d3cgdpry3zzxq6nsd6yg5mnwsjzgcervdpegsunqd3kgy6ygvpjyvyhzj, validator: Established: atest1v4ehgw368qcrqd2ygvmyyvf4g9qnvv3kxucrwv3hxg6ryve4x56r233cxucnysjrxsmygdj9yer4pz }, amount: Amount { raw: 792907 } }, InitValidator { address: Established: atest1v4ehgw36xy65xd3cgvcyxsesgsunys3hgg6nyvekxgerz3fjxaprqvfhxser2wphg5mnjdzpf7edt5, consensus_key: Ed25519(PublicKey(VerificationKey("8f6eeade76a7ce1ccf1d3138807774696d51fcf2c8879e53aa2b082e34eec42b"))), commission_rate: Dec(592790.000000), max_commission_rate_change: Dec(854710.000000) }]) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 1308d92e11..8bde4c0ff0 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -36,9 +36,10 @@ use namada_core::ledger::storage_api::collections::lazy_map::{ use namada_core::ledger::storage_api::collections::{LazyCollection, LazySet}; use namada_core::ledger::storage_api::token::credit_tokens; use namada_core::ledger::storage_api::{ - self, OptionExt, ResultExt, StorageRead, StorageWrite, + self, ResultExt, StorageRead, StorageWrite, }; use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::dec::Dec; use namada_core::types::key::{ common, tm_consensus_key_raw_hash, PublicKeyTmRawHash, }; @@ -47,28 +48,29 @@ use namada_core::types::token; use once_cell::unsync::Lazy; use parameters::PosParams; use rewards::PosRewardsCalculator; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; use storage::{ bonds_for_source_prefix, bonds_prefix, consensus_keys_key, - decimal_mult_amount, get_validator_address_from_bond, into_tm_voting_power, - is_bond_key, is_unbond_key, is_validator_slashes_key, - last_block_proposer_key, params_key, slashes_prefix, - unbonds_for_source_prefix, unbonds_prefix, validator_address_raw_hash_key, - validator_last_slash_key, validator_max_commission_rate_change_key, + get_validator_address_from_bond, into_tm_voting_power, is_bond_key, + is_unbond_key, is_validator_slashes_key, last_block_proposer_key, + params_key, slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, + validator_address_raw_hash_key, validator_max_commission_rate_change_key, BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, - ReverseOrdTokenAmount, RewardsAccumulator, SlashedAmount, UnbondDetails, - ValidatorUnbondRecords, + ReverseOrdTokenAmount, RewardsAccumulator, UnbondDetails, }; use thiserror::Error; use types::{ - decimal_mult_i128, BelowCapacityValidatorSet, BelowCapacityValidatorSets, - BondId, Bonds, CommissionRates, ConsensusValidator, ConsensusValidatorSet, - ConsensusValidatorSets, EpochedSlashes, GenesisValidator, Position, - RewardsProducts, Slash, SlashType, Slashes, TotalDeltas, Unbonds, - ValidatorAddresses, ValidatorConsensusKeys, ValidatorDeltas, - ValidatorPositionAddresses, ValidatorSetPositions, ValidatorSetUpdate, - ValidatorState, ValidatorStates, VoteInfo, WeightedValidator, + BelowCapacityValidatorSet, BelowCapacityValidatorSets, BondId, Bonds, + CommissionRates, ConsensusValidator, ConsensusValidatorSet, + ConsensusValidatorSets, GenesisValidator, Position, RewardsProducts, Slash, + SlashType, Slashes, TotalDeltas, Unbonds, ValidatorConsensusKeys, + ValidatorDeltas, ValidatorPositionAddresses, ValidatorSetPositions, + ValidatorSetUpdate, ValidatorState, ValidatorStates, VoteInfo, + WeightedValidator, +}; + +use crate::storage::{ + validator_last_slash_key, EpochedSlashes, SlashedAmount, + ValidatorAddresses, ValidatorUnbondRecords, }; /// Address of the PoS account implemented as a native VP @@ -130,7 +132,7 @@ pub enum UnbondError { #[error( "Trying to withdraw more tokens ({0}) than the amount bonded ({0})" )] - UnbondAmountGreaterThanBond(token::Amount, token::Amount), + UnbondAmountGreaterThanBond(String, String), #[error("No bonds found for the validator {0}")] ValidatorHasNoBonds(Address), #[error("Voting power not found for the validator {0}")] @@ -169,9 +171,9 @@ pub enum SlashError { #[derive(Error, Debug)] pub enum CommissionRateChangeError { #[error("Unexpected negative commission rate {0} for validator {1}")] - NegativeRate(Decimal, Address), + NegativeRate(Dec, Address), #[error("Rate change of {0} is too large for validator {1}")] - RateChangeTooLarge(Decimal, Address), + RateChangeTooLarge(Dec, Address), #[error( "There is no maximum rate change written in storage for validator {0}" )] @@ -527,7 +529,7 @@ where pub fn read_validator_max_commission_rate_change( storage: &S, validator: &Address, -) -> storage_api::Result> +) -> storage_api::Result> where S: StorageRead, { @@ -539,7 +541,7 @@ where pub fn write_validator_max_commission_rate_change( storage: &mut S, validator: &Address, - change: Decimal, + change: Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -695,6 +697,28 @@ where .collect() } +/// Read all addresses from the below-threshold set +pub fn read_below_threshold_validator_set_addresses( + storage: &S, + epoch: namada_core::types::storage::Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + let params = read_pos_params(storage)?; + Ok(validator_addresses_handle() + .at(&epoch) + .iter(storage)? + .map(Result::unwrap) + .filter(|address| { + matches!( + validator_state_handle(address).get(storage, epoch, ¶ms), + Ok(Some(ValidatorState::BelowThreshold)) + ) + }) + .collect()) +} + /// Read all addresses from consensus validator set with their stake. pub fn read_consensus_validator_set_addresses_with_stake( storage: &S, @@ -862,7 +886,10 @@ where S: StorageRead + StorageWrite, { let amount = amount.change(); - tracing::debug!("Bonding token amount {amount} at epoch {current_epoch}."); + tracing::debug!( + "Bonding token amount {} at epoch {current_epoch}", + amount.to_string_native() + ); let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; if let Some(source) = source { @@ -898,8 +925,12 @@ where let delta = bond_handle .get_delta_val(storage, ep, ¶ms)? .unwrap_or_default(); - if delta != 0 { - tracing::debug!("bond ∆ at epoch {}: {}", ep, delta); + if !delta.is_zero() { + tracing::debug!( + "bond ∆ at epoch {}: {}", + ep, + delta.to_string_native() + ); } } @@ -924,8 +955,12 @@ where let delta = bond_handle .get_delta_val(storage, ep, ¶ms)? .unwrap_or_default(); - if delta != 0 { - tracing::debug!("bond ∆ at epoch {}: {}", ep, delta); + if !delta.is_zero() { + tracing::debug!( + "bond ∆ at epoch {}: {}", + ep, + delta.to_string_native() + ); } } @@ -987,13 +1022,20 @@ where S: StorageRead + StorageWrite, { let target_epoch = current_epoch + offset; - let consensus_set = &consensus_validator_set_handle().at(&target_epoch); - let below_cap_set = - &below_capacity_validator_set_handle().at(&target_epoch); + let consensus_set = consensus_validator_set_handle().at(&target_epoch); + let below_cap_set = below_capacity_validator_set_handle().at(&target_epoch); let num_consensus_validators = get_num_consensus_validators(storage, target_epoch)?; - if num_consensus_validators < params.max_validator_slots { + + if stake < params.validator_stake_threshold { + validator_state_handle(address).set( + storage, + ValidatorState::BelowThreshold, + current_epoch, + offset, + )?; + } else if num_consensus_validators < params.max_validator_slots { insert_validator_into_set( &consensus_set.at(&stake), storage, @@ -1010,7 +1052,7 @@ where // Check to see if the current genesis validator should replace one // already in the consensus set let min_consensus_amount = - get_min_consensus_validator_amount(consensus_set, storage)?; + get_min_consensus_validator_amount(&consensus_set, storage)?; if stake > min_consensus_amount { // Swap this genesis validator in and demote the last min consensus // validator @@ -1071,8 +1113,8 @@ where Ok(()) } -/// Update validator set when a validator receives a new bond and when -/// its bond is unbonded (self-bond or delegation). +/// Update validator set at the pipeline epoch when a validator receives a new +/// bond and when its bond is unbonded (self-bond or delegation). fn update_validator_set( storage: &mut S, params: &PosParams, @@ -1083,164 +1125,266 @@ fn update_validator_set( where S: StorageRead + StorageWrite, { - if token_change == 0_i128 { + if token_change.is_zero() { return Ok(()); } - let epoch = current_epoch + params.pipeline_len; + let pipeline_epoch = current_epoch + params.pipeline_len; tracing::debug!( - "Update epoch for validator set: {epoch}, validator: {validator}" + "Update epoch for validator set: {pipeline_epoch}, validator: \ + {validator}" ); let consensus_validator_set = consensus_validator_set_handle(); let below_capacity_validator_set = below_capacity_validator_set_handle(); - // Validator sets at the pipeline offset. If these are empty, then we need - // to copy over the most recent filled validator set into this epoch first - let consensus_val_handle = consensus_validator_set.at(&epoch); - let below_capacity_val_handle = below_capacity_validator_set.at(&epoch); + // Validator sets at the pipeline offset + let consensus_val_handle = consensus_validator_set.at(&pipeline_epoch); + let below_capacity_val_handle = + below_capacity_validator_set.at(&pipeline_epoch); - let tokens_pre = read_validator_stake(storage, params, validator, epoch)? - .unwrap_or_default(); + let tokens_pre = + read_validator_stake(storage, params, validator, pipeline_epoch)? + .unwrap_or_default(); // tracing::debug!("VALIDATOR STAKE BEFORE UPDATE: {}", tokens_pre); let tokens_post = tokens_pre.change() + token_change; - // TODO: handle overflow or negative vals perhaps with TryFrom let tokens_post = token::Amount::from_change(tokens_post); - // TODO: The position is only set when the validator is in consensus or - // below_capacity set (not in below_threshold set) - let position = - read_validator_set_position(storage, validator, epoch, params)? - .ok_or_err_msg( - "Validator must have a stored validator set position", - )?; - let consensus_vals_pre = consensus_val_handle.at(&tokens_pre); - - let in_consensus = if consensus_vals_pre.contains(storage, &position)? { - let val_address = consensus_vals_pre.get(storage, &position)?; - debug_assert!(val_address.is_some()); - val_address == Some(validator.clone()) - } else { - false - }; - - if in_consensus { - // It's initially consensus - tracing::debug!("Target validator is consensus"); + // If token amounts both before and after the action are below the threshold + // stake, do nothing + if tokens_pre < params.validator_stake_threshold + && tokens_post < params.validator_stake_threshold + { + return Ok(()); + } - consensus_vals_pre.remove(storage, &position)?; + // The position is only set when the validator is in consensus or + // below_capacity set (not in below_threshold set) + let position = read_validator_set_position( + storage, + validator, + pipeline_epoch, + params, + )?; + if let Some(position) = position { + let consensus_vals_pre = consensus_val_handle.at(&tokens_pre); - let max_below_capacity_validator_amount = - get_max_below_capacity_validator_amount( - &below_capacity_val_handle, - storage, - )? - .unwrap_or_default(); + let in_consensus = if consensus_vals_pre.contains(storage, &position)? { + let val_address = consensus_vals_pre.get(storage, &position)?; + debug_assert!(val_address.is_some()); + val_address == Some(validator.clone()) + } else { + false + }; - if tokens_post < max_below_capacity_validator_amount { - tracing::debug!("Need to swap validators"); - // Place the validator into the below-capacity set and promote the - // lowest position max below-capacity validator. + if in_consensus { + // It's initially consensus + tracing::debug!("Target validator is consensus"); - // Remove the max below-capacity validator first - let below_capacity_vals_max = below_capacity_val_handle - .at(&max_below_capacity_validator_amount.into()); - let lowest_position = - find_first_position(&below_capacity_vals_max, storage)? - .unwrap(); - let removed_max_below_capacity = below_capacity_vals_max - .remove(storage, &lowest_position)? - .expect("Must have been removed"); + // First remove the consensus validator + consensus_vals_pre.remove(storage, &position)?; - // Insert the previous max below-capacity validator into the - // consensus set - insert_validator_into_set( - &consensus_val_handle.at(&max_below_capacity_validator_amount), - storage, - &epoch, - &removed_max_below_capacity, - )?; - validator_state_handle(&removed_max_below_capacity).set( - storage, - ValidatorState::Consensus, - current_epoch, - params.pipeline_len, - )?; + let max_below_capacity_validator_amount = + get_max_below_capacity_validator_amount( + &below_capacity_val_handle, + storage, + )? + .unwrap_or_default(); - // Insert the current validator into the below-capacity set - insert_validator_into_set( - &below_capacity_val_handle.at(&tokens_post.into()), - storage, - &epoch, - validator, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - params.pipeline_len, - )?; + if tokens_post < params.validator_stake_threshold { + tracing::debug!( + "Demoting this validator to the below-threshold set" + ); + // Set the validator state as below-threshold + validator_state_handle(validator).set( + storage, + ValidatorState::BelowThreshold, + current_epoch, + params.pipeline_len, + )?; + + // Remove the validator's position from storage + validator_set_positions_handle() + .at(&pipeline_epoch) + .remove(storage, validator)?; + + // Promote the next below-cap validator if there is one + if let Some(max_bc_amount) = + get_max_below_capacity_validator_amount( + &below_capacity_val_handle, + storage, + )? + { + // Remove the max below-capacity validator first + let below_capacity_vals_max = + below_capacity_val_handle.at(&max_bc_amount.into()); + let lowest_position = + find_first_position(&below_capacity_vals_max, storage)? + .unwrap(); + let removed_max_below_capacity = below_capacity_vals_max + .remove(storage, &lowest_position)? + .expect("Must have been removed"); + + // Insert the previous max below-capacity validator into the + // consensus set + insert_validator_into_set( + &consensus_val_handle.at(&max_bc_amount), + storage, + &pipeline_epoch, + &removed_max_below_capacity, + )?; + validator_state_handle(&removed_max_below_capacity).set( + storage, + ValidatorState::Consensus, + current_epoch, + params.pipeline_len, + )?; + } + } else if tokens_post < max_below_capacity_validator_amount { + tracing::debug!( + "Demoting this validator to the below-capacity set and \ + promoting another to the consensus set" + ); + // Place the validator into the below-capacity set and promote + // the lowest position max below-capacity + // validator. + + // Remove the max below-capacity validator first + let below_capacity_vals_max = below_capacity_val_handle + .at(&max_below_capacity_validator_amount.into()); + let lowest_position = + find_first_position(&below_capacity_vals_max, storage)? + .unwrap(); + let removed_max_below_capacity = below_capacity_vals_max + .remove(storage, &lowest_position)? + .expect("Must have been removed"); + + // Insert the previous max below-capacity validator into the + // consensus set + insert_validator_into_set( + &consensus_val_handle + .at(&max_below_capacity_validator_amount), + storage, + &pipeline_epoch, + &removed_max_below_capacity, + )?; + validator_state_handle(&removed_max_below_capacity).set( + storage, + ValidatorState::Consensus, + current_epoch, + params.pipeline_len, + )?; + + // Insert the current validator into the below-capacity set + insert_validator_into_set( + &below_capacity_val_handle.at(&tokens_post.into()), + storage, + &pipeline_epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + params.pipeline_len, + )?; + } else { + tracing::debug!("Validator remains in consensus set"); + // The current validator should remain in the consensus set - + // place it into a new position + insert_validator_into_set( + &consensus_val_handle.at(&tokens_post), + storage, + &pipeline_epoch, + validator, + )?; + } } else { - tracing::debug!("Validator remains in consensus set"); - // The current validator should remain in the consensus set - place - // it into a new position - insert_validator_into_set( - &consensus_val_handle.at(&tokens_post), - storage, - &epoch, - validator, - )?; - } - } else { - // TODO: handle the new third set - below threshold - - // It's initially below-capacity - let below_capacity_vals_pre = - below_capacity_val_handle.at(&tokens_pre.into()); - let removed = below_capacity_vals_pre.remove(storage, &position)?; - debug_assert!(removed.is_some()); - debug_assert_eq!(&removed.unwrap(), validator); - - let min_consensus_validator_amount = - get_min_consensus_validator_amount(&consensus_val_handle, storage)?; - - if tokens_post > min_consensus_validator_amount { - // Place the validator into the consensus set and demote the last - // position min consensus validator to the below-capacity set - - // Remove the min consensus validator first - let consensus_vals_min = - consensus_val_handle.at(&min_consensus_validator_amount); - let last_position_of_min_consensus_vals = - find_last_position(&consensus_vals_min, storage)?.expect( - "There must be always be at least 1 consensus validator", + // It's initially below-capacity + tracing::debug!("Target validator is below-capacity"); + + let below_capacity_vals_pre = + below_capacity_val_handle.at(&tokens_pre.into()); + let removed = below_capacity_vals_pre.remove(storage, &position)?; + debug_assert!(removed.is_some()); + debug_assert_eq!(&removed.unwrap(), validator); + + let min_consensus_validator_amount = + get_min_consensus_validator_amount( + &consensus_val_handle, + storage, + )?; + + if tokens_post > min_consensus_validator_amount { + // Place the validator into the consensus set and demote the + // last position min consensus validator to the + // below-capacity set + tracing::debug!( + "Inserting validator into the consensus set and demoting \ + a consensus validator to the below-capacity set" ); - let removed_min_consensus = consensus_vals_min - .remove(storage, &last_position_of_min_consensus_vals)? - .expect( - "There must be always be at least 1 consensus validator", + + insert_into_consensus_and_demote_to_below_cap( + storage, + params, + validator, + tokens_post, + min_consensus_validator_amount, + current_epoch, + &consensus_val_handle, + &below_capacity_val_handle, + )?; + } else if tokens_post >= params.validator_stake_threshold { + tracing::debug!("Validator remains in below-capacity set"); + // The current validator should remain in the below-capacity set + insert_validator_into_set( + &below_capacity_val_handle.at(&tokens_post.into()), + storage, + &pipeline_epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + params.pipeline_len, + )?; + } else { + // The current validator is demoted to the below-threshold set + tracing::debug!( + "Demoting this validator to the below-threshold set" ); - // Insert the min consensus validator into the below-capacity set - insert_validator_into_set( - &below_capacity_val_handle - .at(&min_consensus_validator_amount.into()), - storage, - &epoch, - &removed_min_consensus, - )?; - validator_state_handle(&removed_min_consensus).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - params.pipeline_len, - )?; + validator_state_handle(validator).set( + storage, + ValidatorState::BelowThreshold, + current_epoch, + params.pipeline_len, + )?; + + // Remove the validator's position from storage + validator_set_positions_handle() + .at(&pipeline_epoch) + .remove(storage, validator)?; + } + } + } else { + // If there is no position at pipeline offset, then the validator must + // be in the below-threshold set + debug_assert!(tokens_pre < params.validator_stake_threshold); + tracing::debug!("Target validator is below-threshold"); + + // Move the validator into the appropriate set + let num_consensus_validators = + get_num_consensus_validators(storage, pipeline_epoch)?; + if num_consensus_validators < params.max_validator_slots { + // Just insert into the consensus set + tracing::debug!("Inserting validator into the consensus set"); - // Insert the current validator into the consensus set insert_validator_into_set( &consensus_val_handle.at(&tokens_post), storage, - &epoch, + &pipeline_epoch, validator, )?; validator_state_handle(validator).set( @@ -1250,21 +1394,107 @@ where params.pipeline_len, )?; } else { - // The current validator should remain in the below-capacity set - insert_validator_into_set( - &below_capacity_val_handle.at(&tokens_post.into()), - storage, - &epoch, - validator, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::BelowCapacity, - current_epoch, - params.pipeline_len, - )?; + let min_consensus_validator_amount = + get_min_consensus_validator_amount( + &consensus_val_handle, + storage, + )?; + if tokens_post > min_consensus_validator_amount { + // Insert this validator into consensus and demote one into the + // below-capacity + tracing::debug!( + "Inserting validator into the consensus set and demoting \ + a consensus validator to the below-capacity set" + ); + + insert_into_consensus_and_demote_to_below_cap( + storage, + params, + validator, + tokens_post, + min_consensus_validator_amount, + current_epoch, + &consensus_val_handle, + &below_capacity_val_handle, + )?; + } else { + // Insert this validator into below-capacity + tracing::debug!( + "Inserting validator into the below-capacity set" + ); + + insert_validator_into_set( + &below_capacity_val_handle.at(&tokens_post.into()), + storage, + &pipeline_epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + params.pipeline_len, + )?; + } } } + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn insert_into_consensus_and_demote_to_below_cap( + storage: &mut S, + params: &PosParams, + validator: &Address, + tokens_post: token::Amount, + min_consensus_amount: token::Amount, + current_epoch: Epoch, + consensus_set: &ConsensusValidatorSet, + below_capacity_set: &BelowCapacityValidatorSet, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // First, remove the last position min consensus validator + let consensus_vals_min = consensus_set.at(&min_consensus_amount); + let last_position_of_min_consensus_vals = + find_last_position(&consensus_vals_min, storage)? + .expect("There must be always be at least 1 consensus validator"); + let removed_min_consensus = consensus_vals_min + .remove(storage, &last_position_of_min_consensus_vals)? + .expect("There must be always be at least 1 consensus validator"); + + let pipeline_epoch = current_epoch + params.pipeline_len; + + // Insert the min consensus validator into the below-capacity + // set + insert_validator_into_set( + &below_capacity_set.at(&min_consensus_amount.into()), + storage, + &pipeline_epoch, + &removed_min_consensus, + )?; + validator_state_handle(&removed_min_consensus).set( + storage, + ValidatorState::BelowCapacity, + current_epoch, + params.pipeline_len, + )?; + + // Insert the current validator into the consensus set + insert_validator_into_set( + &consensus_set.at(&tokens_post), + storage, + &pipeline_epoch, + validator, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::Consensus, + current_epoch, + params.pipeline_len, + )?; Ok(()) } @@ -1379,13 +1609,14 @@ fn read_validator_set_position( storage: &S, validator: &Address, epoch: Epoch, - params: &PosParams, + _params: &PosParams, ) -> storage_api::Result> where S: StorageRead, { let handle = validator_set_positions_handle(); - handle.get_position(storage, &epoch, validator, params) + // handle.get_position(storage, &epoch, validator, params) + handle.get_data_handler().at(&epoch).get(storage, validator) } /// Find the first (lowest) position in a validator set if it is not empty @@ -1523,7 +1754,10 @@ where S: StorageRead + StorageWrite, { let amount = amount.change(); - tracing::debug!("Unbonding token amount {amount} at epoch {current_epoch}"); + tracing::debug!( + "Unbonding token amount {} at epoch {current_epoch}", + amount.to_string_native() + ); let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; @@ -1565,8 +1799,12 @@ where let delta = bonds_handle .get_delta_val(storage, ep, ¶ms)? .unwrap_or_default(); - if delta != 0 { - tracing::debug!("bond ∆ at epoch {}: {}", ep, delta); + if !delta.is_zero() { + tracing::debug!( + "bond ∆ at epoch {}: {}", + ep, + delta.to_string_native() + ); } } @@ -1576,8 +1814,9 @@ where .unwrap_or_default(); if amount > remaining_at_pipeline { return Err(UnbondError::UnbondAmountGreaterThanBond( - token::Amount::from_change(amount), - token::Amount::from_change(remaining_at_pipeline), + token::Amount::from_change(amount).to_string_native(), + token::Amount::from_change(remaining_at_pipeline) + .to_string_native(), ) .into()); } @@ -1666,13 +1905,17 @@ where let delta = bonds_handle .get_delta_val(storage, ep, ¶ms)? .unwrap_or_default(); - if delta != 0 { - tracing::debug!("bond ∆ at epoch {}: {}", ep, delta); + if !delta.is_zero() { + tracing::debug!( + "bond ∆ at epoch {}: {}", + ep, + delta.to_string_native() + ); } } tracing::debug!( "Token change including slashes on unbond = {}", - -amount_after_slashing + (-amount_after_slashing).to_string_native() ); // Update the validator set at the pipeline offset. Since unbonding from a @@ -1720,7 +1963,7 @@ where fn get_slashed_amount( params: &PosParams, amount: token::Amount, - slashes: &BTreeMap, + slashes: &BTreeMap, ) -> storage_api::Result { // println!("FN `get_slashed_amount`"); @@ -1751,7 +1994,7 @@ fn get_slashed_amount( computed_amounts.remove(item.0); } computed_amounts.push(SlashedAmount { - amount: decimal_mult_amount(*slash_rate, updated_amount), + amount: *slash_rate * updated_amount, epoch: *infraction_epoch, }); } @@ -1800,8 +2043,8 @@ pub fn become_validator( address: &Address, consensus_key: &common::PublicKey, current_epoch: Epoch, - commission_rate: Decimal, - max_commission_rate_change: Decimal, + commission_rate: Dec, + max_commission_rate_change: Dec, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -1842,16 +2085,15 @@ where params.pipeline_len, )?; - let stake = token::Amount::default(); - - insert_validator_into_validator_set( + // The validator's stake at initialization is 0, so its state is immediately + // below-threshold + validator_state_handle(address).set( storage, - params, - address, - stake, + ValidatorState::BelowThreshold, current_epoch, params.pipeline_len, )?; + Ok(()) } @@ -1894,7 +2136,8 @@ where ) = unbond?; tracing::debug!( - "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {amount}", + "Unbond delta ({start_epoch}..{withdraw_epoch}), amount {}", + amount.to_string_native() ); // TODO: adding slash rates in same epoch, applying cumulatively in dif @@ -1919,13 +2162,14 @@ where let amount_after_slashing = get_slashed_amount(¶ms, amount, &slashes_for_this_unbond)?; - // total_slashed += - // amount - token::Amount::from_change(amount_after_slashing); - withdrawable_amount += - token::Amount::from_change(amount_after_slashing); + // total_slashed += amount - token::Amount::from(amount_after_slashing); + withdrawable_amount += token::Amount::from(amount_after_slashing); unbonds_to_remove.push((withdraw_epoch, start_epoch)); } - tracing::debug!("Withdrawing total {withdrawable_amount}"); + tracing::debug!( + "Withdrawing total {}", + withdrawable_amount.to_string_native() + ); // Remove the unbond data from storage for (withdraw_epoch, start_epoch) in unbonds_to_remove { @@ -1964,19 +2208,19 @@ where pub fn change_validator_commission_rate( storage: &mut S, validator: &Address, - new_rate: Decimal, + new_rate: Dec, current_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - if new_rate < Decimal::ZERO { - return Err(CommissionRateChangeError::NegativeRate( - new_rate, - validator.clone(), - ) - .into()); - } + // if new_rate < Uint::zero() { + // return Err(CommissionRateChangeError::NegativeRate( + // new_rate, + // validator.clone(), + // ) + // .into()); + // } let max_change = read_validator_max_commission_rate_change(storage, validator)?; @@ -2000,8 +2244,16 @@ where let rate_before_pipeline = commission_handle .get(storage, pipeline_epoch.prev(), ¶ms)? .expect("Could not find a rate in given epoch"); - let change_from_prev = new_rate - rate_before_pipeline; - if change_from_prev.abs() > max_change.unwrap() { + + // TODO: change this back if we use `Dec` type with a signed integer + // let change_from_prev = new_rate - rate_before_pipeline; + // if change_from_prev.abs() > max_change.unwrap() { + let change_from_prev = if new_rate > rate_before_pipeline { + new_rate - rate_before_pipeline + } else { + rate_before_pipeline - new_rate + }; + if change_from_prev > max_change.unwrap() { return Err(CommissionRateChangeError::RateChangeTooLarge( change_from_prev, validator.clone(), @@ -2033,8 +2285,8 @@ where tracing::error!( "PoS system transfer error, the source doesn't have \ sufficient balance. It has {}, but {} is required", - src_balance, - amount + src_balance.to_string_native(), + amount.to_string_native(), ); } src_balance.spend(&amount); @@ -2103,10 +2355,10 @@ where // TODO: review this logic carefully, apply rewards let slashes = find_validator_slashes(storage, &bond_id.validator)?; let slash_rates = slashes.into_iter().fold( - BTreeMap::::new(), + BTreeMap::::new(), |mut map, slash| { let tot_rate = map.entry(slash.epoch).or_default(); - *tot_rate = cmp::min(Decimal::ONE, *tot_rate + slash.rate); + *tot_rate = cmp::min(Dec::one(), *tot_rate + slash.rate); map }, ); @@ -2121,17 +2373,17 @@ where continue; } - total += token::Amount::from_change(delta); - total_active += token::Amount::from_change(delta); + total += token::Amount::from(delta); + total_active += token::Amount::from(delta); for (slash_epoch, rate) in &slash_rates { if *slash_epoch < bond_epoch { continue; } // TODO: think about truncation - let current_slashed = decimal_mult_i128(*rate, delta); + let current_slashed = *rate * delta; total_active - .checked_sub(token::Amount::from_change(current_slashed)) + .checked_sub(token::Amount::from(current_slashed)) .unwrap_or_default(); } } @@ -2155,53 +2407,56 @@ where // give Tendermint updates for the next epoch let next_epoch: Epoch = current_epoch.next(); - let cur_consensus_validators = + let new_consensus_validator_handle = consensus_validator_set_handle().at(&next_epoch); - let prev_consensus_validators = + let prev_consensus_validator_handle = consensus_validator_set_handle().at(¤t_epoch); - let consensus_validators = cur_consensus_validators + let new_consensus_validators = new_consensus_validator_handle .iter(storage)? .filter_map(|validator| { let ( NestedSubKey::Data { - key: cur_stake, + key: new_stake, nested_sub_key: _, }, address, ) = validator.unwrap(); tracing::debug!( - "Consensus validator address {address}, stake {cur_stake}" + "Consensus validator address {address}, stake {}", + new_stake.to_string_native() ); // Check if the validator was consensus in the previous epoch with - // the same stake + // the same stake. If so, no updated is needed. // Look up previous state and prev and current voting powers - if !prev_consensus_validators.is_empty(storage).unwrap() { + if !prev_consensus_validator_handle.is_empty(storage).unwrap() { let prev_state = validator_state_handle(&address) .get(storage, current_epoch, params) .unwrap(); let prev_tm_voting_power = Lazy::new(|| { - let prev_validator_stake = - validator_deltas_handle(&address) - .get_sum(storage, current_epoch, params) - .unwrap() - .map(token::Amount::from_change) - .unwrap_or_default(); + let prev_validator_stake = read_validator_stake( + storage, + params, + &address, + current_epoch, + ) + .unwrap() + .unwrap_or_default(); into_tm_voting_power( params.tm_votes_per_token, prev_validator_stake, ) }); - let cur_tm_voting_power = Lazy::new(|| { - into_tm_voting_power(params.tm_votes_per_token, cur_stake) + let new_tm_voting_power = Lazy::new(|| { + into_tm_voting_power(params.tm_votes_per_token, new_stake) }); // If it was in `Consensus` before and voting power has not // changed, skip the update if matches!(prev_state, Some(ValidatorState::Consensus)) - && *prev_tm_voting_power == *cur_tm_voting_power + && *prev_tm_voting_power == *new_tm_voting_power { tracing::debug!( "skipping validator update, {address} is in consensus \ @@ -2209,16 +2464,19 @@ where ); return None; } - - // If both previous and current voting powers are 0, skip - // update - if *prev_tm_voting_power == 0 && *cur_tm_voting_power == 0 { + // If both previous and current voting powers are 0, and the + // validator_stake_threshold is 0, skip update + if params.validator_stake_threshold == token::Amount::default() + && *prev_tm_voting_power == 0 + && *new_tm_voting_power == 0 + { tracing::info!( "skipping validator update, {address} is in consensus \ set but without voting power" ); return None; } + // TODO: maybe debug_assert that the new stake is >= threshold? } let consensus_key = validator_consensus_key_handle(&address) .get(storage, next_epoch, params) @@ -2230,69 +2488,60 @@ where ); Some(ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key, - bonded_stake: cur_stake.into(), + bonded_stake: new_stake, })) }); - let cur_below_capacity_validators = - below_capacity_validator_set_handle().at(&next_epoch); - let prev_below_capacity_vals = - below_capacity_validator_set_handle().at(¤t_epoch); - - let below_capacity_validators = cur_below_capacity_validators - .iter(storage) - .unwrap() + + let prev_consensus_validators = prev_consensus_validator_handle + .iter(storage)? .filter_map(|validator| { let ( NestedSubKey::Data { - key: cur_stake, + key: _prev_stake, nested_sub_key: _, }, address, ) = validator.unwrap(); - let cur_stake = token::Amount::from(cur_stake); - tracing::debug!( - "Below-capacity validator address {address}, stake {cur_stake}" - ); + let new_state = validator_state_handle(&address) + .get(storage, next_epoch, params) + .unwrap(); - let prev_validator_stake = validator_deltas_handle(&address) - .get_sum(storage, current_epoch, params) + let prev_tm_voting_power = Lazy::new(|| { + let prev_validator_stake = read_validator_stake( + storage, + params, + &address, + current_epoch, + ) .unwrap() - .map(token::Amount::from_change) .unwrap_or_default(); - let prev_tm_voting_power = into_tm_voting_power( - params.tm_votes_per_token, - prev_validator_stake, - ); + into_tm_voting_power( + params.tm_votes_per_token, + prev_validator_stake, + ) + }); - // If the validator previously had no voting power, it wasn't in - // tendermint set and we have to skip it. - if prev_tm_voting_power == 0 { - tracing::debug!( - "skipping validator update {address}, it's inactive and \ - previously had no voting power" + // If the validator is still in the Consensus set, we accounted for + // it in the `new_consensus_validators` iterator above + if matches!(new_state, Some(ValidatorState::Consensus)) { + return None; + } else if params.validator_stake_threshold + == token::Amount::default() + && *prev_tm_voting_power == 0 + { + // If the new state is not Consensus but its prev voting power + // was 0 and the stake threshold is 0, we can also skip the + // update + tracing::info!( + "skipping validator update, {address} is in consensus set \ + but without voting power" ); return None; } - if !prev_below_capacity_vals.is_empty(storage).unwrap() { - // Look up the previous state - let prev_state = validator_state_handle(&address) - .get(storage, current_epoch, params) - .unwrap(); - // If the `prev_state.is_none()`, it's a new validator that - // is `BelowCapacity`, so no update is needed. If it - // previously was `BelowCapacity` there's no update needed - // either. - if !matches!(prev_state, Some(ValidatorState::Consensus)) { - tracing::debug!( - "skipping validator update, {address} is not and \ - wasn't previously in consensus set" - ); - return None; - } - } - + // The remaining validators were previously Consensus but no longer + // are, so they must be deactivated let consensus_key = validator_consensus_key_handle(&address) .get(storage, next_epoch, params) .unwrap() @@ -2303,8 +2552,9 @@ where ); Some(ValidatorSetUpdate::Deactivated(consensus_key)) }); - Ok(consensus_validators - .chain(below_capacity_validators) + + Ok(new_consensus_validators + .chain(prev_consensus_validators) .map(f) .collect()) } @@ -2516,7 +2766,7 @@ where } let change: token::Change = BorshDeserialize::try_from_slice(&val_bytes).ok()?; - if change == 0 { + if change.is_zero() { return None; } return Some((bond_id, start, change)); @@ -2689,14 +2939,14 @@ fn make_bond_details( .cloned() .unwrap_or_default(); let amount = token::Amount::from_change(change); - let mut slash_rates_by_epoch = BTreeMap::::new(); + let mut slash_rates_by_epoch = BTreeMap::::new(); let validator_slashes = applied_slashes.entry(validator.clone()).or_default(); for slash in slashes { if slash.epoch >= start { let cur_rate = slash_rates_by_epoch.entry(slash.epoch).or_default(); - *cur_rate = cmp::min(Decimal::ONE, *cur_rate + slash.rate); + *cur_rate = cmp::min(Dec::one(), *cur_rate + slash.rate); if !prev_applied_slashes.iter().any(|s| s == slash) { validator_slashes.push(slash.clone()); @@ -2733,7 +2983,7 @@ fn make_unbond_details( .get(validator) .cloned() .unwrap_or_default(); - let mut slash_rates_by_epoch = BTreeMap::::new(); + let mut slash_rates_by_epoch = BTreeMap::::new(); let validator_slashes = applied_slashes.entry(validator.clone()).or_default(); @@ -2748,7 +2998,7 @@ fn make_unbond_details( .unwrap_or_default() { let cur_rate = slash_rates_by_epoch.entry(slash.epoch).or_default(); - *cur_rate = cmp::min(Decimal::ONE, *cur_rate + slash.rate); + *cur_rate = cmp::min(Dec::one(), *cur_rate + slash.rate); if !prev_applied_slashes.iter().any(|s| s == slash) { validator_slashes.push(slash.clone()); @@ -2833,7 +3083,7 @@ where debug_assert_eq!( into_tm_voting_power( params.tm_votes_per_token, - stake_from_deltas + stake_from_deltas, ), i64::try_from(validator_vp).unwrap_or_default(), ); @@ -2848,8 +3098,8 @@ where let rewards_calculator = PosRewardsCalculator { proposer_reward: params.block_proposer_reward, signer_reward: params.block_vote_reward, - signing_stake: u64::from(total_signing_stake), - total_stake: u64::from(total_consensus_stake), + signing_stake: total_signing_stake, + total_stake: total_consensus_stake, }; let coeffs = rewards_calculator .get_reward_coeffs() @@ -2866,10 +3116,9 @@ where // Compute the fractional block rewards for each consensus validator and // update the reward accumulators - let consensus_stake_unscaled: Decimal = - total_consensus_stake.as_dec_unscaled(); - let signing_stake_unscaled: Decimal = total_signing_stake.as_dec_unscaled(); - let mut values: HashMap = HashMap::new(); + let consensus_stake_unscaled: Dec = total_consensus_stake.into(); + let signing_stake_unscaled: Dec = total_signing_stake.into(); + let mut values: HashMap = HashMap::new(); for validator in consensus_validators.iter(storage)? { let ( NestedSubKey::Data { @@ -2887,8 +3136,8 @@ where continue; } - let mut rewards_frac = Decimal::default(); - let stake_unscaled: Decimal = stake.as_dec_unscaled(); + let mut rewards_frac = Dec::zero(); + let stake_unscaled: Dec = stake.into(); // println!( // "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} = // {}", epoch, stake @@ -2927,25 +3176,25 @@ pub fn compute_cubic_slash_rate( storage: &S, params: &PosParams, infraction_epoch: Epoch, -) -> storage_api::Result +) -> storage_api::Result where S: StorageRead, { // println!("COMPUTING CUBIC SLASH RATE"); - let mut sum_vp_fraction = Decimal::ZERO; + let mut sum_vp_fraction = Dec::zero(); let (start_epoch, end_epoch) = params.cubic_slash_epoch_window(infraction_epoch); for epoch in Epoch::iter_bounds_inclusive(start_epoch, end_epoch) { let consensus_stake = - Decimal::from(get_total_consensus_stake(storage, epoch)?); + Dec::from(get_total_consensus_stake(storage, epoch)?); // println!("Consensus stake in epoch {}: {}", epoch, consensus_stake); let processing_epoch = epoch + params.slash_processing_epoch_offset(); let slashes = enqueued_slashes_handle().at(&processing_epoch); - let infracting_stake = slashes - .iter(storage)? - .map(|res| { + let infracting_stake = slashes.iter(storage)?.fold( + Ok(Dec::zero()), + |acc: storage_api::Result, res| { let ( NestedSubKey::Data { key: validator, @@ -2959,21 +3208,26 @@ where .unwrap_or_default(); // println!("Val {} stake: {}", &validator, validator_stake); - Ok(Decimal::from(validator_stake)) + if let Ok(inner) = acc { + Ok(inner + Dec::from(validator_stake)) + } else { + acc + } // TODO: does something more complex need to be done // here in the event some of these slashes correspond to // the same validator? - }) - .sum::>()?; + }, + )?; sum_vp_fraction += infracting_stake / consensus_stake; } // println!("sum_vp_fraction: {}", sum_vp_fraction); - Ok(dec!(9) * sum_vp_fraction * sum_vp_fraction) + Ok(Dec::new(9, 0).unwrap() * sum_vp_fraction * sum_vp_fraction) } /// Record a slash for a misbehavior that has been received from Tendermint and /// then jail the validator, removing it from the validator set. The slash rate /// will be computed at a later epoch. +#[allow(clippy::too_many_arguments)] pub fn slash( storage: &mut S, params: &PosParams, @@ -2982,6 +3236,7 @@ pub fn slash( evidence_block_height: impl Into, slash_type: SlashType, validator: &Address, + validator_set_update_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -2991,7 +3246,7 @@ where epoch: evidence_epoch, block_height: evidence_block_height, r#type: slash_type, - rate: Decimal::ZERO, // Let the rate be 0 initially before processing + rate: Dec::zero(), // Let the rate be 0 initially before processing }; // Need `+1` because we process at the beginning of a new epoch let processing_epoch = @@ -3017,7 +3272,7 @@ where // Remove the validator from the set starting at the next epoch and up thru // the pipeline epoch. for epoch in - Epoch::iter_bounds_inclusive(current_epoch.next(), pipeline_epoch) + Epoch::iter_bounds_inclusive(validator_set_update_epoch, pipeline_epoch) { let prev_state = validator_state_handle(validator) .get(storage, epoch, params)? @@ -3035,6 +3290,9 @@ where .at(&epoch) .at(&token::Amount::from_change(amount_pre)) .remove(storage, &val_position)?; + validator_set_positions_handle() + .at(&epoch) + .remove(storage, validator)?; // For the pipeline epoch only: // promote the next max inactive validator to the active @@ -3091,6 +3349,12 @@ where .at(&epoch) .at(&token::Amount::from_change(amount_pre).into()) .remove(storage, &val_position)?; + validator_set_positions_handle() + .at(&epoch) + .remove(storage, validator)?; + } + ValidatorState::BelowThreshold => { + println!("Below-threshold"); } ValidatorState::Inactive => { println!("INACTIVE"); @@ -3172,7 +3436,7 @@ where debug_assert_eq!(enqueued_slash.epoch, infraction_epoch); let slash_rate = cmp::min( - Decimal::ONE, + Dec::one(), cmp::max( enqueued_slash.r#type.get_slash_rate(¶ms), cubic_slash_rate, @@ -3213,10 +3477,10 @@ where "Validator {} stake at infraction epoch {} = {}", &validator, infraction_epoch, - validator_stake_at_infraction + validator_stake_at_infraction.to_string_native() ); - let mut total_rate = Decimal::ZERO; + let mut total_rate = Dec::zero(); for enqueued_slash in &enqueued_slashes { // Add this slash to the list of validator's slashes in storage @@ -3225,7 +3489,7 @@ where total_rate += enqueued_slash.rate; } - total_rate = cmp::min(Decimal::ONE, total_rate); + total_rate = cmp::min(Dec::one(), total_rate); // Find the total amount deducted from the deltas due to unbonds that // became active after the infraction epoch, accounting for slashes @@ -3248,7 +3512,7 @@ where let (start, unbond_amount) = unbond?; tracing::debug!( "UnbondRecord: amount = {}, start_epoch {}", - &unbond_amount, + unbond_amount.to_string_native(), &start ); if start <= infraction_epoch { @@ -3283,7 +3547,7 @@ where tracing::debug!( "Total unbonded (epoch {}) w slashing = {}", epoch, - total_unbonded + total_unbonded.to_string_native() ); } @@ -3300,7 +3564,7 @@ where tracing::debug!( "Epoch {}\nLast slash = {}", current_epoch + offset, - last_slash + last_slash.to_string_native() ); let mut recent_unbonds = token::Change::default(); let unbonds = @@ -3310,7 +3574,7 @@ where let (start, unbond_amount) = unbond?; tracing::debug!( "UnbondRecord: amount = {}, start_epoch {}", - &unbond_amount, + unbond_amount.to_string_native(), &start ); if start <= infraction_epoch { @@ -3345,18 +3609,14 @@ where tracing::debug!( "Total unbonded (offset {}) w slashing = {}", offset, - total_unbonded + total_unbonded.to_string_native() ); } - let this_slash = decimal_mult_amount( - total_rate, - validator_stake_at_infraction - total_unbonded, - ) - .change(); + let this_slash = total_rate + * (validator_stake_at_infraction - total_unbonded).change(); let diff_slashed_amount = last_slash - this_slash; last_slash = this_slash; - // println!("This slash = {}", this_slash); // println!("Diff slashed amount = {}", diff_slashed_amount); // total_slashed -= diff_slashed_amount; @@ -3397,7 +3657,7 @@ where tracing::debug!( "Deltas change = {} at offset {} for validator {}", - delta, + delta.to_string_native(), offset, &validator ); @@ -3553,11 +3813,11 @@ fn find_slashes_in_range( start: Epoch, end: Option, validator: &Address, -) -> storage_api::Result> +) -> storage_api::Result> where S: StorageRead, { - let mut slashes = BTreeMap::::new(); + let mut slashes = BTreeMap::::new(); for slash in validator_slashes_handle(validator).iter(storage)? { let slash = slash?; if start <= slash.epoch @@ -3568,7 +3828,7 @@ where // &slash.epoch, &slash.rate // ); let cur_rate = slashes.entry(slash.epoch).or_default(); - *cur_rate = cmp::min(*cur_rate + slash.rate, Decimal::ONE); + *cur_rate = cmp::min(*cur_rate + slash.rate, Dec::one()); } } Ok(slashes) diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 0e54e0f7f2..8501aff379 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -1,10 +1,10 @@ //! Proof-of-Stake system parameters use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::types::dec::Dec; use namada_core::types::storage::Epoch; -use rust_decimal::prelude::ToPrimitive; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; +use namada_core::types::token; +use namada_core::types::uint::Uint; use thiserror::Error; /// Proof-of-Stake system parameters, set at genesis and can only be changed via @@ -24,25 +24,28 @@ pub struct PosParams { /// The voting power per fundamental unit of the staking token (namnam). /// Used in validators' voting power calculation to interface with /// tendermint. - pub tm_votes_per_token: Decimal, + pub tm_votes_per_token: Dec, /// Amount of tokens rewarded to a validator for proposing a block - pub block_proposer_reward: Decimal, + pub block_proposer_reward: Dec, /// Amount of tokens rewarded to each validator that voted on a block /// proposal - pub block_vote_reward: Decimal, + pub block_vote_reward: Dec, /// Maximum staking rewards rate per annum - pub max_inflation_rate: Decimal, + pub max_inflation_rate: Dec, /// Target ratio of staked NAM tokens to total NAM tokens - pub target_staked_ratio: Decimal, + pub target_staked_ratio: Dec, /// Fraction of validator's stake that should be slashed on a duplicate /// vote. - pub duplicate_vote_min_slash_rate: Decimal, + pub duplicate_vote_min_slash_rate: Dec, /// Fraction of validator's stake that should be slashed on a light client /// attack. - pub light_client_attack_min_slash_rate: Decimal, + pub light_client_attack_min_slash_rate: Dec, /// Number of epochs above and below (separately) the current epoch to /// consider when doing cubic slashing pub cubic_slashing_window_length: u64, + /// The minimum amount of bonded tokens that a validator needs to be in + /// either the `consensus` or `below_capacity` validator sets + pub validator_stake_threshold: token::Amount, } impl Default for PosParams { @@ -53,18 +56,20 @@ impl Default for PosParams { unbonding_len: 21, // 1 voting power per 1 fundamental token (10^6 per NAM or 1 per // namnam) - tm_votes_per_token: dec!(1.0), - block_proposer_reward: dec!(0.125), - block_vote_reward: dec!(0.1), + tm_votes_per_token: Dec::one(), + block_proposer_reward: Dec::new(125, 3).expect("Test failed"), + block_vote_reward: Dec::new(1, 1).expect("Test failed"), // PoS inflation of 10% - max_inflation_rate: dec!(0.1), + max_inflation_rate: Dec::new(1, 1).expect("Test failed"), // target staked ratio of 2/3 - target_staked_ratio: dec!(0.6667), + target_staked_ratio: Dec::new(6667, 4).expect("Test failed"), // slash 0.1% - duplicate_vote_min_slash_rate: dec!(0.001), + duplicate_vote_min_slash_rate: Dec::new(1, 3).expect("Test failed"), // slash 0.1% - light_client_attack_min_slash_rate: dec!(0.001), + light_client_attack_min_slash_rate: Dec::new(1, 3) + .expect("Test failed"), cubic_slashing_window_length: 1, + validator_stake_threshold: token::Amount::native_whole(1_u64), } } } @@ -76,9 +81,9 @@ pub enum ValidationError { "Maximum total voting power is too large: got {0}, expected at most \ {MAX_TOTAL_VOTING_POWER}" )] - TotalVotingPowerTooLarge(u64), + TotalVotingPowerTooLarge(Uint), #[error("Votes per token cannot be greater than 1, got {0}")] - VotesPerTokenGreaterThanOne(Decimal), + VotesPerTokenGreaterThanOne(Dec), #[error("Pipeline length must be >= 2, got {0}")] PipelineLenTooShort(u64), #[error( @@ -118,28 +123,26 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows - // - // TODO: decide if this is still a check we want to do (in its current - // state with our latest voting power conventions, it will fail - // always) - let max_total_voting_power = Decimal::from(self.max_validator_slots) - * self.tm_votes_per_token - * Decimal::from(TOKEN_MAX_AMOUNT); + let max_total_voting_power = (self.tm_votes_per_token + * TOKEN_MAX_AMOUNT + * self.max_validator_slots) + .to_uint() + .expect("Cannot fail"); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { errors.push(ValidationError::TotalVotingPowerTooLarge( - max_total_voting_power.to_u64().unwrap(), + max_total_voting_power, )) } } Err(_) => errors.push(ValidationError::TotalVotingPowerTooLarge( - max_total_voting_power.to_u64().unwrap(), + max_total_voting_power, )), } // Check that there is no more than 1 vote per token - if self.tm_votes_per_token > dec!(1.0) { + if self.tm_votes_per_token > Dec::one() { errors.push(ValidationError::VotesPerTokenGreaterThanOne( self.tm_votes_per_token, )) @@ -197,8 +200,8 @@ mod tests { /// Testing helpers #[cfg(any(test, feature = "testing"))] pub mod testing { + use namada_core::types::dec::Dec; use proptest::prelude::*; - use rust_decimal::Decimal; use super::*; @@ -210,13 +213,13 @@ pub mod testing { // `unbonding_len` > `pipeline_len` unbonding_len in pipeline_len + 1..pipeline_len + 8, pipeline_len in Just(pipeline_len), - tm_votes_per_token in 1..10_001_u64) + tm_votes_per_token in 1..10_001_i128) -> PosParams { PosParams { max_validator_slots, pipeline_len, unbonding_len, - tm_votes_per_token: Decimal::from(tm_votes_per_token) / dec!(10_000), + tm_votes_per_token: Dec::new(tm_votes_per_token, 4).expect("Test failed"), // The rest of the parameters that are not being used in the PoS // VP are constant for now ..Default::default() @@ -224,10 +227,10 @@ pub mod testing { } } - /// Get an arbitrary rate - a Decimal value between 0 and 1 inclusive, with + /// Get an arbitrary rate - a Dec value between 0 and 1 inclusive, with /// some fixed precision - pub fn arb_rate() -> impl Strategy { - (0..=100_000_u64) - .prop_map(|num| Decimal::from(num) / Decimal::from(100_000_u64)) + pub fn arb_rate() -> impl Strategy { + (0..=100_000_i128) + .prop_map(|num| Dec::new(num, 5).expect("Test failed")) } } diff --git a/proof_of_stake/src/pos_queries.rs b/proof_of_stake/src/pos_queries.rs index 251350e414..6873531cd9 100644 --- a/proof_of_stake/src/pos_queries.rs +++ b/proof_of_stake/src/pos_queries.rs @@ -123,9 +123,8 @@ where pub fn get_total_voting_power(self, epoch: Option) -> token::Amount { self.get_consensus_validators(epoch) .iter() - .map(|validator| u64::from(validator.bonded_stake)) - .sum::() - .into() + .map(|validator| validator.bonded_stake) + .sum::() } /// Simple helper function for the ledger to get balances diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs index 6f830d9c52..26d1b91442 100644 --- a/proof_of_stake/src/rewards.rs +++ b/proof_of_stake/src/rewards.rs @@ -1,10 +1,13 @@ //! PoS rewards distribution. -use rust_decimal::Decimal; -use rust_decimal_macros::dec; +use namada_core::types::dec::Dec; +use namada_core::types::token::Amount; +use namada_core::types::uint::{Uint, I256}; use thiserror::Error; -const MIN_PROPOSER_REWARD: Decimal = dec!(0.01); +/// This is equal to 0.01. +const MIN_PROPOSER_REWARD: Dec = + Dec(I256(Uint([10000000000u64, 0u64, 0u64, 0u64]))); /// Errors during rewards calculation #[derive(Debug, Error)] @@ -16,8 +19,8 @@ pub enum RewardsError { least 2/3 of the total bonded stake)." )] InsufficientVotes { - votes_needed: u64, - signing_stake: u64, + votes_needed: Uint, + signing_stake: Uint, }, /// rewards coefficients are not set #[error("Rewards coefficients are not properly set.")] @@ -28,9 +31,9 @@ pub enum RewardsError { #[derive(Debug, Copy, Clone)] #[allow(missing_docs)] pub struct PosRewards { - pub proposer_coeff: Decimal, - pub signer_coeff: Decimal, - pub active_val_coeff: Decimal, + pub proposer_coeff: Dec, + pub signer_coeff: Dec, + pub active_val_coeff: Dec, } /// Holds relevant PoS parameters and is used to calculate the coefficients for @@ -38,13 +41,13 @@ pub struct PosRewards { #[derive(Debug, Copy, Clone)] pub struct PosRewardsCalculator { /// Rewards fraction that goes to the block proposer - pub proposer_reward: Decimal, + pub proposer_reward: Dec, /// Rewards fraction that goes to the block signers - pub signer_reward: Decimal, + pub signer_reward: Dec, /// Total stake of validators who signed the block - pub signing_stake: u64, + pub signing_stake: Amount, /// Total stake of the whole consensus set - pub total_stake: u64, + pub total_stake: Amount, } impl PosRewardsCalculator { @@ -64,18 +67,18 @@ impl PosRewardsCalculator { if signing_stake < votes_needed { return Err(RewardsError::InsufficientVotes { - votes_needed, - signing_stake, + votes_needed: votes_needed.into(), + signing_stake: signing_stake.into(), }); } // Logic for determining the coefficients. - let proposer_coeff = proposer_reward - * Decimal::from(signing_stake - votes_needed) - / Decimal::from(total_stake) - + MIN_PROPOSER_REWARD; + let proposer_coeff = + Dec::from(proposer_reward * (signing_stake - votes_needed)) + / Dec::from(total_stake) + + MIN_PROPOSER_REWARD; let signer_coeff = signer_reward; - let active_val_coeff = dec!(1.0) - proposer_coeff - signer_coeff; + let active_val_coeff = Dec::one() - proposer_coeff - signer_coeff; let coeffs = PosRewards { proposer_coeff, @@ -87,7 +90,7 @@ impl PosRewardsCalculator { } /// Implement as ceiling of (2/3) * validator set stake - fn get_min_required_votes(&self) -> u64 { - ((2 * self.total_stake) + 3 - 1) / 3 + fn get_min_required_votes(&self) -> Amount { + ((self.total_stake * 2u64) + (3u64 - 1u64)) / 3u64 } } diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 95b8d91fac..da8f9bb6b6 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -13,16 +13,16 @@ use namada_core::types::address::testing::{ address_from_simple_seed, arb_established_address, }; use namada_core::types::address::{Address, EstablishedAddressGen}; +use namada_core::types::dec::Dec; use namada_core::types::key::common::{PublicKey, SecretKey}; use namada_core::types::key::testing::{ arb_common_keypair, common_sk_from_simple_seed, }; use namada_core::types::storage::{BlockHeight, Epoch}; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_core::types::{address, key, token}; use proptest::prelude::*; use proptest::test_runner::Config; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see // `tracing` logs from tests use test_log::test; @@ -30,10 +30,10 @@ use test_log::test; use crate::parameters::testing::arb_pos_params; use crate::parameters::PosParams; use crate::types::{ - decimal_mult_amount, into_tm_voting_power, BondDetails, BondId, - BondsAndUnbondsDetails, ConsensusValidator, GenesisValidator, Position, - ReverseOrdTokenAmount, SlashType, UnbondDetails, ValidatorSetUpdate, - ValidatorState, WeightedValidator, + into_tm_voting_power, BondDetails, BondId, BondsAndUnbondsDetails, + ConsensusValidator, GenesisValidator, Position, ReverseOrdTokenAmount, + SlashType, UnbondDetails, ValidatorSetUpdate, ValidatorState, + WeightedValidator, }; use crate::{ become_validator, below_capacity_validator_set_handle, bond_handle, @@ -42,6 +42,7 @@ use crate::{ get_num_consensus_validators, init_genesis, insert_validator_into_validator_set, is_validator, process_slashes, read_below_capacity_validator_set_addresses_with_stake, + read_below_threshold_validator_set_addresses, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_delta_value, read_validator_stake, slash, staking_token_address, total_deltas_handle, unbond_handle, unbond_tokens, @@ -60,9 +61,8 @@ proptest! { #[test] fn test_init_genesis( - pos_params in arb_pos_params(Some(5)), + (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..10), start_epoch in (0_u64..1000).prop_map(Epoch), - genesis_validators in arb_genesis_validators(1..10), ) { test_init_genesis_aux(pos_params, start_epoch, genesis_validators) @@ -78,8 +78,7 @@ proptest! { #[test] fn test_bonds( - pos_params in arb_pos_params(Some(5)), - genesis_validators in arb_genesis_validators(1..3), + (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..3), ) { test_bonds_aux(pos_params, genesis_validators) @@ -95,10 +94,9 @@ proptest! { #[test] fn test_become_validator( - pos_params in arb_pos_params(Some(5)), + (pos_params, genesis_validators) in arb_params_and_genesis_validators(Some(5), 1..3), new_validator in arb_established_address().prop_map(Address::Established), new_validator_consensus_key in arb_common_keypair(), - genesis_validators in arb_genesis_validators(1..3), ) { test_become_validator_aux(pos_params, new_validator, @@ -122,6 +120,20 @@ proptest! { } } +fn arb_params_and_genesis_validators( + num_max_validator_slots: Option, + val_size: Range, +) -> impl Strategy)> { + let params = arb_pos_params(num_max_validator_slots); + params.prop_flat_map(move |params| { + let validators = arb_genesis_validators( + val_size.clone(), + Some(params.validator_stake_threshold), + ); + (Just(params), validators) + }) +} + fn test_slashes_with_unbonding_params() -> impl Strategy, u64)> { let params = arb_pos_params(Some(5)); @@ -129,7 +141,7 @@ fn test_slashes_with_unbonding_params() let unbond_delay = 0..(params.slash_processing_epoch_offset() * 2); // Must have at least 4 validators so we can slash one and the cubic // slash rate will be less than 100% - let validators = arb_genesis_validators(4..10); + let validators = arb_genesis_validators(4..10, None); (Just(params), validators, unbond_delay) }) } @@ -177,7 +189,9 @@ fn test_init_genesis_aux( let state = validator_state_handle(&validator.address) .get(&s, start_epoch, ¶ms) .unwrap(); - if (i as u64) < params.max_validator_slots { + if (i as u64) < params.max_validator_slots + && validator.tokens >= params.validator_stake_threshold + { // should be in consensus set let handle = consensus_validator_set_handle().at(&start_epoch); assert!(handle.at(&validator.tokens).iter(&s).unwrap().any( @@ -187,10 +201,9 @@ fn test_init_genesis_aux( } )); assert_eq!(state, Some(ValidatorState::Consensus)); - } else { - // TODO: one more set once we have `below_threshold` - - // should be in below-capacity set + } else if validator.tokens >= params.validator_stake_threshold { + // Should be in below-capacity set if its tokens are greater than + // `validator_stake_threshold` let handle = below_capacity_validator_set_handle().at(&start_epoch); assert!(handle.at(&validator.tokens.into()).iter(&s).unwrap().any( |result| { @@ -199,6 +212,17 @@ fn test_init_genesis_aux( } )); assert_eq!(state, Some(ValidatorState::BelowCapacity)); + } else { + // Should be in below-threshold + let bt_addresses = + read_below_threshold_validator_set_addresses(&s, start_epoch) + .unwrap(); + assert!( + bt_addresses + .into_iter() + .any(|addr| { addr == validator.address }) + ); + assert_eq!(state, Some(ValidatorState::BelowThreshold)); } } } @@ -244,7 +268,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { read_total_stake(&s, ¶ms, pipeline_epoch).unwrap(); // Self-bond - let amount_self_bond = token::Amount::from(100_500_000); + let amount_self_bond = token::Amount::from_uint(100_500_000, 0).unwrap(); credit_tokens(&mut s, &staking_token, &validator.address, amount_self_bond) .unwrap(); bond_tokens( @@ -348,7 +372,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Get a non-validating account with tokens let delegator = address::testing::gen_implicit_address(); - let amount_del = token::Amount::from(201_000_000); + let amount_del = token::Amount::from_uint(201_000_000, 0).unwrap(); credit_tokens(&mut s, &staking_token, &delegator, amount_del).unwrap(); let balance_key = token::balance_key(&staking_token, &delegator); let balance = s @@ -484,7 +508,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { // Unbond the self-bond with an amount that will remove all of the self-bond // executed after genesis and some of the genesis bond let amount_self_unbond: token::Amount = - amount_self_bond + (u64::from(validator.tokens) / 2).into(); + amount_self_bond + (validator.tokens / 2); // When the difference is 0, only the non-genesis self-bond is unbonded let unbonded_genesis_self_bond = amount_self_unbond - amount_self_bond != token::Amount::default(); @@ -628,7 +652,7 @@ fn test_bonds_aux(params: PosParams, validators: Vec) { ); // Unbond delegation - let amount_undel = token::Amount::from(1_000_000); + let amount_undel = token::Amount::from_uint(1_000_000, 0).unwrap(); unbond_tokens( &mut s, Some(&delegator), @@ -799,8 +823,8 @@ fn test_become_validator_aux( &new_validator, &consensus_key, current_epoch, - Decimal::new(5, 2), - Decimal::new(5, 2), + Dec::new(5, 2).expect("Dec creation failed"), + Dec::new(5, 2).expect("Dec creation failed"), ) .unwrap(); assert!(is_validator(&s, &new_validator).unwrap()); @@ -808,21 +832,16 @@ fn test_become_validator_aux( let num_consensus_after = get_num_consensus_validators(&s, current_epoch + params.pipeline_len) .unwrap(); - assert_eq!( - if validators.len() as u64 >= params.max_validator_slots { - num_consensus_before - } else { - num_consensus_before + 1 - }, - num_consensus_after - ); + // The new validator is initialized with no stake and thus is in the + // below-threshold set + assert_eq!(num_consensus_before, num_consensus_after); // Advance to epoch 2 current_epoch = advance_epoch(&mut s, ¶ms); // Self-bond to the new validator let staking_token = staking_token_address(&s); - let amount = token::Amount::from(100_500_000); + let amount = token::Amount::from_uint(100_500_000, 0).unwrap(); credit_tokens(&mut s, &staking_token, &new_validator, amount).unwrap(); bond_tokens(&mut s, None, &new_validator, amount, current_epoch).unwrap(); @@ -909,7 +928,8 @@ fn test_slashes_with_unbonding_aux( let val_addr = &validator.address; let val_tokens = validator.tokens; println!( - "Validator that will misbehave addr {val_addr}, tokens {val_tokens}" + "Validator that will misbehave addr {val_addr}, tokens {}", + val_tokens.to_string_native() ); // Genesis @@ -941,6 +961,7 @@ fn test_slashes_with_unbonding_aux( evidence_block_height, slash_0_type, val_addr, + current_epoch.next(), ) .unwrap(); @@ -958,8 +979,8 @@ fn test_slashes_with_unbonding_aux( } // Unbond half of the tokens - let unbond_amount = decimal_mult_amount(dec!(0.5), val_tokens); - println!("Going to unbond {unbond_amount}"); + let unbond_amount = Dec::new(5, 1).unwrap() * val_tokens; + println!("Going to unbond {}", unbond_amount.to_string_native()); let unbond_epoch = current_epoch; unbond_tokens(&mut s, None, val_addr, unbond_amount, unbond_epoch).unwrap(); @@ -978,6 +999,7 @@ fn test_slashes_with_unbonding_aux( evidence_block_height, slash_1_type, val_addr, + current_epoch.next(), ) .unwrap(); @@ -1004,7 +1026,7 @@ fn test_slashes_with_unbonding_aux( let val_balance_post = read_balance(&s, &token, val_addr).unwrap(); let withdrawn_tokens = val_balance_post - val_balance_pre; - println!("Withdrew {withdrawn_tokens} tokens"); + println!("Withdrew {} tokens", withdrawn_tokens.to_string_native()); assert_eq!(exp_withdraw_from_details, withdrawn_tokens); @@ -1020,17 +1042,17 @@ fn test_slashes_with_unbonding_aux( .rate; println!("Slash 0 rate {slash_rate_0}, slash 1 rate {slash_rate_1}"); - let expected_withdrawn_amount = decimal_mult_amount( - dec!(1) - slash_rate_1, - decimal_mult_amount(dec!(1) - slash_rate_0, unbond_amount), + let expected_withdrawn_amount = Dec::from( + (Dec::one() - slash_rate_1) + * (Dec::one() - slash_rate_0) + * unbond_amount, ); // Allow some rounding error, 1 NAMNAM per each slash - let rounding_error_tolerance = 2; + let rounding_error_tolerance = + Dec::new(2, NATIVE_MAX_DECIMAL_PLACES).unwrap(); assert!( - dbg!( - (expected_withdrawn_amount.change() - withdrawn_tokens.change()) - .abs() - ) <= rounding_error_tolerance + dbg!(expected_withdrawn_amount.abs_diff(&Dec::from(withdrawn_tokens))) + <= rounding_error_tolerance ); // TODO: finish once implemented @@ -1118,20 +1140,27 @@ fn test_validator_sets() { // Start with two genesis validators with 1 NAM stake let epoch = Epoch::default(); - let ((val1, pk1), stake1) = (gen_validator(), token::Amount::whole(1)); - let ((val2, pk2), stake2) = (gen_validator(), token::Amount::whole(1)); - let ((val3, pk3), stake3) = (gen_validator(), token::Amount::whole(10)); - let ((val4, pk4), stake4) = (gen_validator(), token::Amount::whole(1)); - let ((val5, pk5), stake5) = (gen_validator(), token::Amount::whole(100)); - let ((val6, pk6), stake6) = (gen_validator(), token::Amount::whole(1)); - let ((val7, pk7), stake7) = (gen_validator(), token::Amount::whole(1)); - println!("val1: {val1}, {pk1}, {stake1}"); - println!("val2: {val2}, {pk2}, {stake2}"); - println!("val3: {val3}, {pk3}, {stake3}"); - println!("val4: {val4}, {pk4}, {stake4}"); - println!("val5: {val5}, {pk5}, {stake5}"); - println!("val6: {val6}, {pk6}, {stake6}"); - println!("val7: {val7}, {pk7}, {stake7}"); + let ((val1, pk1), stake1) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val2, pk2), stake2) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val3, pk3), stake3) = + (gen_validator(), token::Amount::native_whole(10)); + let ((val4, pk4), stake4) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val5, pk5), stake5) = + (gen_validator(), token::Amount::native_whole(100)); + let ((val6, pk6), stake6) = + (gen_validator(), token::Amount::native_whole(1)); + let ((val7, pk7), stake7) = + (gen_validator(), token::Amount::native_whole(1)); + println!("\nval1: {val1}, {pk1}, {}", stake1.to_string_native()); + println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); + println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); + println!("val4: {val4}, {pk4}, {}", stake4.to_string_native()); + println!("val5: {val5}, {pk5}, {}", stake5.to_string_native()); + println!("val6: {val6}, {pk6}, {}", stake6.to_string_native()); + println!("val7: {val7}, {pk7}, {}", stake7.to_string_native()); init_genesis( &mut s, @@ -1141,15 +1170,17 @@ fn test_validator_sets() { address: val1.clone(), tokens: stake1, consensus_key: pk1.clone(), - commission_rate: Decimal::new(1, 1), - max_commission_rate_change: Decimal::new(1, 1), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), }, GenesisValidator { address: val2.clone(), tokens: stake2, consensus_key: pk2.clone(), - commission_rate: Decimal::new(1, 1), - max_commission_rate_change: Decimal::new(1, 1), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), }, ] .into_iter(), @@ -1300,7 +1331,7 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk3, - bonded_stake: stake3.into(), + bonded_stake: stake3, }) ); @@ -1355,16 +1386,18 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk5, - bonded_stake: stake5.into(), + bonded_stake: stake5, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk2)); // Unbond some stake from val1, it should be be swapped with the greatest - // below-capacity validator val2 into the below-capacity set - let unbond = token::Amount::from(500_000); + // below-capacity validator val2 into the below-capacity set. The stake of + // val1 will go below 1 NAM, which is the validator_stake_threshold, so it + // will enter the below-threshold validator set. + let unbond = token::Amount::from_uint(500_000, 0).unwrap(); let stake1 = stake1 - unbond; - println!("val1 {val1} new stake {stake1}"); + println!("val1 {val1} new stake {}", stake1.to_string_native()); // Because `update_validator_set` and `update_validator_deltas` are // effective from pipeline offset, we use pipeline epoch for the rest of the // checks @@ -1422,7 +1455,7 @@ fn test_validator_sets() { .map(Result::unwrap) .collect(); - assert_eq!(below_capacity_vals.len(), 3); + assert_eq!(below_capacity_vals.len(), 2); assert!(matches!( &below_capacity_vals[0], (lazy_map::NestedSubKey::Data { @@ -1439,17 +1472,15 @@ fn test_validator_sets() { }, address) if address == &val6 && stake == &stake6 && *position == Position(2) )); - assert!(matches!( - &below_capacity_vals[2], - ( - lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, - address - ) - if address == &val1 && stake == &stake1 && *position == Position(0) - )); + + let below_threshold_vals = + read_below_threshold_validator_set_addresses(&s, pipeline_epoch) + .unwrap() + .into_iter() + .collect::>(); + + assert_eq!(below_threshold_vals.len(), 1); + assert_eq!(&below_threshold_vals[0], &val1); // Advance to EPOCH 5 let epoch = advance_epoch(&mut s, ¶ms); @@ -1461,7 +1492,7 @@ fn test_validator_sets() { assert!(tm_updates.is_empty()); // Insert another validator with stake 1 - it should be added to below - // capacity set after val1 + // capacity set insert_validator(&mut s, &val7, &pk7, stake7, epoch); // Epoch 7 let val7_epoch = pipeline_epoch; @@ -1506,7 +1537,7 @@ fn test_validator_sets() { .map(Result::unwrap) .collect(); - assert_eq!(below_capacity_vals.len(), 4); + assert_eq!(below_capacity_vals.len(), 3); assert!(matches!( &below_capacity_vals[0], (lazy_map::NestedSubKey::Data { @@ -1534,17 +1565,15 @@ fn test_validator_sets() { ) if address == &val7 && stake == &stake7 && *position == Position(3) )); - assert!(matches!( - &below_capacity_vals[3], - ( - lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, - address - ) - if address == &val1 && stake == &stake1 && *position == Position(0) - )); + + let below_threshold_vals = + read_below_threshold_validator_set_addresses(&s, pipeline_epoch) + .unwrap() + .into_iter() + .collect::>(); + + assert_eq!(below_threshold_vals.len(), 1); + assert_eq!(&below_threshold_vals[0], &val1); // Advance to EPOCH 6 let epoch = advance_epoch(&mut s, ¶ms); @@ -1558,16 +1587,16 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk4.clone(), - bonded_stake: stake4.into(), + bonded_stake: stake4, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk1)); // Bond some stake to val6, it should be be swapped with the lowest // consensus validator val2 into the consensus set - let bond = token::Amount::from(500_000); + let bond = token::Amount::from_uint(500_000, 0).unwrap(); let stake6 = stake6 + bond; - println!("val6 {val6} new stake {stake6}"); + println!("val6 {val6} new stake {}", stake6.to_string_native()); update_validator_set(&mut s, ¶ms, &val6, bond.change(), epoch).unwrap(); update_validator_deltas( &mut s, @@ -1620,7 +1649,7 @@ fn test_validator_sets() { .map(Result::unwrap) .collect(); - assert_eq!(below_capacity_vals.len(), 4); + assert_eq!(below_capacity_vals.len(), 3); dbg!(&below_capacity_vals); assert!(matches!( &below_capacity_vals[0], @@ -1649,17 +1678,15 @@ fn test_validator_sets() { ) if address == &val4 && stake == &stake4 && *position == Position(4) )); - assert!(matches!( - &below_capacity_vals[3], - ( - lazy_map::NestedSubKey::Data { - key: ReverseOrdTokenAmount(stake), - nested_sub_key: lazy_map::SubKey::Data(position), - }, - address - ) - if address == &val1 && stake == &stake1 && *position == Position(0) - )); + + let below_threshold_vals = + read_below_threshold_validator_set_addresses(&s, pipeline_epoch) + .unwrap() + .into_iter() + .collect::>(); + + assert_eq!(below_threshold_vals.len(), 1); + assert_eq!(&below_threshold_vals[0], &val1); // Advance to EPOCH 7 let epoch = advance_epoch(&mut s, ¶ms); @@ -1681,7 +1708,7 @@ fn test_validator_sets() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk6, - bonded_stake: stake6.into(), + bonded_stake: stake6, }) ); assert_eq!(tm_updates[1], ValidatorSetUpdate::Deactivated(pk4)); @@ -1699,8 +1726,11 @@ fn test_validator_sets_swap() { // Only 2 consensus validator slots let params = PosParams { max_validator_slots: 2, + // Set the stake threshold to 0 so no validators are in the + // below-threshold set + validator_stake_threshold: token::Amount::default(), // Set 0.1 votes per token - tm_votes_per_token: dec!(0.1), + tm_votes_per_token: Dec::new(1, 1).expect("Dec creation failed"), ..Default::default() }; let addr_seed = "seed"; @@ -1752,14 +1782,17 @@ fn test_validator_sets_swap() { // Start with two genesis validators, one with 1 voting power and other 0 let epoch = Epoch::default(); // 1M voting power - let ((val1, pk1), stake1) = (gen_validator(), token::Amount::whole(10)); + let ((val1, pk1), stake1) = + (gen_validator(), token::Amount::native_whole(10)); // 0 voting power - let ((val2, pk2), stake2) = (gen_validator(), token::Amount::from(5)); + let ((val2, pk2), stake2) = + (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); // 0 voting power - let ((val3, pk3), stake3) = (gen_validator(), token::Amount::from(5)); - println!("val1: {val1}, {pk1}, {stake1}"); - println!("val2: {val2}, {pk2}, {stake2}"); - println!("val3: {val3}, {pk3}, {stake3}"); + let ((val3, pk3), stake3) = + (gen_validator(), token::Amount::from_uint(5, 0).unwrap()); + println!("val1: {val1}, {pk1}, {}", stake1.to_string_native()); + println!("val2: {val2}, {pk2}, {}", stake2.to_string_native()); + println!("val3: {val3}, {pk3}, {}", stake3.to_string_native()); init_genesis( &mut s, @@ -1769,15 +1802,17 @@ fn test_validator_sets_swap() { address: val1, tokens: stake1, consensus_key: pk1, - commission_rate: Decimal::new(1, 1), - max_commission_rate_change: Decimal::new(1, 1), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), }, GenesisValidator { address: val2.clone(), tokens: stake2, consensus_key: pk2, - commission_rate: Decimal::new(1, 1), - max_commission_rate_change: Decimal::new(1, 1), + commission_rate: Dec::new(1, 1).expect("Dec creation failed"), + max_commission_rate_change: Dec::new(1, 1) + .expect("Dec creation failed"), }, ] .into_iter(), @@ -1796,9 +1831,9 @@ fn test_validator_sets_swap() { // Add 2 bonds, one for val2 and greater one for val3 let bonds_epoch_1 = pipeline_epoch; - let bond2 = token::Amount::from(1); + let bond2 = token::Amount::from_uint(1, 0).unwrap(); let stake2 = stake2 + bond2; - let bond3 = token::Amount::from(4); + let bond3 = token::Amount::from_uint(4, 0).unwrap(); let stake3 = stake3 + bond3; assert!(stake2 < stake3); @@ -1835,7 +1870,7 @@ fn test_validator_sets_swap() { // Add 2 more bonds, same amount for `val2` and val3` let bonds_epoch_2 = pipeline_epoch; - let bonds = token::Amount::whole(1); + let bonds = token::Amount::native_whole(1); let stake2 = stake2 + bonds; let stake3 = stake3 + bonds; assert!(stake2 < stake3); @@ -1893,7 +1928,7 @@ fn test_validator_sets_swap() { tm_updates[0], ValidatorSetUpdate::Consensus(ConsensusValidator { consensus_key: pk3, - bonded_stake: stake3.into(), + bonded_stake: stake3, }) ); } @@ -1930,30 +1965,57 @@ fn advance_epoch(s: &mut TestWlStorage, params: &PosParams) -> Epoch { fn arb_genesis_validators( size: Range, + threshold: Option, ) -> impl Strategy> { let tokens: Vec<_> = (0..size.end) - .map(|_| (1..=10_000_000_u64).prop_map(token::Amount::from)) + .map(|ix| { + if ix == 0 { + // If there's a threshold, make sure that at least one validator + // has at least a stake greater or equal to the threshold to + // avoid having an empty consensus set. + threshold + .map(|token| token.raw_amount().as_u64()) + .unwrap_or(1)..=10_000_000_u64 + } else { + 1..=10_000_000_u64 + } + .prop_map(token::Amount::from) + }) .collect(); - (size, tokens).prop_map(|(size, token_amounts)| { - // use unique seeds to generate validators' address and consensus key - let seeds = (0_u64..).take(size); - seeds - .zip(token_amounts) - .map(|(seed, tokens)| { - let address = address_from_simple_seed(seed); - let consensus_sk = common_sk_from_simple_seed(seed); - let consensus_key = consensus_sk.to_public(); - - let commission_rate = Decimal::new(5, 2); - let max_commission_rate_change = Decimal::new(1, 2); - GenesisValidator { - address, - tokens, - consensus_key, - commission_rate, - max_commission_rate_change, + (size, tokens) + .prop_map(|(size, token_amounts)| { + // use unique seeds to generate validators' address and consensus + // key + let seeds = (0_u64..).take(size); + seeds + .zip(token_amounts) + .map(|(seed, tokens)| { + let address = address_from_simple_seed(seed); + let consensus_sk = common_sk_from_simple_seed(seed); + let consensus_key = consensus_sk.to_public(); + + let commission_rate = Dec::new(5, 2).expect("Test failed"); + let max_commission_rate_change = + Dec::new(1, 2).expect("Test failed"); + GenesisValidator { + address, + tokens, + consensus_key, + commission_rate, + max_commission_rate_change, + } + }) + .collect() + }) + .prop_filter( + "Must have at least one genesis validator with stake above the \ + provided threshold, if any.", + move |gen_vals: &Vec| { + if let Some(thresh) = threshold { + gen_vals.iter().any(|val| val.tokens >= thresh) + } else { + true } - }) - .collect() - }) + }, + ) } diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index df3c85be13..bd0d890c4e 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -9,32 +9,32 @@ use namada_core::ledger::storage_api::collections::lazy_map::NestedSubKey; use namada_core::ledger::storage_api::token::read_balance; use namada_core::ledger::storage_api::{token, StorageRead}; use namada_core::types::address::{self, Address}; +use namada_core::types::dec::Dec; use namada_core::types::key; use namada_core::types::key::common::PublicKey; use namada_core::types::storage::Epoch; +use namada_core::types::token::Change; use proptest::prelude::*; use proptest::test_runner::Config; use proptest_state_machine::{ prop_state_machine, ReferenceStateMachine, StateMachineTest, }; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; // Use `RUST_LOG=info` (or another tracing level) and `--nocapture` to see // `tracing` logs from tests use test_log::test; -use super::arb_genesis_validators; -use crate::parameters::testing::{arb_pos_params, arb_rate}; +use crate::parameters::testing::arb_rate; use crate::parameters::PosParams; +use crate::tests::arb_params_and_genesis_validators; use crate::types::{ - decimal_mult_amount, decimal_mult_i128, BondId, GenesisValidator, - ReverseOrdTokenAmount, Slash, SlashType, SlashedAmount, ValidatorState, - WeightedValidator, + BondId, GenesisValidator, ReverseOrdTokenAmount, Slash, SlashType, + SlashedAmount, ValidatorState, WeightedValidator, }; use crate::{ below_capacity_validator_set_handle, consensus_validator_set_handle, - enqueued_slashes_handle, read_pos_params, validator_deltas_handle, - validator_slashes_handle, validator_state_handle, + enqueued_slashes_handle, read_below_threshold_validator_set_addresses, + read_pos_params, validator_deltas_handle, validator_slashes_handle, + validator_state_handle, }; prop_state_machine! { @@ -71,6 +71,8 @@ struct AbstractPosState { /// Below-capacity validator set. Pipelined. below_capacity_set: BTreeMap>>, + /// Below-threshold validator set. Pipelined. + below_threshold_set: BTreeMap>, /// Validator states. Pipelined. validator_states: BTreeMap>, /// Unbonded bonds. The outer key for Epoch is pipeline + unbonding offset @@ -104,8 +106,8 @@ enum Transition { InitValidator { address: Address, consensus_key: PublicKey, - commission_rate: Decimal, - max_commission_rate_change: Decimal, + commission_rate: Dec, + max_commission_rate_change: Dec, }, Bond { id: BondId, @@ -169,7 +171,7 @@ impl StateMachineTest for ConcretePosState { &crate::ADDRESS, ) .unwrap(); - println!("PoS balance: {}", pos_balance); + println!("PoS balance: {}", pos_balance.to_string_native()); match transition { Transition::NextEpoch => { println!("\nCONCRETE Next epoch"); @@ -421,6 +423,7 @@ impl StateMachineTest for ConcretePosState { height, slash_type, &address, + current_epoch.next(), ) .unwrap(); @@ -652,26 +655,31 @@ impl ConcretePosState { ) { let pipeline = submit_epoch + params.pipeline_len; // Read the consensus sets data using iterator - let consensus_set = crate::consensus_validator_set_handle() + let num_in_consensus = crate::consensus_validator_set_handle() .at(&pipeline) .iter(&self.s) .unwrap() .map(|res| res.unwrap()) - .collect::>(); - let below_cap_set = crate::below_capacity_validator_set_handle() + .filter(|(_keys, addr)| addr == &id.validator) + .count(); + + let num_in_below_cap = crate::below_capacity_validator_set_handle() .at(&pipeline) .iter(&self.s) .unwrap() .map(|res| res.unwrap()) - .collect::>(); - let num_occurrences = consensus_set - .iter() .filter(|(_keys, addr)| addr == &id.validator) - .count() - + below_cap_set - .iter() - .filter(|(_keys, addr)| addr == &id.validator) + .count(); + + let num_in_below_thresh = + read_below_threshold_validator_set_addresses(&self.s, pipeline) + .unwrap() + .into_iter() + .filter(|addr| addr == &id.validator) .count(); + + let num_occurrences = + num_in_consensus + num_in_below_cap + num_in_below_thresh; let validator_is_jailed = crate::validator_state_handle(&id.validator) .get(&self.s, pipeline, params) .unwrap() @@ -696,22 +704,31 @@ impl ConcretePosState { &self.s, pipeline, ) .unwrap(); + let below_thresh_set = + crate::read_below_threshold_validator_set_addresses( + &self.s, pipeline, + ) + .unwrap(); let weighted = WeightedValidator { bonded_stake: stake_at_pipeline, address: id.validator, }; let consensus_val = consensus_set.get(&weighted); let below_cap_val = below_cap_set.get(&weighted); + let below_thresh_val = below_thresh_set.get(&weighted.address); // Post-condition: The validator should be updated in exactly once in // the validator sets let jailed_condition = validator_is_jailed && consensus_val.is_none() - && below_cap_val.is_none(); - assert!( - (consensus_val.is_some() ^ below_cap_val.is_some()) - || jailed_condition - ); + && below_cap_val.is_none() + && below_thresh_val.is_none(); + + let mut num_sets = i32::from(consensus_val.is_some()); + num_sets += i32::from(below_cap_val.is_some()); + num_sets += i32::from(below_thresh_val.is_some()); + + assert!(num_sets == 1 || jailed_condition); // Post-condition: The stake of the validators in the consensus set is // greater than or equal to below-capacity validators @@ -727,9 +744,11 @@ impl ConcretePosState { { assert!( consensus_stake >= below_cap_stake, - "Consensus validator {consensus_addr} with stake \ - {consensus_stake} and below-capacity {below_cap_addr} \ - with stake {below_cap_stake} should be swapped." + "Consensus validator {consensus_addr} with stake {} and \ + below-capacity {below_cap_addr} with stake {} should be \ + swapped.", + consensus_stake.to_string_native(), + below_cap_stake.to_string_native() ); } } @@ -758,29 +777,36 @@ impl ConcretePosState { .unwrap() .contains(address) ); + assert!( + !crate::read_below_threshold_validator_set_addresses( + &self.s, epoch + ) + .unwrap() + .contains(address) + ); assert!( !crate::read_all_validator_addresses(&self.s, epoch) .unwrap() .contains(address) ); } - let weighted = WeightedValidator { - bonded_stake: Default::default(), - address: address.clone(), - }; let in_consensus = - crate::read_consensus_validator_set_addresses_with_stake( - &self.s, pipeline, - ) - .unwrap() - .contains(&weighted); - let in_bc = - crate::read_below_capacity_validator_set_addresses_with_stake( + crate::read_consensus_validator_set_addresses(&self.s, pipeline) + .unwrap() + .contains(address); + let in_bc = crate::read_below_capacity_validator_set_addresses( + &self.s, pipeline, + ) + .unwrap() + .contains(address); + let in_below_thresh = + crate::read_below_threshold_validator_set_addresses( &self.s, pipeline, ) .unwrap() - .contains(&weighted); - assert!(in_consensus ^ in_bc); + .contains(address); + + assert!(in_below_thresh && !in_consensus && !in_bc); } fn check_misbehavior_post_conditions( @@ -837,7 +863,7 @@ impl ConcretePosState { if let Some(slash) = slash { assert_eq!(slash.epoch, infraction_epoch); assert_eq!(slash.r#type, slash_type); - assert_eq!(slash.rate, Decimal::ZERO); + assert_eq!(slash.rate, Dec::zero()); } else { panic!("Could not find the slash enqueued"); } @@ -878,24 +904,35 @@ impl ConcretePosState { .unwrap(); assert_eq!(val_state, Some(ValidatorState::Jailed)); } - let in_consensus = consensus_validator_set_handle() - .at(&(current_epoch + params.pipeline_len)) + let pipeline_epoch = current_epoch + params.pipeline_len; + + let num_in_consensus = consensus_validator_set_handle() + .at(&pipeline_epoch) .iter(&self.s) .unwrap() - .any(|res| { - let (_, val_address) = res.unwrap(); - val_address == validator.clone() - }); + .map(|res| res.unwrap()) + .filter(|(_keys, addr)| addr == validator) + .count(); - let in_bc = below_capacity_validator_set_handle() - .at(&(current_epoch + params.pipeline_len)) + let num_in_bc = below_capacity_validator_set_handle() + .at(&pipeline_epoch) .iter(&self.s) .unwrap() - .any(|res| { - let (_, val_address) = res.unwrap(); - val_address == validator.clone() - }); - assert!(in_consensus ^ in_bc); + .map(|res| res.unwrap()) + .filter(|(_keys, addr)| addr == validator) + .count(); + + let num_in_bt = read_below_threshold_validator_set_addresses( + &self.s, + pipeline_epoch, + ) + .unwrap() + .into_iter() + .filter(|addr| addr == validator) + .count(); + + let num_occurrences = num_in_consensus + num_in_bc + num_in_bt; + assert_eq!(num_occurrences, 1); let val_state = validator_state_handle(validator) .get(&self.s, current_epoch + params.pipeline_len, params) @@ -903,6 +940,7 @@ impl ConcretePosState { assert!( val_state == Some(ValidatorState::Consensus) || val_state == Some(ValidatorState::BelowCapacity) + || val_state == Some(ValidatorState::BelowThreshold) ); } @@ -934,10 +972,10 @@ impl ConcretePosState { tracing::debug!( "Consensus val {}, stake: {} ({})", &validator, - u64::from(bonded_stake), - deltas_stake + bonded_stake.to_string_native(), + deltas_stake.to_string_native(), ); - assert!(deltas_stake >= 0); + assert!(!deltas_stake.is_negative()); assert_eq!( bonded_stake, token::Amount::from_change(deltas_stake) @@ -987,8 +1025,8 @@ impl ConcretePosState { tracing::debug!( "Below-cap val {}, stake: {} ({})", &validator, - u64::from(bonded_stake), - deltas_stake + bonded_stake.to_string_native(), + deltas_stake.to_string_native(), ); assert_eq!( bonded_stake, @@ -1039,6 +1077,53 @@ impl ConcretePosState { assert!(!vals.contains(&validator)); vals.insert(validator); } + + for validator in + crate::read_below_threshold_validator_set_addresses( + &self.s, epoch, + ) + .unwrap() + { + let stake = validator_deltas_handle(&validator) + .get_sum(&self.s, epoch, params) + .unwrap() + .unwrap_or_default(); + tracing::debug!( + "Below-thresh val {}, stake {}", + &validator, + stake.to_string_native() + ); + + let state = crate::validator_state_handle(&validator) + .get(&self.s, epoch, params) + .unwrap() + .unwrap(); + + assert_eq!(state, ValidatorState::BelowThreshold); + assert_eq!( + state, + ref_state + .validator_states + .get(&epoch) + .unwrap() + .get(&validator) + .cloned() + .unwrap() + ); + assert_eq!( + stake, + ref_state + .validator_stakes + .get(&epoch) + .unwrap() + .get(&validator) + .cloned() + .unwrap() + ); + assert!(!vals.contains(&validator)); + vals.insert(validator); + } + // Jailed validators not in a set let all_validators = crate::read_all_validator_addresses(&self.s, epoch).unwrap(); @@ -1064,7 +1149,11 @@ impl ConcretePosState { .get_sum(&self.s, epoch, params) .unwrap() .unwrap_or_default(); - tracing::debug!("Jailed val {}, stake {}", &val, stake); + tracing::debug!( + "Jailed val {}, stake {}", + &val, + stake.to_string_native() + ); assert_eq!( state, @@ -1100,7 +1189,7 @@ impl ReferenceStateMachine for AbstractPosState { fn init_state() -> BoxedStrategy { println!("\nInitializing abstract state machine"); - (arb_pos_params(Some(5)), arb_genesis_validators(5..10)) + arb_params_and_genesis_validators(Some(5), 5..10) .prop_map(|(params, genesis_validators)| { let epoch = Epoch::default(); let mut state = Self { @@ -1118,6 +1207,7 @@ impl ReferenceStateMachine for AbstractPosState { validator_stakes: Default::default(), consensus_set: Default::default(), below_capacity_set: Default::default(), + below_threshold_set: Default::default(), validator_states: Default::default(), validator_slashes: Default::default(), enqueued_slashes: Default::default(), @@ -1153,7 +1243,19 @@ impl ReferenceStateMachine for AbstractPosState { .iter() .map(|(_stake, validators)| validators.len() as u64) .sum(); - let deque = if state.params.max_validator_slots + + if tokens < state.params.validator_stake_threshold { + state + .below_threshold_set + .entry(epoch) + .or_default() + .insert(address.clone()); + state + .validator_states + .entry(epoch) + .or_default() + .insert(address, ValidatorState::BelowThreshold); + } else if state.params.max_validator_slots > consensus_vals_len { state @@ -1161,7 +1263,10 @@ impl ReferenceStateMachine for AbstractPosState { .entry(epoch) .or_default() .insert(address.clone(), ValidatorState::Consensus); - consensus_set.entry(tokens).or_default() + consensus_set + .entry(tokens) + .or_default() + .push_back(address); } else { state .validator_states @@ -1176,11 +1281,13 @@ impl ReferenceStateMachine for AbstractPosState { below_cap_set .entry(ReverseOrdTokenAmount(tokens)) .or_default() + .push_back(address) }; - deque.push_back(address) } - // Ensure that below-capacity set is initialized even if empty + // Ensure that below-capacity and below-threshold sets are + // initialized even if empty state.below_capacity_set.entry(epoch).or_default(); + state.below_threshold_set.entry(epoch).or_default(); // Copy validator sets up to pipeline epoch for epoch in epoch.next().iter_range(state.params.pipeline_len) @@ -1274,11 +1381,13 @@ impl ReferenceStateMachine for AbstractPosState { let arb_unbondable = prop::sample::select(unbondable); let arb_unbond = arb_unbondable.prop_flat_map(|(id, deltas_sum)| { + let deltas_sum = i128::try_from(deltas_sum).unwrap(); // Generate an amount to unbond, up to the sum assert!(deltas_sum > 0); (0..deltas_sum).prop_map(move |to_unbond| { let id = id.clone(); - let amount = token::Amount::from_change(to_unbond); + let amount = + token::Amount::from_change(Change::from(to_unbond)); Transition::Unbond { id, amount } }) }); @@ -1333,44 +1442,29 @@ impl ReferenceStateMachine for AbstractPosState { .validator_stakes .entry(pipeline) .or_default() - .insert(address.clone(), 0_i128); - - // Insert into validator set at pipeline - let consensus_set = - state.consensus_set.entry(pipeline).or_default(); - - let consensus_vals_len = consensus_set - .iter() - .map(|(_stake, validators)| validators.len() as u64) - .sum(); + .insert(address.clone(), 0_i128.into()); - let deque = if state.params.max_validator_slots - > consensus_vals_len - { - state - .validator_states - .entry(pipeline) - .or_default() - .insert(address.clone(), ValidatorState::Consensus); - consensus_set.entry(token::Amount::default()).or_default() - } else { - state - .validator_states - .entry(pipeline) - .or_default() - .insert(address.clone(), ValidatorState::BelowCapacity); - let below_cap_set = - state.below_capacity_set.entry(pipeline).or_default(); - below_cap_set - .entry(ReverseOrdTokenAmount(token::Amount::default())) - .or_default() - }; - deque.push_back(address.clone()); + // Insert into the below-threshold set at pipeline since the + // initial stake is 0 + state + .below_threshold_set + .entry(pipeline) + .or_default() + .insert(address.clone()); + state + .validator_states + .entry(pipeline) + .or_default() + .insert(address.clone(), ValidatorState::BelowThreshold); state.debug_validators(); } Transition::Bond { id, amount } => { - println!("\nABSTRACT Bond {} tokens, id = {}", amount, id); + println!( + "\nABSTRACT Bond {} tokens, id = {}", + amount.to_string_native(), + id + ); if *amount != token::Amount::default() { let change = token::Change::from(*amount); @@ -1391,7 +1485,11 @@ impl ReferenceStateMachine for AbstractPosState { state.debug_validators(); } Transition::Unbond { id, amount } => { - println!("\nABSTRACT Unbond {} tokens, id = {}", amount, id); + println!( + "\nABSTRACT Unbond {} tokens, id = {}", + amount.to_string_native(), + id + ); if *amount != token::Amount::default() { let change = token::Change::from(*amount); @@ -1448,7 +1546,7 @@ impl ReferenceStateMachine for AbstractPosState { epoch: *infraction_epoch, block_height: *height, r#type: *slash_type, - rate: Decimal::ZERO, + rate: Dec::zero(), }; // Enqueue the slash for future processing @@ -1555,6 +1653,15 @@ impl ReferenceStateMachine for AbstractPosState { .or_default() .remove(&stake.into()); } + } else if state + .is_in_below_threshold(address, current_epoch + offset) + { + let removed = state + .below_threshold_set + .entry(current_epoch + offset) + .or_default() + .remove(address); + debug_assert!(removed); } else { // Just make sure the validator is already jailed debug_assert_eq!( @@ -1622,7 +1729,20 @@ impl ReferenceStateMachine for AbstractPosState { sum + validators.len() as u64 }); - if num_consensus < state.params.max_validator_slots { + if pipeline_stake + < state.params.validator_stake_threshold.change() + { + // Place into the below-threshold set + let below_threshold_set_pipeline = state + .below_threshold_set + .entry(pipeline_epoch) + .or_default(); + below_threshold_set_pipeline.insert(address.clone()); + validator_states_pipeline.insert( + address.clone(), + ValidatorState::BelowThreshold, + ); + } else if num_consensus < state.params.max_validator_slots { // Place directly into the consensus set debug_assert!( state @@ -1806,7 +1926,8 @@ impl ReferenceStateMachine for AbstractPosState { .iter() .filter(|(_addr, val_state)| match val_state { ValidatorState::Consensus - | ValidatorState::BelowCapacity => true, + | ValidatorState::BelowCapacity + | ValidatorState::BelowThreshold => true, ValidatorState::Inactive | ValidatorState::Jailed => false, }) @@ -1906,6 +2027,10 @@ impl AbstractPosState { epoch, self.below_capacity_set.get(&prev_epoch).unwrap().clone(), ); + self.below_threshold_set.insert( + epoch, + self.below_threshold_set.get(&prev_epoch).unwrap().clone(), + ); self.validator_states.insert( epoch, self.validator_states.get(&prev_epoch).unwrap().clone(), @@ -1923,7 +2048,7 @@ impl AbstractPosState { let bond = bonds.entry(pipeline_epoch).or_default(); *bond += change; // Remove fully unbonded entries - if *bond == 0 { + if bond.is_zero() { bonds.remove(&pipeline_epoch); } // Update total_bonded @@ -1965,27 +2090,37 @@ impl AbstractPosState { tracing::debug!("Bonds before decrementing"); for (start, amnt) in bonds.iter() { - tracing::debug!("Bond epoch {} - amnt {}", start, amnt); + tracing::debug!( + "Bond epoch {} - amnt {}", + start, + amnt.to_string_native() + ); } for (bond_epoch, bond_amnt) in bonds.iter_mut().rev() { - tracing::debug!("remaining {}", remaining); - tracing::debug!("Bond epoch {} - amnt {}", bond_epoch, bond_amnt); + tracing::debug!("remaining {}", remaining.to_string_native()); + tracing::debug!( + "Bond epoch {} - amnt {}", + bond_epoch, + bond_amnt.to_string_native() + ); let to_unbond = cmp::min(*bond_amnt, remaining); - tracing::debug!("to_unbond (init) = {}", to_unbond); + tracing::debug!( + "to_unbond (init) = {}", + to_unbond.to_string_native() + ); *bond_amnt -= to_unbond; *unbonds += token::Amount::from_change(to_unbond); - let slashes_for_this_bond: BTreeMap = - validator_slashes - .iter() - .cloned() - .filter(|s| *bond_epoch <= s.epoch) - .fold(BTreeMap::new(), |mut acc, s| { - let cur = acc.entry(s.epoch).or_default(); - *cur += s.rate; - acc - }); + let slashes_for_this_bond: BTreeMap = validator_slashes + .iter() + .cloned() + .filter(|s| *bond_epoch <= s.epoch) + .fold(BTreeMap::new(), |mut acc, s| { + let cur = acc.entry(s.epoch).or_default(); + *cur += s.rate; + acc + }); tracing::debug!( "Slashes for this bond{:?}", slashes_for_this_bond.clone() @@ -1999,21 +2134,25 @@ impl AbstractPosState { .change(); tracing::debug!( "Cur amnt after slashing = {}", - &amount_after_slashing + &amount_after_slashing.to_string_native() ); let amt = unbond_records.entry(*bond_epoch).or_default(); *amt += token::Amount::from_change(to_unbond); remaining -= to_unbond; - if remaining == 0 { + if remaining.is_zero() { break; } } tracing::debug!("Bonds after decrementing"); for (start, amnt) in bonds.iter() { - tracing::debug!("Bond epoch {} - amnt {}", start, amnt); + tracing::debug!( + "Bond epoch {} - amnt {}", + start, + amnt.to_string_native() + ); } let pipeline_state = self @@ -2065,20 +2204,29 @@ impl AbstractPosState { let consensus_set = self.consensus_set.entry(pipeline).or_default(); let below_cap_set = self.below_capacity_set.entry(pipeline).or_default(); + let below_thresh_set = + self.below_threshold_set.entry(pipeline).or_default(); + let validator_stakes = self.validator_stakes.get(&pipeline).unwrap(); let validator_states = self.validator_states.get_mut(&pipeline).unwrap(); - let state = validator_states.get(validator).unwrap(); + let state_pre = validator_states.get(validator).unwrap(); let this_val_stake_pre = *validator_stakes.get(validator).unwrap(); let this_val_stake_post = token::Amount::from_change(this_val_stake_pre + change); - let this_val_stake_pre = token::Amount::from_change( - *validator_stakes.get(validator).unwrap(), - ); + let this_val_stake_pre = token::Amount::from_change(this_val_stake_pre); + + let threshold = self.params.validator_stake_threshold; + if this_val_stake_pre < threshold && this_val_stake_post < threshold { + // Validator is already below-threshold and will remain there, so do + // nothing + debug_assert!(below_thresh_set.contains(validator)); + return; + } - match state { + match state_pre { ValidatorState::Consensus => { // println!("Validator initially in consensus"); // Remove from the prior stake @@ -2091,6 +2239,37 @@ impl AbstractPosState { consensus_set.remove(&this_val_stake_pre); } + // If posterior stake is below threshold, place into the + // below-threshold set + if this_val_stake_post < threshold { + below_thresh_set.insert(validator.clone()); + validator_states.insert( + validator.clone(), + ValidatorState::BelowThreshold, + ); + + // Promote the next below-cap validator if there is one + if let Some(mut max_below_cap) = below_cap_set.last_entry() + { + let max_below_cap_stake = *max_below_cap.key(); + let vals = max_below_cap.get_mut(); + let promoted_val = vals.pop_front().unwrap(); + // Remove the key if there's nothing left + if vals.is_empty() { + below_cap_set.remove(&max_below_cap_stake); + } + + consensus_set + .entry(max_below_cap_stake.0) + .or_default() + .push_back(promoted_val.clone()); + validator_states + .insert(promoted_val, ValidatorState::Consensus); + } + + return; + } + // If unbonding, check the max below-cap validator's state if we // need to do a swap if change < token::Change::default() { @@ -2146,6 +2325,17 @@ impl AbstractPosState { below_cap_set.remove(&this_val_stake_pre.into()); } + // If posterior stake is below threshold, place into the + // below-threshold set + if this_val_stake_post < threshold { + below_thresh_set.insert(validator.clone()); + validator_states.insert( + validator.clone(), + ValidatorState::BelowThreshold, + ); + return; + } + // If bonding, check the min consensus validator's state if we // need to do a swap if change >= token::Change::default() { @@ -2194,6 +2384,67 @@ impl AbstractPosState { .or_default() .push_back(validator.clone()); } + ValidatorState::BelowThreshold => { + // We know that this validator will be promoted into one of the + // higher sets, so first remove from the below-threshold set. + below_thresh_set.remove(validator); + + let num_consensus = + consensus_set.iter().fold(0, |sum, (_, validators)| { + sum + validators.len() as u64 + }); + if num_consensus < self.params.max_validator_slots { + // Place the validator directly into the consensus set + consensus_set + .entry(this_val_stake_post) + .or_default() + .push_back(validator.clone()); + validator_states + .insert(validator.clone(), ValidatorState::Consensus); + return; + } + // Determine which set to place the validator into + if let Some(mut min_consensus) = consensus_set.first_entry() { + // dbg!(&min_consensus); + let min_consensus_stake = *min_consensus.key(); + if this_val_stake_post > min_consensus_stake { + // Swap this validator with the max consensus + let vals = min_consensus.get_mut(); + let last_val = vals.pop_back().unwrap(); + // Remove the key if there's nothing left + if vals.is_empty() { + consensus_set.remove(&min_consensus_stake); + } + // Do the swap in the validator sets + below_cap_set + .entry(min_consensus_stake.into()) + .or_default() + .push_back(last_val.clone()); + consensus_set + .entry(this_val_stake_post) + .or_default() + .push_back(validator.clone()); + + // Change the validator states + validator_states.insert( + validator.clone(), + ValidatorState::Consensus, + ); + validator_states + .insert(last_val, ValidatorState::BelowCapacity); + } else { + // Place the validator into the below-capacity set + below_cap_set + .entry(this_val_stake_post.into()) + .or_default() + .push_back(validator.clone()); + validator_states.insert( + validator.clone(), + ValidatorState::BelowCapacity, + ); + } + } + } ValidatorState::Inactive => { panic!("unexpected state") } @@ -2228,10 +2479,10 @@ impl AbstractPosState { tracing::debug!( "Val {} stake at infraction {}", validator, - stake_at_infraction + stake_at_infraction.to_string_native(), ); - let mut total_rate = Decimal::ZERO; + let mut total_rate = Dec::zero(); for slash in slashes { debug_assert_eq!(slash.epoch, infraction_epoch); @@ -2253,7 +2504,7 @@ impl AbstractPosState { total_rate += rate; } - total_rate = cmp::min(total_rate, Decimal::ONE); + total_rate = cmp::min(total_rate, Dec::one()); tracing::debug!("Total rate: {}", total_rate); let mut total_unbonded = token::Amount::default(); @@ -2272,7 +2523,7 @@ impl AbstractPosState { for (start, unbond_amount) in unbond_records { tracing::debug!( "UnbondRecord: amount = {}, start_epoch {}", - &unbond_amount, + &unbond_amount.to_string_native(), &start ); if start <= infraction_epoch { @@ -2293,7 +2544,7 @@ impl AbstractPosState { }) .cloned() .fold( - BTreeMap::::new(), + BTreeMap::::new(), |mut acc, s| { let cur = acc.entry(s.epoch).or_default(); @@ -2318,7 +2569,7 @@ impl AbstractPosState { tracing::debug!( "Total unbonded (epoch {}) w slashing = {}", epoch, - total_unbonded + total_unbonded.to_string_native() ); } sum_post_bonds += self @@ -2336,7 +2587,7 @@ impl AbstractPosState { tracing::debug!( "Epoch {}\nLast slash = {}", self.epoch + offset, - last_slash + last_slash.to_string_native(), ); let mut recent_unbonds = token::Change::default(); let unbond_records = self @@ -2349,7 +2600,7 @@ impl AbstractPosState { for (start, unbond_amount) in unbond_records { tracing::debug!( "UnbondRecord: amount = {}, start_epoch {}", - &unbond_amount, + unbond_amount.to_string_native(), &start ); if start <= infraction_epoch { @@ -2370,7 +2621,7 @@ impl AbstractPosState { }) .cloned() .fold( - BTreeMap::::new(), + BTreeMap::::new(), |mut acc, s| { let cur = acc.entry(s.epoch).or_default(); @@ -2396,23 +2647,24 @@ impl AbstractPosState { tracing::debug!( "Total unbonded (offset {}) w slashing = {}", offset, - total_unbonded + total_unbonded.to_string_native() ); } tracing::debug!( "stake at infraction {}", - stake_at_infraction + stake_at_infraction.to_string_native(), ); - tracing::debug!("total unbonded {}", total_unbonded); - let this_slash = decimal_mult_i128( - total_rate, - stake_at_infraction - total_unbonded.change(), + tracing::debug!( + "total unbonded {}", + total_unbonded.to_string_native() ); + let this_slash = total_rate + * (stake_at_infraction - total_unbonded.change()); let diff_slashed_amount = last_slash - this_slash; tracing::debug!( "Offset {} diff_slashed_amount {}", offset, - diff_slashed_amount + diff_slashed_amount.to_string_native(), ); last_slash = this_slash; // total_unbonded = token::Amount::default(); @@ -2435,7 +2687,10 @@ impl AbstractPosState { .unwrap_or_default() - recent_unbonds; - tracing::debug!("\nUnslashable bonds = {}", sum_post_bonds); + tracing::debug!( + "\nUnslashable bonds = {}", + sum_post_bonds.to_string_native() + ); let validator_stake_at_offset = self .validator_stakes .entry(self.epoch + offset) @@ -2448,18 +2703,18 @@ impl AbstractPosState { tracing::debug!( "Val stake pre (epoch {}) = {}", self.epoch + offset, - validator_stake_at_offset + validator_stake_at_offset.to_string_native(), ); tracing::debug!( "Slashable stake at offset = {}", - slashable_stake_at_offset + slashable_stake_at_offset.to_string_native(), ); let change = cmp::max( -slashable_stake_at_offset, diff_slashed_amount, ); - tracing::debug!("Change = {}", change); + tracing::debug!("Change = {}", change.to_string_native()); *validator_stake_at_offset += change; for os in (offset + 1)..=self.params.pipeline_len { @@ -2481,7 +2736,7 @@ impl AbstractPosState { tracing::debug!( "New stake at epoch {} = {}", self.epoch + os, - offset_stake + offset_stake.to_string_native() ); } } @@ -2544,6 +2799,14 @@ impl AbstractPosState { None } + fn is_in_below_threshold(&self, validator: &Address, epoch: Epoch) -> bool { + self.below_threshold_set + .get(&epoch) + .unwrap() + .iter() + .any(|val| val == validator) + } + /// Find the sums of the bonds across all epochs fn bond_sums(&self) -> BTreeMap { self.bonds.iter().fold( @@ -2576,7 +2839,7 @@ impl AbstractPosState { } /// Compute the cubic slashing rate for the current epoch - fn cubic_slash_rate(&self) -> Decimal { + fn cubic_slash_rate(&self) -> Dec { let infraction_epoch = self.epoch - self.params.unbonding_len - 1_u64 @@ -2592,7 +2855,7 @@ impl AbstractPosState { let epoch_end = infraction_epoch + window_width; // Calculate cubic slashing rate with the abstract state - let mut vp_frac_sum = Decimal::default(); + let mut vp_frac_sum = Dec::zero(); for epoch in Epoch::iter_bounds_inclusive(epoch_start, epoch_end) { let consensus_stake = self.consensus_set.get(&epoch).unwrap().iter().fold( @@ -2604,7 +2867,7 @@ impl AbstractPosState { tracing::debug!( "Consensus stake in epoch {}: {}", epoch, - consensus_stake + consensus_stake.to_string_native() ); let processing_epoch = epoch @@ -2626,18 +2889,21 @@ impl AbstractPosState { "Val {} stake epoch {}: {}", &validator, epoch, - val_stake + val_stake.to_string_native(), ); - vp_frac_sum += Decimal::from(slashes.len()) - * Decimal::from(val_stake) - / Decimal::from(consensus_stake); + vp_frac_sum += Dec::from(slashes.len()) + * Dec::from(val_stake) + / Dec::from(consensus_stake); } } } - let vp_frac_sum = cmp::min(Decimal::ONE, vp_frac_sum); + let vp_frac_sum = cmp::min(Dec::one(), vp_frac_sum); tracing::debug!("vp_frac_sum: {}", vp_frac_sum); - cmp::min(dec!(9) * vp_frac_sum * vp_frac_sum, Decimal::ONE) + cmp::min( + Dec::new(9, 0).unwrap() * vp_frac_sum * vp_frac_sum, + Dec::one(), + ) } fn debug_validators(&self) { @@ -2669,8 +2935,8 @@ impl AbstractPosState { tracing::debug!( "Consensus val {}, stake {} ({}) - ({:?})", val, - u64::from(*amount), - deltas_stake, + amount.to_string_native(), + deltas_stake.to_string_native(), val_state ); debug_assert_eq!( @@ -2703,8 +2969,8 @@ impl AbstractPosState { tracing::debug!( "Below-cap val {}, stake {} ({}) - ({:?})", val, - u64::from(token::Amount::from(*amount)), - deltas_stake, + token::Amount::from(*amount).to_string_native(), + deltas_stake.to_string_native(), val_state ); debug_assert_eq!( @@ -2714,8 +2980,40 @@ impl AbstractPosState { debug_assert_eq!(*val_state, ValidatorState::BelowCapacity); } } + if max_bc > min_consensus { + println!( + "min_consensus = {}, max_bc = {}", + min_consensus.to_string_native(), + max_bc.to_string_native() + ); + } assert!(min_consensus >= max_bc); + for addr in self.below_threshold_set.get(&epoch).unwrap() { + let state = self + .validator_states + .get(&epoch) + .unwrap() + .get(addr) + .unwrap(); + + let stake = self + .validator_stakes + .get(&epoch) + .unwrap() + .get(addr) + .cloned() + .unwrap_or_default(); + tracing::debug!( + "Below-thresh val {}, stake {} - ({:?})", + addr, + stake.to_string_native(), + state + ); + + assert_eq!(*state, ValidatorState::BelowThreshold); + } + for addr in self .validator_states .get(&epoch) @@ -2724,9 +3022,10 @@ impl AbstractPosState { .cloned() .collect::>() { - if let (None, None) = ( + if let (None, None, false) = ( self.is_in_consensus_w_info(&addr, epoch), self.is_in_below_capacity_w_info(&addr, epoch), + self.is_in_below_threshold(&addr, epoch), ) { assert_eq!( self.validator_states @@ -2743,7 +3042,11 @@ impl AbstractPosState { .get(&addr) .cloned() .unwrap_or_default(); - tracing::debug!("Jailed val {}, stake {}", &addr, &stake); + tracing::debug!( + "Jailed val {}, stake {}", + &addr, + &stake.to_string_native() + ); } } } @@ -2818,7 +3121,8 @@ fn arb_self_bond( // Bond up to 10 tokens (10M micro units) to avoid overflows pub fn arb_bond_amount() -> impl Strategy { - (1_u64..10_000_000).prop_map(token::Amount::from) + (1_u64..10_000_000) + .prop_map(|val| token::Amount::from_uint(val, 0).unwrap()) } /// Arbitrary validator misbehavior @@ -2855,7 +3159,7 @@ fn arb_slash(state: &AbstractPosState) -> impl Strategy { } fn compute_amount_after_slashing( - slashes: &BTreeMap, + slashes: &BTreeMap, amount: token::Amount, unbonding_len: u64, cubic_slash_window_len: u64, @@ -2880,7 +3184,7 @@ fn compute_amount_after_slashing( computed_amounts.remove(idx); } computed_amounts.push(SlashedAmount { - amount: decimal_mult_amount(*slash_rate, updated_amount), + amount: *slash_rate * updated_amount, epoch: *infraction_epoch, }); } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index bf81ad2286..7625fdb7ce 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -16,14 +16,17 @@ use namada_core::ledger::storage_api::collections::{ }; use namada_core::ledger::storage_api::{self, StorageRead}; use namada_core::types::address::Address; +use namada_core::types::dec::Dec; use namada_core::types::key::common; use namada_core::types::storage::{Epoch, KeySeg}; use namada_core::types::token; +use namada_core::types::token::Amount; pub use rev_order::ReverseOrdTokenAmount; -use rust_decimal::prelude::{Decimal, ToPrimitive}; use crate::parameters::PosParams; +// TODO: replace `POS_MAX_DECIMAL_PLACES` with +// core::types::token::NATIVE_MAX_DECIMAL_PLACES?? const U64_MAX: u64 = u64::MAX; // TODO: add this to the spec @@ -122,7 +125,7 @@ pub type TotalDeltas = crate::epoched::EpochedDelta< /// Epoched validator commission rate pub type CommissionRates = - crate::epoched::Epoched; + crate::epoched::Epoched; /// Epoched validator's bonds pub type Bonds = crate::epoched::EpochedDelta< @@ -181,17 +184,17 @@ pub struct SlashedAmount { /// Commission rate and max commission rate change per epoch for a validator pub struct CommissionPair { /// Validator commission rate - pub commission_rate: Decimal, + pub commission_rate: Dec, /// Validator max commission rate change per epoch - pub max_commission_change_per_epoch: Decimal, + pub max_commission_change_per_epoch: Dec, } /// Epoched rewards products -pub type RewardsProducts = LazyMap; +pub type RewardsProducts = LazyMap; /// Consensus validator rewards accumulator (for tracking the fractional block /// rewards owed over the course of an epoch) -pub type RewardsAccumulator = LazyMap; +pub type RewardsAccumulator = LazyMap; // -------------------------------------------------------------------------------------------- @@ -215,9 +218,9 @@ pub struct GenesisValidator { /// A public key used for signing validator's consensus actions pub consensus_key: common::PublicKey, /// Commission rate charged on rewards for delegators (bounded inside 0-1) - pub commission_rate: Decimal, + pub commission_rate: Dec, /// Maximum change in commission rate permitted per epoch - pub max_commission_rate_change: Decimal, + pub max_commission_rate_change: Dec, } /// An update of the consensus and below-capacity validator set. @@ -236,7 +239,7 @@ pub struct ConsensusValidator { /// A public key used for signing validator's consensus actions pub consensus_key: common::PublicKey, /// Total bonded stake of the validator - pub bonded_stake: u64, + pub bonded_stake: token::Amount, } /// ID of a bond and/or an unbond. @@ -287,7 +290,8 @@ impl Display for WeightedValidator { write!( f, "{} with bonded stake {}", - self.address, self.bonded_stake + self.address, + self.bonded_stake.to_string_native() ) } } @@ -362,6 +366,9 @@ pub enum ValidatorState { /// A validator who does not have enough stake to be considered in the /// `Consensus` validator set but still may have active bonds and unbonds BelowCapacity, + /// A validator who has stake less than the `validator_stake_threshold` + /// parameter + BelowThreshold, /// A validator who is deactivated via a tx when a validator no longer /// wants to be one (not implemented yet) Inactive, @@ -392,7 +399,7 @@ pub struct Slash { /// A type of slashable event. pub r#type: SlashType, /// The cubic slashing rate for this validator - pub rate: Decimal, + pub rate: Dec, } /// Slashes applied to validator, to punish byzantine behavior by removing @@ -489,7 +496,7 @@ impl Display for BondId { impl SlashType { /// Get the slash rate applicable to the given slash type from the PoS /// parameters. - pub fn get_slash_rate(&self, params: &PosParams) -> Decimal { + pub fn get_slash_rate(&self, params: &PosParams) -> Dec { match self { SlashType::DuplicateVote => params.duplicate_vote_min_slash_rate, SlashType::LightClientAttack => { @@ -508,52 +515,13 @@ impl Display for SlashType { } } -/// Multiply a value of type Decimal with one of type u64 and then return the -/// truncated u64 -pub fn decimal_mult_u64(dec: Decimal, int: u64) -> u64 { - let prod = dec * Decimal::from(int); - // truncate the number to the floor - prod.to_u64().expect("Product is out of bounds") -} - -/// Multiply a value of type Decimal with one of type i128 and then return the -/// truncated i128 -pub fn decimal_mult_i128(dec: Decimal, int: i128) -> i128 { - let prod = dec * Decimal::from(int); - // truncate the number to the floor - prod.to_i128().expect("Product is out of bounds") -} - -/// Multiply a value of type Decimal with one of type i128 and then convert it -/// to an Amount type -pub fn mult_change_to_amount( - dec: Decimal, - change: token::Change, -) -> token::Amount { - let prod = dec * Decimal::from(change); - // truncate the number to the floor - token::Amount::from(prod.to_u64().expect("Product is out of bounds")) -} - -/// Multiply a value of type Decimal with one of type Amount and then return the -/// truncated Amount -pub fn decimal_mult_amount( - dec: Decimal, - amount: token::Amount, -) -> token::Amount { - let prod = dec * Decimal::from(amount); - // truncate the number to the floor - token::Amount::from(prod.to_u64().expect("Product is out of bounds")) -} - /// Calculate voting power in the tendermint context (which is stored as i64) /// from the number of tokens -pub fn into_tm_voting_power( - votes_per_token: Decimal, - tokens: impl Into, -) -> i64 { - let prod = decimal_mult_u64(votes_per_token, tokens.into()); - i64::try_from(prod).expect("Invalid voting power") +pub fn into_tm_voting_power(votes_per_token: Dec, tokens: Amount) -> i64 { + let pow = votes_per_token + * u128::try_from(tokens).expect("Voting power out of bounds"); + i64::try_from(pow.to_uint().expect("Cant fail")) + .expect("Invalid voting power") } #[cfg(test)] diff --git a/proof_of_stake/src/types/rev_order.rs b/proof_of_stake/src/types/rev_order.rs index 807795e10e..57619941d4 100644 --- a/proof_of_stake/src/types/rev_order.rs +++ b/proof_of_stake/src/types/rev_order.rs @@ -23,7 +23,7 @@ impl From for ReverseOrdTokenAmount { /// Invert the token amount fn invert(amount: token::Amount) -> token::Amount { - token::MAX_AMOUNT - amount + token::Amount::max_signed() - amount } impl KeySeg for ReverseOrdTokenAmount { @@ -46,15 +46,16 @@ impl KeySeg for ReverseOrdTokenAmount { impl std::fmt::Display for ReverseOrdTokenAmount { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) + f.write_str(&self.0.to_string_native()) } } impl std::str::FromStr for ReverseOrdTokenAmount { - type Err = ::Err; + type Err = token::AmountParseError; fn from_str(s: &str) -> Result { - let amount = token::Amount::from_str(s)?; + let amount = + token::Amount::from_str(s, token::NATIVE_MAX_DECIMAL_PLACES)?; Ok(Self(amount)) } } diff --git a/scripts/generator.sh b/scripts/generator.sh index 9c8a2de352..701f8fbabe 100755 --- a/scripts/generator.sh +++ b/scripts/generator.sh @@ -12,26 +12,6 @@ NAMADA_DIR="$(pwd)" export NAMADA_LEDGER_LOG_PATH="$(pwd)/vectors.json" export NAMADA_TX_LOG_PATH="$(pwd)/debugs.txt" -echo '{ - "content": { - "title": "TheTitle", - "authors": "test@test.com", - "discussions-to": "www.github.com/anoma/aip/1", - "created": "2022-03-10T08:54:37Z", - "license": "MIT", - "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "requires": "2" - }, - "author": "atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw", - "voting_start_epoch": 12, - "voting_end_epoch": 24, - "grace_epoch": 30, - "type": { "Default": "'"$NAMADA_DIR"'/wasm_for_tests/tx_no_op.wasm" } -} -' > valid_proposal.json - if [ "$#" -ne 1 ]; then echo "Illegal number of parameters" elif [ "$1" = "server" ]; then @@ -39,7 +19,7 @@ elif [ "$1" = "server" ]; then sed -i 's/^epochs_per_year = 31_536_000$/epochs_per_year = 262_800/' genesis/test-vectors-single-node.toml - NAMADA_GENESIS_FILE=$(cargo run --bin namadac -- --mode validator utils init-network --genesis-path genesis/test-vectors-single-node.toml --wasm-checksums-path wasm/checksums.json --chain-prefix e2e-test --unsafe-dont-encrypt --localhost --allow-duplicate-ip | grep 'Genesis file generated at ' | sed 's/^Genesis file generated at //') + NAMADA_GENESIS_FILE=$(cargo run --bin namadac -- utils init-network --genesis-path genesis/test-vectors-single-node.toml --wasm-checksums-path wasm/checksums.json --chain-prefix e2e-test --unsafe-dont-encrypt --localhost --allow-duplicate-ip | grep 'Genesis file generated at ' | sed 's/^Genesis file generated at //') rm genesis/test-vectors-single-node.toml @@ -51,16 +31,245 @@ elif [ "$1" = "server" ]; then cp $NAMADA_BASE_DIR/setup/other/wallet.toml $NAMADA_BASE_DIR/wallet.toml - cargo run --bin namadan -- --mode validator --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada/ ledger + cargo run --bin namadan -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada/ ledger elif [ "$1" = "client" ]; then echo > $NAMADA_TX_LOG_PATH echo $'[' > $NAMADA_LEDGER_LOG_PATH + + ALBERT_ADDRESS=$(cargo run --bin namadaw -- address find --alias albert | sed 's/^Found address Established: //') + + echo '{ + "author":"'$ALBERT_ADDRESS'", + "content":{ + "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors":"test@test.com", + "created":"2022-03-10T08:54:37Z", + "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to":"www.github.com/anoma/aip/1", + "license":"MIT", + "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires":"2", + "title":"TheTitle" + }, + "grace_epoch":30, + "type":{ + "Default":"'$NAMADA_DIR'/wasm_for_tests/tx_proposal_code.wasm" + }, + "voting_end_epoch":24, + "voting_start_epoch":12 +} +' > proposal_submission_valid_proposal.json + + echo '{ + "content": { + "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors": "test@test.com", + "created": "2022-03-10T08:54:37Z", + "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to": "www.github.com/anoma/aip/1", + "license": "MIT", + "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires": "2", + "title": "TheTitle" + }, + "author": "'$ALBERT_ADDRESS'", + "tally_epoch": 18, + "signature": { + "Ed25519": { + "R_bytes": [ + 113, + 196, + 231, + 134, + 101, + 191, + 75, + 17, + 245, + 19, + 50, + 231, + 183, + 80, + 162, + 38, + 108, + 72, + 72, + 2, + 116, + 112, + 121, + 33, + 197, + 67, + 64, + 116, + 21, + 250, + 196, + 121 + ], + "s_bytes": [ + 87, + 163, + 134, + 87, + 42, + 156, + 121, + 211, + 189, + 19, + 255, + 5, + 23, + 178, + 143, + 39, + 118, + 249, + 37, + 53, + 121, + 136, + 59, + 103, + 190, + 91, + 121, + 95, + 46, + 54, + 168, + 9 + ] + } + }, + "address": "'$ALBERT_ADDRESS'" +} +' > proposal_offline_proposal + + echo '{ + "author":"'$ALBERT_ADDRESS'", + "content":{ + "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors":"test@test.com", + "created":"2022-03-10T08:54:37Z", + "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to":"www.github.com/anoma/aip/1", + "license":"MIT", + "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires":"2", + "title":"TheTitle" + }, + "grace_epoch":18, + "type":{ + "Default":null + }, + "voting_end_epoch":9, + "voting_start_epoch":3 +}' > proposal_offline_valid_proposal.json + + echo '{ + "author":"'$ALBERT_ADDRESS'", + "content":{ + "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors":"test@test.com", + "created":"2022-03-10T08:54:37Z", + "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to":"www.github.com/anoma/aip/1", + "license":"MIT", + "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires":"2", + "title":"TheTitle" + }, + "grace_epoch":30, + "type":"ETHBridge", + "voting_end_epoch":24, + "voting_start_epoch":12 +}' > eth_governance_proposal_valid_proposal.json + + echo '{ + "author":"'$ALBERT_ADDRESS'", + "content":{ + "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors":"test@test.com", + "created":"2022-03-10T08:54:37Z", + "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to":"www.github.com/anoma/aip/1", + "license":"MIT", + "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires":"2", + "title":"TheTitle" + }, + "grace_epoch":30, + "type":"PGFCouncil", + "voting_end_epoch":24, + "voting_start_epoch":12 +}' > pgf_governance_proposal_valid_proposal.json + + # proposal_submission + + cargo run --bin namadac --features std -- --mode full bond --validator validator-0 --source Bertha --amount 900 --gas-amount 0 --gas-limit 0 --gas-token NAM --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Bertha --commission-rate 0.02 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + + PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path proposal_submission_valid_proposal.json --node 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') + + cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada --mode validator vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --signer validator-0 --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote nay --signer Bertha --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --signer Albert --node 127.0.0.1:27657 + + # proposal_offline + + cargo run --bin namadac --features std -- --mode full bond --validator validator-0 --source Albert --amount 900 --gas-amount 0 --gas-limit 0 --gas-token NAM --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Albert --commission-rate 0.05 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path proposal_offline_valid_proposal.json --offline --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --data-path proposal_offline_proposal --vote yay --signer Albert --offline --node 127.0.0.1:27657 + + # eth_governance_proposal + + cargo run --bin namadac --features std -- --mode full bond --validator validator-0 --source Bertha --amount 900 --gas-amount 0 --gas-limit 0 --gas-token NAM --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Bertha --commission-rate 0.07 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + + PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path eth_governance_proposal_valid_proposal.json --ledger-address 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id 0 --vote yay --eth '011586062748ba53bc53155e817ec1ea708de75878dcb9a5713bf6986d87fe14e7 fd34672ab5' --signer Bertha --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada --mode validator vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --eth '011586062748ba53bc53155e817ec1ea708de75878dcb9a5713bf6986d87fe14e7 fd34672ab5' --signer validator-0 --ledger-address 127.0.0.1:27657 + + # pgf_governance_proposal + + cargo run --bin namadac --features std -- --mode full bond --validator validator-0 --source Bertha --amount 900 --gas-amount 0 --gas-limit 0 --gas-token NAM --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Bertha --commission-rate 0.09 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + + PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path pgf_governance_proposal_valid_proposal.json --ledger-address 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') + + PROPOSAL_ID_1=$(cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path pgf_governance_proposal_valid_proposal.json --ledger-address 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') + + cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada --mode validator vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --pgf "$ALBERT_ADDRESS 1000" --signer validator-0 --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --pgf "$ALBERT_ADDRESS 900" --signer Bertha --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id $PROPOSAL_ID_1 --vote yay --pgf "$ALBERT_ADDRESS 900" --signer Bertha --ledger-address 127.0.0.1:27657 + + # non-proposal tests cargo run --bin namadac --features std -- transfer --source bertha --target christel --token btc --amount 23 --force --signing-key bertha-key --ledger-address 127.0.0.1:27657 cargo run --bin namadac --features std -- bond --validator bertha --amount 25 --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Bertha --commission-rate 0.11 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + cargo run --bin namadac --features std -- reveal-pk --public-key albert-key --force --ledger-address 127.0.0.1:27657 cargo run --bin namadac --features std -- update --code-path vp_user.wasm --address bertha --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 @@ -77,8 +286,6 @@ elif [ "$1" = "client" ]; then cargo run --bin namadac --features std -- ibc-transfer --source bertha --receiver christel --token btc --amount 24 --channel-id channel-141 --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 - #cargo run --bin namadac -- init-proposal --data-path valid_proposal.json --epoch 2 --force --signing-key bertha-key --ledger-address 127.0.0.1:27657 - cargo run --bin namadaw -- masp add --alias a_spending_key --value xsktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxcvedhsv --unsafe-dont-encrypt cargo run --bin namadaw -- masp add --alias b_spending_key --value xsktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7c2k4r7f7zu2yr5rjwr374unjjeuzrh6mquzy6grfdcnnu5clzaq2llqhr70a8yyx0p62aajqvrqjxrht3myuyypsvm725uyt5vm0fqzrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqqd82s0 --unsafe-dont-encrypt @@ -89,21 +296,29 @@ elif [ "$1" = "client" ]; then cargo run --bin namadaw -- masp add --alias bb_payment_address --value patest1vqe0vyxh6wmhahwa52gthgd6edgqxfmgyv8e94jtwn55mdvpvylcyqnp59595272qrz3zxn0ysg - cargo run --bin namadac --features std -- --mode full transfer --source albert --target aa_payment_address --token btc --amount 20 --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- transfer --source albert --target aa_payment_address --token btc --amount 20 --force --ledger-address 127.0.0.1:27657 - cargo run --bin namadac --features std -- --mode full transfer --source a_spending_key --target ab_payment_address --token btc --amount 7 --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- transfer --source a_spending_key --target ab_payment_address --token btc --amount 7 --force --ledger-address 127.0.0.1:27657 - until cargo run --bin namadac -- --mode full epoch --ledger-address 127.0.0.1:27657 | grep -m1 "Last committed epoch: 2" ; do sleep 10 ; done; + until cargo run --bin namadac -- epoch --ledger-address 127.0.0.1:27657 | grep -m1 "Last committed epoch: 2" ; do sleep 10 ; done; - cargo run --bin namadac --features std -- --mode full transfer --source a_spending_key --target bb_payment_address --token btc --amount 7 --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- transfer --source a_spending_key --target bb_payment_address --token btc --amount 7 --force --ledger-address 127.0.0.1:27657 - cargo run --bin namadac --features std -- --mode full transfer --source a_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- transfer --source a_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 - cargo run --bin namadac --features std -- --mode full transfer --source b_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- transfer --source b_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 + + rm proposal_submission_valid_proposal.json + + rm proposal_offline_proposal + + rm proposal_offline_valid_proposal.json + + rm eth_governance_proposal_valid_proposal.json + + rm pgf_governance_proposal_valid_proposal.json perl -0777 -i.original -pe 's/,\s*$//igs' $NAMADA_LEDGER_LOG_PATH echo $'\n]' >> $NAMADA_LEDGER_LOG_PATH fi - -rm valid_proposal.json diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 3b43d464bd..7cd1e6e1e0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -29,8 +29,8 @@ wasm-runtime = [ "namada_core/wasm-runtime", "loupe", "parity-wasm", - "pwasm-utils", "rayon", + "wasm-instrument", "wasmer-cache", "wasmer-compiler-singlepass", "wasmer-engine-dylib", @@ -85,7 +85,7 @@ namada-sdk = [ multicore = ["masp_proofs/multicore"] [dependencies] -namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign-verify"]} +namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign"]} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} async-std.workspace = true async-trait = {version = "0.1.51", optional = true} @@ -98,16 +98,17 @@ derivation-path.workspace = true derivative.workspace = true itertools.workspace = true loupe = {version = "0.1.3", optional = true} +masp_primitives.workspace = true +masp_proofs = { workspace = true, features = ["download-params"] } orion.workspace = true parity-wasm = {version = "0.45.0", features = ["sign_ext"], optional = true} paste.workspace = true proptest = {version = "1.2.0", optional = true} prost.workspace = true -pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} +rand = {version = "0.8", default-features = false, optional = true} +rand_core = {version = "0.6", default-features = false, optional = true} rayon = {version = "=1.5.3", optional = true} ripemd.workspace = true -rust_decimal.workspace = true -rust_decimal_macros.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true @@ -120,21 +121,18 @@ tiny-hderive.workspace = true tokio.workspace = true toml.workspace = true tracing.workspace = true -wasmer = {version = "=2.2.0", optional = true} -wasmer-cache = {version = "=2.2.0", optional = true} -wasmer-compiler-singlepass = {version = "=2.2.0", optional = true} -wasmer-engine-dylib = {version = "=2.2.0", optional = true} -wasmer-engine-universal = {version = "=2.2.0", optional = true} -wasmer-vm = {version = "2.2.0", optional = true} +wasm-instrument = {version = "0.4.0", features = ["sign_ext"], optional = true} +wasmer = {version = "=2.3.0", optional = true} +wasmer-cache = {version = "=2.3.0", optional = true} +wasmer-compiler-singlepass = {version = "=2.3.0", optional = true} +wasmer-engine-dylib = {version = "=2.3.0", optional = true} +wasmer-engine-universal = {version = "=2.3.0", optional = true} +wasmer-vm = {version = "2.3.0", optional = true} wasmparser.workspace = true -masp_primitives.workspace = true -masp_proofs = { workspace = true, features = ["download-params"] } -rand = {version = "0.8", default-features = false, optional = true} -rand_core = {version = "0.6", default-features = false, optional = true} zeroize.workspace = true [dev-dependencies] -namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign-verify", "testing", "ibc-mocks"]} +namada_core = {path = "../core", default-features = false, features = ["secp256k1-sign", "testing", "ibc-mocks"]} namada_test_utils = {path = "../test_utils"} assert_matches.workspace = true async-trait.workspace = true diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 58863756bd..e05e216db4 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -2,8 +2,8 @@ use std::path::PathBuf; use namada_core::types::chain::ChainId; +use namada_core::types::dec::Dec; use namada_core::types::time::DateTimeUtc; -use rust_decimal::Decimal; use zeroize::Zeroizing; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; @@ -96,12 +96,23 @@ pub struct TxTransfer { /// Transferred token address pub sub_prefix: Option, /// Transferred token amount - pub amount: token::Amount, + pub amount: InputAmount, /// Native token address pub native_token: C::NativeAddress, /// Path to the TX WASM code file pub tx_code_path: PathBuf, } +/// An amount read in by the cli +#[derive(Copy, Clone, Debug)] +pub enum InputAmount { + /// An amount whose representation has been validated + /// against the allowed representation in storage + Validated(token::DenominatedAmount), + /// The parsed amount read in from the cli. It has + /// not yet been validated against the allowed + /// representation in storage. + Unvalidated(token::DenominatedAmount), +} /// IBC transfer transaction arguments #[derive(Clone, Debug)] @@ -112,7 +123,7 @@ pub struct TxIbcTransfer { pub source: C::Address, /// Transfer target address pub receiver: String, - /// Transferred token address + /// Transferred token addres s pub token: C::Address, /// Transferred token address pub sub_prefix: Option, @@ -161,9 +172,9 @@ pub struct TxInitValidator { /// Protocol key pub protocol_key: Option, /// Commission rate - pub commission_rate: Decimal, + pub commission_rate: Dec, /// Maximum commission rate change - pub max_commission_rate_change: Decimal, + pub max_commission_rate_change: Dec, /// Path to the VP WASM code file pub validator_vp_code_path: PathBuf, /// Path to the TX WASM code file @@ -293,6 +304,8 @@ pub struct QueryTransfers { pub owner: Option, /// Address of a token pub token: Option, + /// sub-prefix if querying a multi-token + pub sub_prefix: Option, } /// Query PoS bond(s) @@ -319,13 +332,13 @@ pub struct QueryBondedStake { #[derive(Clone, Debug)] /// Commission rate change args -pub struct TxCommissionRateChange { +pub struct CommissionRateChange { /// Common tx arguments pub tx: Tx, /// Validator address (should be self) pub validator: C::Address, /// Value to which the tx changes the commission rate - pub rate: Decimal, + pub rate: Dec, /// Path to the TX WASM code file pub tx_code_path: PathBuf, } @@ -408,7 +421,7 @@ pub struct Tx { /// wallet. pub wallet_alias_force: bool, /// The amount being payed to include the transaction - pub fee_amount: token::Amount, + pub fee_amount: InputAmount, /// The token in which the fee is being paid pub fee_token: C::Address, /// The max amount of gas used to process tx diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index c8e74979bd..0da78bba53 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -11,7 +11,9 @@ use namada_core::ledger::storage_api::StorageRead; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; -use namada_core::types::token::{is_any_token_balance_key, Amount}; +use namada_core::types::token::{ + is_any_token_balance_key, is_any_token_or_multitoken_balance_key, Amount, +}; use super::Error; use crate::ledger::native_vp::CtxPreStorageRead; @@ -119,10 +121,12 @@ where dest: &Key, amount: Amount, ) -> Result<(), Self::Error> { - let src_owner = is_any_token_balance_key(src); + let src_owner = is_any_token_or_multitoken_balance_key(src); let mut src_bal = match src_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => Amount::max(), - Some(Address::Internal(InternalAddress::IbcBurn)) => { + Some([_, Address::Internal(InternalAddress::IbcMint)]) => { + Amount::max() + } + Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { unreachable!("Invalid transfer from IBC burn address") } _ => match self.read(src)? { @@ -135,7 +139,7 @@ where src_bal.spend(&amount); let dest_owner = is_any_token_balance_key(dest); let mut dest_bal = match dest_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { + Some([_, Address::Internal(InternalAddress::IbcMint)]) => { unreachable!("Invalid transfer to IBC mint address") } _ => match self.read(dest)? { diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 518e7d540a..7ea826c686 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -18,6 +18,7 @@ use namada_core::ledger::storage::write_log::StorageModification; use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; use namada_core::proto::Tx; use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::dec::Dec; use namada_core::types::storage::Key; use namada_proof_of_stake::read_pos_params; use thiserror::Error; @@ -224,19 +225,19 @@ pub fn get_dummy_header() -> crate::types::storage::Header { #[cfg(any(test, feature = "testing"))] pub fn get_dummy_genesis_validator() -> namada_proof_of_stake::types::GenesisValidator { - use rust_decimal::prelude::Decimal; - use crate::core::types::address::testing::established_address_1; use crate::types::key::testing::common_sk_from_simple_seed; use crate::types::token::Amount; let address = established_address_1(); - let tokens = Amount::whole(1); + let tokens = Amount::native_whole(1); let consensus_sk = common_sk_from_simple_seed(0); let consensus_key = consensus_sk.to_public(); - let commission_rate = Decimal::new(1, 1); - let max_commission_rate_change = Decimal::new(1, 1); + let commission_rate = + Dec::new(1, 1).expect("expected 0.1 to be a valid decimal"); + let max_commission_rate_change = + Dec::new(1, 1).expect("expected 0.1 to be a valid decimal"); namada_proof_of_stake::types::GenesisValidator { address, tokens, @@ -719,11 +720,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let ctx = Ctx::new( @@ -797,11 +794,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); @@ -939,11 +932,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); @@ -1051,11 +1040,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1151,11 +1136,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); @@ -1283,11 +1264,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1395,11 +1372,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1484,11 +1457,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1609,11 +1578,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1735,11 +1700,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1845,11 +1806,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1953,11 +1910,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2011,7 +1964,7 @@ mod tests { // init balance let sender = established_address_1(); let balance_key = balance_key(&nam(), &sender); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&balance_key, amount.try_to_vec().unwrap()) @@ -2094,11 +2047,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2273,11 +2222,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2421,11 +2366,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2476,7 +2417,7 @@ mod tests { // init the escrow balance let balance_key = balance_key(&nam(), &Address::Internal(InternalAddress::IbcEscrow)); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&balance_key, amount.try_to_vec().unwrap()) @@ -2574,11 +2515,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2629,7 +2566,7 @@ mod tests { // init the escrow balance let balance_key = balance_key(&nam(), &Address::Internal(InternalAddress::IbcEscrow)); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&balance_key, amount.try_to_vec().unwrap()) @@ -2727,11 +2664,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index da0043046f..18234abd35 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -97,11 +97,14 @@ where .filter(|k| { matches!( token::is_any_token_balance_key(k), - Some(Address::Internal( - InternalAddress::IbcEscrow - | InternalAddress::IbcBurn - | InternalAddress::IbcMint - )) + Some([ + _, + Address::Internal( + InternalAddress::IbcEscrow + | InternalAddress::IbcBurn + | InternalAddress::IbcMint + ) + ]) ) }) .cloned() @@ -126,7 +129,7 @@ where changes.insert(sub_prefix, change + this_change); } } - if changes.iter().all(|(_, c)| *c == 0) { + if changes.iter().all(|(_, c)| c.is_zero()) { return Ok(true); } else { return Err(Error::TokenTransfer( @@ -292,7 +295,7 @@ where )? .unwrap_or_default(); // the previous balance of the mint address should be the maximum - Amount::max().change() - post.change() + Amount::max_signed().change() - post.change() }; if change == amount.change() { @@ -343,7 +346,7 @@ where )? .unwrap_or_default(); // the previous balance of the mint address should be the maximum - Amount::max().change() - post.change() + Amount::max_signed().change() - post.change() }; if change == amount.change() { diff --git a/shared/src/ledger/inflation.rs b/shared/src/ledger/inflation.rs index 2779dc92f6..1c4850d2b1 100644 --- a/shared/src/ledger/inflation.rs +++ b/shared/src/ledger/inflation.rs @@ -2,9 +2,7 @@ //! proof-of-stake, providing liquity to shielded asset pools, and public goods //! funding. -use rust_decimal::prelude::ToPrimitive; -use rust_decimal::Decimal; -use rust_decimal_macros::dec; +use namada_core::types::dec::Dec; use crate::types::token; @@ -21,8 +19,8 @@ pub enum RewardsType { /// Holds the PD controller values that should be updated in storage #[allow(missing_docs)] pub struct ValsToUpdate { - pub locked_ratio: Decimal, - pub inflation: u64, + pub locked_ratio: Dec, + pub inflation: token::Amount, } /// PD controller used to dynamically adjust the rewards rates @@ -33,17 +31,17 @@ pub struct RewardsController { /// Total token supply pub total_tokens: token::Amount, /// PD target locked ratio - pub locked_ratio_target: Decimal, + pub locked_ratio_target: Dec, /// PD last locked ratio - pub locked_ratio_last: Decimal, + pub locked_ratio_last: Dec, /// Maximum reward rate - pub max_reward_rate: Decimal, + pub max_reward_rate: Dec, /// Last inflation amount pub last_inflation_amount: token::Amount, /// Nominal proportional gain - pub p_gain_nom: Decimal, + pub p_gain_nom: Dec, /// Nominal derivative gain - pub d_gain_nom: Decimal, + pub d_gain_nom: Dec, /// Number of epochs per year pub epochs_per_year: u64, } @@ -63,9 +61,13 @@ impl RewardsController { epochs_per_year, } = self; - let locked: Decimal = u64::from(locked_tokens).into(); - let total: Decimal = u64::from(total_tokens).into(); - let epochs_py: Decimal = (epochs_per_year).into(); + // Token amounts must be expressed in terms of the raw amount (namnam) + // to properly run the PD controller + let locked = + Dec::try_from(locked_tokens.raw_amount()).expect("Should not fail"); + let total = + Dec::try_from(total_tokens.raw_amount()).expect("Should not fail"); + let epochs_py: Dec = epochs_per_year.into(); let locked_ratio = locked / total; let max_inflation = total * max_reward_rate / epochs_py; @@ -76,16 +78,16 @@ impl RewardsController { let delta_error = locked_ratio_last - locked_ratio; let control_val = p_gain * error - d_gain * delta_error; - let last_inflation_amount = Decimal::from(last_inflation_amount); + let last_inflation_amount = Dec::from(last_inflation_amount); let new_inflation_amount = last_inflation_amount + control_val; - let inflation = if new_inflation_amount > max_inflation { + let inflation = if last_inflation_amount + control_val > max_inflation { max_inflation - } else if new_inflation_amount > dec!(0.0) { + } else if new_inflation_amount > Dec::zero() { new_inflation_amount } else { - dec!(0.0) + Dec::zero() }; - let inflation: u64 = inflation.to_u64().unwrap(); + let inflation = token::Amount::from(inflation); ValsToUpdate { locked_ratio, diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 3575695454..7ae4f142bb 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -14,6 +14,9 @@ use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; use itertools::Either; use masp_primitives::asset_type::AssetType; +#[cfg(feature = "mainnet")] +use masp_primitives::consensus::MainNetwork; +#[cfg(not(feature = "mainnet"))] use masp_primitives::consensus::TestNetwork; use masp_primitives::convert::AllowedConversion; use masp_primitives::ff::PrimeField; @@ -47,6 +50,7 @@ use masp_proofs::bellman::groth16::PreparedVerifyingKey; use masp_proofs::bls12_381::Bls12; use masp_proofs::prover::LocalTxProver; use masp_proofs::sapling::SaplingVerificationContext; +use namada_core::types::token::{Change, MaspDenom, TokenAddress}; use namada_core::types::transaction::AffineCurve; #[cfg(feature = "masp-tx-gen")] use rand_core::{CryptoRng, OsRng, RngCore}; @@ -54,8 +58,10 @@ use ripemd::Digest as RipemdDigest; #[cfg(feature = "masp-tx-gen")] use sha2::Digest; +use crate::ledger::args::InputAmount; use crate::ledger::queries::Client; -use crate::ledger::rpc::query_storage_value; +use crate::ledger::rpc::{query_conversion, query_storage_value}; +use crate::ledger::tx::decode_component; use crate::ledger::{args, rpc}; use crate::proto::Tx; use crate::tendermint_rpc::query::Query; @@ -381,24 +387,17 @@ pub fn find_valid_diversifier( /// Determine if using the current note would actually bring us closer to our /// target pub fn is_amount_required(src: Amount, dest: Amount, delta: Amount) -> bool { - if delta > Amount::zero() { - let gap = dest - src; - for (asset_type, value) in gap.components() { - if *value > 0 && delta[asset_type] > 0 { - return true; - } + let gap = dest - src; + for (asset_type, value) in gap.components() { + if *value >= 0 && delta[asset_type] >= 0 { + return true; } } false } -/// An extension of Option's cloned method for pair types -fn cloned_pair((a, b): (&T, &U)) -> (T, U) { - (a.clone(), b.clone()) -} - /// Errors that can occur when trying to retrieve pinned transaction -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Copy, Clone)] pub enum PinnedBalanceError { /// No transaction has yet been pinned to the given payment address NoTransactionPinned, @@ -406,15 +405,127 @@ pub enum PinnedBalanceError { InvalidViewingKey, } +// #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +// pub struct MaspAmount { +// pub asset: TokenAddress, +// pub amount: token::Amount, +// } + +/// a masp change +#[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] +pub struct MaspChange { + /// the token address + pub asset: TokenAddress, + /// the change in the token + pub change: token::Change, +} + +/// a masp amount +#[derive( + BorshSerialize, BorshDeserialize, Debug, Clone, Default, PartialEq, Eq, +)] +pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); + +impl std::ops::Deref for MaspAmount { + type Target = HashMap<(Epoch, TokenAddress), token::Change>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for MaspAmount { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl std::ops::Add for MaspAmount { + type Output = MaspAmount; + + fn add(mut self, mut rhs: MaspAmount) -> Self::Output { + for (key, value) in rhs.drain() { + self.entry(key) + .and_modify(|val| *val += value) + .or_insert(value); + } + self.retain(|_, v| !v.is_zero()); + self + } +} + +impl std::ops::AddAssign for MaspAmount { + fn add_assign(&mut self, amount: MaspAmount) { + *self = self.clone() + amount + } +} + +// please stop copying and pasting make a function +impl std::ops::Sub for MaspAmount { + type Output = MaspAmount; + + fn sub(mut self, mut rhs: MaspAmount) -> Self::Output { + for (key, value) in rhs.drain() { + self.entry(key) + .and_modify(|val| *val -= value) + .or_insert(-value); + } + self.0.retain(|_, v| !v.is_zero()); + self + } +} + +impl std::ops::SubAssign for MaspAmount { + fn sub_assign(&mut self, amount: MaspAmount) { + *self = self.clone() - amount + } +} + +impl std::ops::Mul for MaspAmount { + type Output = Self; + + fn mul(mut self, rhs: Change) -> Self::Output { + for (_, value) in self.iter_mut() { + *value = *value * rhs + } + self + } +} + +impl<'a> From<&'a MaspAmount> for Amount { + fn from(masp_amount: &'a MaspAmount) -> Amount { + let mut res = Amount::zero(); + for ((epoch, key), val) in masp_amount.iter() { + for denom in MaspDenom::iter() { + let asset = make_asset_type( + Some(*epoch), + &key.address, + &key.sub_prefix, + denom, + ); + res += Amount::from_pair(asset, denom.denominate_i128(val)) + .unwrap(); + } + } + res + } +} + +impl From for Amount { + fn from(amt: MaspAmount) -> Self { + Self::from(&amt) + } +} + /// Represents the amount used of different conversions pub type Conversions = - HashMap, i64)>; + HashMap, i128)>; /// Represents the changes that were made to a list of transparent accounts -pub type TransferDelta = HashMap>; +pub type TransferDelta = HashMap; /// Represents the changes that were made to a list of shielded accounts -pub type TransactionDelta = HashMap; +pub type TransactionDelta = HashMap; /// Represents the current state of the shielded pool from the perspective of /// the chosen viewing keys. @@ -447,7 +558,8 @@ pub struct ShieldedContext { /// The set of note positions that have been spent pub spents: HashSet, /// Maps asset types to their decodings - pub asset_types: HashMap, + pub asset_types: + HashMap, MaspDenom, Epoch)>, /// Maps note positions to their corresponding viewing keys pub vk_map: HashMap, } @@ -560,7 +672,9 @@ impl ShieldedContext { while tx_ctx.last_txidx != self.last_txidx { if let Some(((height, idx), (epoch, tx, stx))) = tx_iter.next() { - tx_ctx.scan_tx(*height, *idx, *epoch, tx, stx); + tx_ctx + .scan_tx(client, *height, *idx, *epoch, tx, stx) + .await; } else { break; } @@ -576,7 +690,7 @@ impl ShieldedContext { // Now that we possess the unspent notes corresponding to both old and // new keys up until tx_pos, proceed to scan the new transactions. for ((height, idx), (epoch, tx, stx)) in &mut tx_iter { - self.scan_tx(*height, *idx, *epoch, tx, stx); + self.scan_tx(client, *height, *idx, *epoch, tx, stx).await; } } @@ -631,8 +745,9 @@ impl ShieldedContext { /// we have spent are updated. The witness map is maintained to make it /// easier to construct note merkle paths in other code. See /// https://zips.z.cash/protocol/protocol.pdf#scan - pub fn scan_tx( + pub async fn scan_tx( &mut self, + client: &U::C, height: BlockHeight, index: TxIndex, epoch: Epoch, @@ -663,7 +778,9 @@ impl ShieldedContext { self.witness_map.insert(note_pos, witness); // Let's try to see if any of our viewing keys can decrypt latest // note - for (vk, notes) in self.pos_map.iter_mut() { + let mut pos_map = HashMap::new(); + std::mem::swap(&mut pos_map, &mut self.pos_map); + for (vk, notes) in pos_map.iter_mut() { let decres = try_sapling_note_decryption::<_, OutputDescription<<::SaplingAuth as masp_primitives::transaction::components::sapling::Authorization>::Proof>>( &NETWORK, 1.into(), @@ -686,16 +803,25 @@ impl ShieldedContext { // Note the account changes let balance = transaction_delta .entry(*vk) - .or_insert_with(Amount::zero); - *balance += - Amount::from_nonnegative(note.asset_type, note.value) + .or_insert_with(MaspAmount::default); + *balance += self + .decode_all_amounts( + client, + Amount::from_nonnegative( + note.asset_type, + note.value, + ) .expect( "found note with invalid value or asset type", - ); + ), + ) + .await; + self.vk_map.insert(note_pos, *vk); break; } } + std::mem::swap(&mut pos_map, &mut self.pos_map); } // Cancel out those of our notes that have been spent for ss in shielded @@ -709,26 +835,38 @@ impl ShieldedContext { // Note the account changes let balance = transaction_delta .entry(self.vk_map[note_pos]) - .or_insert_with(Amount::zero); + .or_insert_with(MaspAmount::default); let note = self.note_map[note_pos]; - *balance -= - Amount::from_nonnegative(note.asset_type, note.value) - .expect("found note with invalid value or asset type"); + *balance -= self + .decode_all_amounts( + client, + Amount::from_nonnegative(note.asset_type, note.value) + .expect( + "found note with invalid value or asset type", + ), + ) + .await; } } // Record the changes to the transparent accounts - let transparent_delta = - Amount::from_nonnegative(tx.token.clone(), u64::from(tx.amount)) - .expect("invalid value for amount"); let mut transfer_delta = TransferDelta::new(); - transfer_delta - .insert(tx.source.clone(), Amount::zero() - &transparent_delta); - transfer_delta.insert(tx.target.clone(), transparent_delta); + let token_addr = TokenAddress { + address: tx.token.clone(), + sub_prefix: tx.sub_prefix.clone(), + }; + transfer_delta.insert( + tx.source.clone(), + MaspChange { + asset: token_addr, + change: -tx.amount.amount.change(), + }, + ); + self.last_txidx += 1; + self.delta_map.insert( (height, index), (epoch, transfer_delta, transaction_delta), ); - self.last_txidx += 1; } /// Summarize the effects on shielded and transparent accounts of each @@ -745,7 +883,11 @@ impl ShieldedContext { /// Compute the total unspent notes associated with the viewing key in the /// context. If the key is not in the context, then we do not know the /// balance and hence we return None. - pub fn compute_shielded_balance(&self, vk: &ViewingKey) -> Option { + pub async fn compute_shielded_balance( + &mut self, + client: &U::C, + vk: &ViewingKey, + ) -> Option { // Cannot query the balance of a key that's not in the map if !self.pos_map.contains_key(vk) { return None; @@ -766,7 +908,7 @@ impl ShieldedContext { .expect("found note with invalid value or asset type"); } } - Some(val_acc) + Some(self.decode_all_amounts(client, val_acc).await) } /// Query the ledger for the decoding of the given asset type and cache it @@ -775,16 +917,23 @@ impl ShieldedContext { &mut self, client: &U::C, asset_type: AssetType, - ) -> Option<(Address, Epoch)> { + ) -> Option<(Address, Option, MaspDenom, Epoch)> { // Try to find the decoding in the cache if let decoded @ Some(_) = self.asset_types.get(&asset_type) { return decoded.cloned(); } // Query for the ID of the last accepted transaction - let (addr, ep, _conv, _path): (Address, _, Amount, MerklePath) = - rpc::query_conversion(client, asset_type).await?; - self.asset_types.insert(asset_type, (addr.clone(), ep)); - Some((addr, ep)) + let (addr, sub_prefix, denom, ep, _conv, _path): ( + Address, + Option, + MaspDenom, + _, + Amount, + MerklePath, + ) = rpc::query_conversion(client, asset_type).await?; + self.asset_types + .insert(asset_type, (addr.clone(), sub_prefix.clone(), denom, ep)); + Some((addr, sub_prefix, denom, ep)) } /// Query the ledger for the conversion that is allowed for the given asset @@ -794,19 +943,17 @@ impl ShieldedContext { client: &U::C, asset_type: AssetType, conversions: &'a mut Conversions, - ) -> Option<&'a mut (AllowedConversion, MerklePath, i64)> { - match conversions.entry(asset_type) { - Entry::Occupied(conv_entry) => Some(conv_entry.into_mut()), - Entry::Vacant(conv_entry) => { - // Query for the ID of the last accepted transaction - let (addr, ep, conv, path): (Address, _, _, _) = - rpc::query_conversion(client, asset_type).await?; - self.asset_types.insert(asset_type, (addr, ep)); + ) { + if let Entry::Vacant(conv_entry) = conversions.entry(asset_type) { + // Query for the ID of the last accepted transaction + if let Some((addr, sub_prefix, denom, ep, conv, path)) = + query_conversion(client, asset_type).await + { + self.asset_types + .insert(asset_type, (addr, sub_prefix, denom, ep)); // If the conversion is 0, then we just have a pure decoding - if conv == Amount::zero() { - None - } else { - Some(conv_entry.insert((Amount::into(conv), path, 0))) + if conv != Amount::zero() { + conv_entry.insert((conv.into(), path, 0)); } } } @@ -821,20 +968,20 @@ impl ShieldedContext { client: &U::C, vk: &ViewingKey, target_epoch: Epoch, - ) -> Option { + ) -> Option { // First get the unexchanged balance - if let Some(balance) = self.compute_shielded_balance(vk) { - // And then exchange balance into current asset types - Some( - self.compute_exchanged_amount( + if let Some(balance) = self.compute_shielded_balance(client, vk).await { + let exchanged_amount = self + .compute_exchanged_amount( client, balance, target_epoch, HashMap::new(), ) .await - .0, - ) + .0; + // And then exchange balance into current asset types + Some(self.decode_all_amounts(client, exchanged_amount).await) } else { None } @@ -845,23 +992,36 @@ impl ShieldedContext { /// conversion used, the conversions are applied to the given input, and /// the trace amount that could not be converted is moved from input to /// output. - fn apply_conversion( + #[allow(clippy::too_many_arguments)] + async fn apply_conversion( + &mut self, + client: &U::C, conv: AllowedConversion, - asset_type: AssetType, - value: i64, - usage: &mut i64, - input: &mut Amount, - output: &mut Amount, + asset_type: (Epoch, TokenAddress, MaspDenom), + value: i128, + usage: &mut i128, + input: &mut MaspAmount, + output: &mut MaspAmount, ) { + // we do not need to convert negative values + if value <= 0 { + return; + } // If conversion if possible, accumulate the exchanged amount let conv: Amount = conv.into(); // The amount required of current asset to qualify for conversion - let threshold = -conv[&asset_type]; + let masp_asset = make_asset_type( + Some(asset_type.0), + &asset_type.1.address, + &asset_type.1.sub_prefix, + asset_type.2, + ); + let threshold = -conv[&masp_asset]; if threshold == 0 { eprintln!( "Asset threshold of selected conversion for asset type {} is \ 0, this is a bug, please report it.", - asset_type + masp_asset ); } // We should use an amount of the AllowedConversion that almost @@ -869,11 +1029,17 @@ impl ShieldedContext { let required = value / threshold; // Forget about the trace amount left over because we cannot // realize its value - let trace = Amount::from_pair(asset_type, value % threshold).unwrap(); + let trace = MaspAmount(HashMap::from([( + (asset_type.0, asset_type.1), + Change::from(value % threshold), + )])); // Record how much more of the given conversion has been used *usage += required; // Apply the conversions to input and move the trace amount to output - *input += conv * required - &trace; + *input += self + .decode_all_amounts(client, conv.clone() * required) + .await + - trace.clone(); *output += trace; } @@ -884,77 +1050,106 @@ impl ShieldedContext { pub async fn compute_exchanged_amount( &mut self, client: &U::C, - mut input: Amount, + mut input: MaspAmount, target_epoch: Epoch, mut conversions: Conversions, ) -> (Amount, Conversions) { // Where we will store our exchanged value - let mut output = Amount::zero(); + let mut output = MaspAmount::default(); // Repeatedly exchange assets until it is no longer possible - while let Some((asset_type, value)) = - input.components().next().map(cloned_pair) + while let Some(((asset_epoch, token_addr), value)) = input.iter().next() { - let target_asset_type = self - .decode_asset_type(client, asset_type) - .await - .map(|(addr, _epoch)| make_asset_type(target_epoch, &addr)) - .unwrap_or(asset_type); - let at_target_asset_type = asset_type == target_asset_type; - if let (Some((conv, _wit, usage)), false) = ( - self.query_allowed_conversion( - client, - asset_type, - &mut conversions, - ) - .await, - at_target_asset_type, - ) { - println!( - "converting current asset type to latest asset type..." + let value = *value; + let asset_epoch = *asset_epoch; + let token_addr = token_addr.clone(); + for denom in MaspDenom::iter() { + let target_asset_type = make_asset_type( + Some(target_epoch), + &token_addr.address, + &token_addr.sub_prefix, + denom, ); - // Not at the target asset type, not at the latest asset type. - // Apply conversion to get from current asset type to the latest - // asset type. - Self::apply_conversion( - conv.clone(), - asset_type, - value, - usage, - &mut input, - &mut output, + let asset_type = make_asset_type( + Some(asset_epoch), + &token_addr.address, + &token_addr.sub_prefix, + denom, ); - } else if let (Some((conv, _wit, usage)), false) = ( + let at_target_asset_type = target_epoch == asset_epoch; + + let denom_value = denom.denominate_i128(&value); self.query_allowed_conversion( client, target_asset_type, &mut conversions, ) - .await, - at_target_asset_type, - ) { - println!( - "converting latest asset type to target asset type..." - ); - // Not at the target asset type, yes at the latest asset type. - // Apply inverse conversion to get from latest asset type to - // the target asset type. - Self::apply_conversion( - conv.clone(), + .await; + self.query_allowed_conversion( + client, asset_type, - value, - usage, - &mut input, - &mut output, - ); - } else { - // At the target asset type. Then move component over to output. - let comp = input.project(asset_type); - output += ∁ - // Strike from input to avoid repeating computation - input -= comp; + &mut conversions, + ) + .await; + if let (Some((conv, _wit, usage)), false) = + (conversions.get_mut(&asset_type), at_target_asset_type) + { + println!( + "converting current asset type to latest asset type..." + ); + // Not at the target asset type, not at the latest asset + // type. Apply conversion to get from + // current asset type to the latest + // asset type. + self.apply_conversion( + client, + conv.clone(), + (asset_epoch, token_addr.clone(), denom), + denom_value, + usage, + &mut input, + &mut output, + ) + .await; + } else if let (Some((conv, _wit, usage)), false) = ( + conversions.get_mut(&target_asset_type), + at_target_asset_type, + ) { + println!( + "converting latest asset type to target asset type..." + ); + // Not at the target asset type, yet at the latest asset + // type. Apply inverse conversion to get + // from latest asset type to the target + // asset type. + self.apply_conversion( + client, + conv.clone(), + (asset_epoch, token_addr.clone(), denom), + denom_value, + usage, + &mut input, + &mut output, + ) + .await; + } else { + // At the target asset type. Then move component over to + // output. + let mut comp = MaspAmount::default(); + comp.insert( + (asset_epoch, token_addr.clone()), + denom_value.into(), + ); + for ((e, key), val) in input.iter() { + if *key == token_addr && *e == asset_epoch { + comp.insert((*e, key.clone()), *val); + } + } + output += comp.clone(); + input -= comp; + } } } - (output, conversions) + (output.into(), conversions) } /// Collect enough unspent notes in this context to exceed the given amount @@ -994,10 +1189,11 @@ impl ShieldedContext { // The amount contributed by this note before conversion let pre_contr = Amount::from_pair(note.asset_type, note.value) .expect("received note has invalid value or asset type"); + let input = self.decode_all_amounts(client, pre_contr).await; let (contr, proposed_convs) = self .compute_exchanged_amount( client, - pre_contr, + input, target_epoch, conversions.clone(), ) @@ -1089,7 +1285,6 @@ impl ShieldedContext { .expect( "found note with invalid value or asset type", ); - break; } _ => {} } @@ -1107,17 +1302,21 @@ impl ShieldedContext { client: &U::C, owner: PaymentAddress, viewing_key: &ViewingKey, - ) -> Result<(Amount, Epoch), PinnedBalanceError> { + ) -> Result<(MaspAmount, Epoch), PinnedBalanceError> { // Obtain the balance that will be exchanged let (amt, ep) = Self::compute_pinned_balance(client, owner, viewing_key).await?; + println!("Pinned balance: {:?}", amt); + // Establish connection with which to do exchange rate queries + let amount = self.decode_all_amounts(client, amt).await; + println!("Decoded pinned balance: {:?}", amount); // Finally, exchange the balance to the transaction's epoch - Ok(( - self.compute_exchanged_amount(client, amt, ep, HashMap::new()) - .await - .0, - ep, - )) + let computed_amount = self + .compute_exchanged_amount(client, amount, ep, HashMap::new()) + .await + .0; + println!("Exchanged amount: {:?}", computed_amount); + Ok((self.decode_all_amounts(client, computed_amount).await, ep)) } /// Convert an amount whose units are AssetTypes to one whose units are @@ -1128,15 +1327,25 @@ impl ShieldedContext { client: &U::C, amt: Amount, target_epoch: Epoch, - ) -> Amount
{ - let mut res = Amount::zero(); + ) -> HashMap { + let mut res = HashMap::new(); for (asset_type, val) in amt.components() { // Decode the asset type let decoded = self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count match decoded { - Some((addr, epoch)) if epoch == target_epoch => { - res += &Amount::from_pair(addr, *val).unwrap() + Some(asset_type @ (_, _, _, epoch)) + if epoch == target_epoch => + { + decode_component( + asset_type, + *val, + &mut res, + |address, sub_prefix, _| TokenAddress { + address, + sub_prefix, + }, + ); } _ => {} } @@ -1150,17 +1359,31 @@ impl ShieldedContext { &mut self, client: &U::C, amt: Amount, - ) -> Amount<(Address, Epoch)> { - let mut res = Amount::zero(); + ) -> MaspAmount { + let mut res: HashMap<(Epoch, TokenAddress), Change> = + HashMap::default(); for (asset_type, val) in amt.components() { // Decode the asset type - let decoded = self.decode_asset_type(client, *asset_type).await; - // Only assets with the target timestamp count - if let Some((addr, epoch)) = decoded { - res += &Amount::from_pair((addr, epoch), *val).unwrap() + if let Some(decoded) = + self.decode_asset_type(client, *asset_type).await + { + decode_component( + decoded, + *val, + &mut res, + |address, sub_prefix, epoch| { + ( + epoch, + TokenAddress { + address, + sub_prefix, + }, + ) + }, + ) } } - res + MaspAmount(res) } /// Make shielded components to embed within a Transfer object. If no @@ -1174,7 +1397,7 @@ impl ShieldedContext { pub async fn gen_shielded_transfer( &mut self, client: &U::C, - args: args::TxTransfer, + args: &args::TxTransfer, shielded_gas: bool, ) -> Result< Option<( @@ -1206,27 +1429,40 @@ impl ShieldedContext { let epoch = rpc::query_epoch(client).await; // Context required for storing which notes are in the source's // possesion - let amt: u64 = args.amount.into(); let memo = MemoBytes::empty(); // Now we build up the transaction within this object - let mut builder = Builder::<_, OsRng>::new(NETWORK, 1.into()); + let mut builder = Builder::::new(NETWORK, 1.into()); + + // break up a transfer into a number of transfers with suitable + // denominations + let InputAmount::Validated(amt) = args.amount else { + unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") + }; // Convert transaction amount into MASP types - let (asset_type, amount) = - convert_amount(epoch, &args.token, args.amount); - // The fee to be paid for the transaction - let tx_fee; + let (asset_types, amount) = convert_amount( + epoch, + &args.token, + &args.sub_prefix.as_ref(), + amt.amount, + ); + let tx_fee = // If there are shielded inputs if let Some(sk) = spending_key { + let InputAmount::Validated(fee) = args.tx.fee_amount else { + unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") + }; // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used - let (_, fee) = - convert_amount(epoch, &args.tx.fee_token, args.tx.fee_amount); - tx_fee = fee.clone(); - // If the gas is coming from the shielded pool, then our shielded - // inputs must also cover the gas fee - let required_amt = if shielded_gas { amount + fee } else { amount }; + let (_, shielded_fee) = + convert_amount(epoch, &args.tx.fee_token, &None, fee.amount); + let required_amt = if shielded_gas { + amount + shielded_fee.clone() + } else { + amount + }; + // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = self .collect_unspent_notes( @@ -1244,20 +1480,17 @@ impl ShieldedContext { } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { - if *value > 0 { - builder - .add_sapling_convert( - conv.clone(), - *value as u64, - wit.clone(), - ) - .map_err(builder::Error::SaplingBuild)?; + if value.is_positive() { + builder.add_sapling_convert( + conv.clone(), + *value as u64, + wit.clone(), + ) + .map_err(builder::Error::SaplingBuild)?; } } + shielded_fee } else { - // No transfer fees come from the shielded transaction for non-MASP - // sources - tx_fee = Amount::zero(); // We add a dummy UTXO to our transaction, but only the source of // the parent Transfer object is used to validate fund // availability @@ -1271,27 +1504,36 @@ impl ShieldedContext { source_enc.as_ref(), )); let script = TransparentAddress(hash.into()); - builder - .add_transparent_input(TxOut { - asset_type, - value: amt.try_into().expect("supplied amount too large"), - address: script, - }) - .map_err(builder::Error::TransparentBuild)?; - } + for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) { + builder + .add_transparent_input(TxOut { + asset_type: *asset_type, + value: denom.denominate(&amt) as i128, + address: script, + }) + .map_err(builder::Error::TransparentBuild)?; + } + // No transfer fees come from the shielded transaction for non-MASP + // sources + Amount::zero() + }; + // Now handle the outputs of this transaction // If there is a shielded output if let Some(pa) = payment_address { let ovk_opt = spending_key.map(|x| x.expsk.ovk); - builder - .add_sapling_output( - ovk_opt, - pa.into(), - asset_type, - amt, - memo.clone(), - ) - .map_err(builder::Error::SaplingBuild)?; + for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) + { + builder + .add_sapling_output( + ovk_opt, + pa.into(), + *asset_type, + denom.denominate(&amt), + memo.clone(), + ) + .map_err(builder::Error::SaplingBuild)?; + } } else { // Embed the transparent target address into the shielded // transaction so that it can be signed @@ -1304,13 +1546,19 @@ impl ShieldedContext { let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest( target_enc.as_ref(), )); - builder - .add_transparent_output( - &TransparentAddress(hash.into()), - asset_type, - amt.try_into().expect("supplied amount too large"), - ) - .map_err(builder::Error::TransparentBuild)?; + for (denom, asset_type) in MaspDenom::iter().zip(asset_types.iter()) + { + let vout = denom.denominate(&amt); + if vout != 0 { + builder + .add_transparent_output( + &TransparentAddress(hash.into()), + *asset_type, + vout as i128, + ) + .map_err(builder::Error::TransparentBuild)?; + } + } } // Now add outputs representing the change from this payment @@ -1449,17 +1697,18 @@ impl ShieldedContext { } // Describe how a Transfer simply subtracts from one // account and adds the same to another - let mut delta = TransferDelta::default(); - let tfer_delta = Amount::from_nonnegative( - transfer.token.clone(), - u64::from(transfer.amount), - ) - .expect("invalid value for amount"); - delta.insert( - transfer.source, - Amount::zero() - &tfer_delta, - ); - delta.insert(transfer.target, tfer_delta); + + let delta = TransferDelta::from([( + transfer.source.clone(), + MaspChange { + asset: TokenAddress { + address: transfer.token.clone(), + sub_prefix: transfer.sub_prefix.clone(), + }, + change: -transfer.amount.amount.change(), + }, + )]); + // No shielded accounts are affected by this // Transfer transfers.insert( @@ -1495,11 +1744,36 @@ fn extract_payload( } /// Make asset type corresponding to given address and epoch -pub fn make_asset_type(epoch: Epoch, token: &Address) -> AssetType { +pub fn make_asset_type( + epoch: Option, + token: &Address, + sub_prefix: &Option, + denom: MaspDenom, +) -> AssetType { // Typestamp the chosen token with the current epoch - let token_bytes = (token, epoch.0) - .try_to_vec() - .expect("token should serialize"); + let token_bytes = match epoch { + None => ( + token, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + ) + .try_to_vec() + .expect("token should serialize"), + Some(epoch) => ( + token, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + epoch.0, + ) + .try_to_vec() + .expect("token should serialize"), + }; // Generate the unique asset identifier from the unique token address AssetType::new(token_bytes.as_ref()).expect("unable to create asset type") } @@ -1508,13 +1782,24 @@ pub fn make_asset_type(epoch: Epoch, token: &Address) -> AssetType { fn convert_amount( epoch: Epoch, token: &Address, + sub_prefix: &Option<&String>, val: token::Amount, -) -> (AssetType, Amount) { - let asset_type = make_asset_type(epoch, token); - // Combine the value and unit into one amount - let amount = Amount::from_nonnegative(asset_type, u64::from(val)) - .expect("invalid value for amount"); - (asset_type, amount) +) -> ([AssetType; 4], Amount) { + let mut amount = Amount::zero(); + let asset_types: [AssetType; 4] = MaspDenom::iter() + .map(|denom| { + let asset_type = + make_asset_type(Some(epoch), token, sub_prefix, denom); + // Combine the value and unit into one amount + amount += + Amount::from_nonnegative(asset_type, denom.denominate(&val)) + .expect("invalid value for amount"); + asset_type + }) + .collect::>() + .try_into() + .expect("This can't fail"); + (asset_types, amount) } mod tests { diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index f933df26b3..399f75f800 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -14,6 +14,7 @@ pub mod queries; pub mod rpc; pub mod signing; pub mod storage; +#[allow(clippy::result_large_err)] pub mod tx; pub mod vp_host_fns; pub mod wallet; diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 2c15a8d3a2..7d9323f256 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -683,7 +683,7 @@ where } #[allow(clippy::upper_case_acronyms)] -#[derive(Debug)] +#[derive(Clone, Debug)] enum KeyType { #[allow(non_camel_case_types)] COUNTER, diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs index fe87319ff2..c00f57b5d9 100644 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ b/shared/src/ledger/native_vp/governance/utils.rs @@ -19,7 +19,6 @@ use crate::types::governance::{ ProposalVote, Tally, TallyResult, VotePower, VoteType, }; use crate::types::storage::Epoch; -use crate::types::token; /// Proposal structure holding votes information necessary to compute the /// outcome @@ -99,7 +98,7 @@ pub fn compute_tally( for (_, (amount, validator_vote)) in yay_validators.iter() { if let ProposalVote::Yay(vote_type) = validator_vote { if proposal_type == vote_type { - total_yay_staked_tokens += amount; + total_yay_staked_tokens += *amount; } else { // Log the error and continue tracing::error!( @@ -130,7 +129,7 @@ pub fn compute_tally( if !yay_validators.contains_key(validator_address) { // YAY: Add delegator amount whose validator // didn't vote / voted nay - total_yay_staked_tokens += vote_power; + total_yay_staked_tokens += *vote_power; } } ProposalVote::Nay => { @@ -138,7 +137,7 @@ pub fn compute_tally( // validator vote yay if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= vote_power; + total_yay_staked_tokens -= *vote_power; } } @@ -176,14 +175,14 @@ pub fn compute_tally( result: tally_result, total_voting_power: total_stake, total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, + total_nay_power: 0.into(), }) } else { Ok(ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stake, total_yay_power: total_yay_staked_tokens, - total_nay_power: 0, + total_nay_power: 0.into(), }) } } @@ -194,8 +193,9 @@ pub fn compute_tally( validator_vote { for v in votes { - *total_yay_staked_tokens.entry(v).or_insert(0) += - amount; + *total_yay_staked_tokens + .entry(v) + .or_insert(VotePower::zero()) += *amount; } } else { // Log the error and continue @@ -236,7 +236,7 @@ pub fn compute_tally( total_yay_staked_tokens .get_mut(vote) { - *power -= vote_power; + *power -= *vote_power; } else { return Err(Error::Tally( format!( @@ -252,7 +252,9 @@ pub fn compute_tally( // this, add voting power *total_yay_staked_tokens .entry(vote) - .or_insert(0) += vote_power; + .or_insert( + VotePower::zero(), + ) += *vote_power; } } } else { @@ -272,7 +274,8 @@ pub fn compute_tally( for vote in delegator_votes { *total_yay_staked_tokens .entry(vote) - .or_insert(0) += vote_power; + .or_insert(VotePower::zero()) += + *vote_power; } } } @@ -295,7 +298,7 @@ pub fn compute_tally( total_yay_staked_tokens .get_mut(vote) { - *power -= vote_power; + *power -= *vote_power; } else { return Err(Error::Tally( format!( @@ -334,14 +337,16 @@ pub fn compute_tally( // At least 1/3 of the total voting power must vote Yay let total_yay_voted_power = total_yay_staked_tokens .iter() - .fold(0, |acc, (_, vote_power)| acc + vote_power); + .fold(VotePower::zero(), |acc, (_, vote_power)| { + acc + *vote_power + }); - match total_yay_voted_power.checked_mul(3) { + match total_yay_voted_power.checked_mul(3.into()) { Some(v) if v < total_stake => Ok(ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stake, total_yay_power: total_yay_voted_power, - total_nay_power: 0, + total_nay_power: VotePower::zero(), }), _ => { // Select the winner council based on approval voting @@ -360,7 +365,7 @@ pub fn compute_tally( result: TallyResult::Passed(Tally::PGFCouncil(council)), total_voting_power: total_stake, total_yay_power: total_yay_voted_power, - total_nay_power: 0, + total_nay_power: VotePower::zero(), }) } } @@ -404,7 +409,8 @@ where epoch, )? .unwrap_or_default() - .into(); + .try_into() + .expect("Amount out of bounds"); yay_validators .insert(voter_address.clone(), (amount, vote)); @@ -420,13 +426,16 @@ where let amount = bond_amount(storage, &bond_id, epoch)?.1; - if amount != token::Amount::default() { + if !amount.is_zero() { let entry = delegators .entry(voter_address.to_owned()) .or_default(); entry.insert( validator.to_owned(), - (VotePower::from(amount), vote), + ( + VotePower::try_from(amount).unwrap(), + vote, + ), ); } } diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 12d879e2f0..152112874b 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -5,13 +5,13 @@ pub mod vp; pub use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::types::address; +pub use namada_core::types::dec::Dec; pub use namada_core::types::key::common; pub use namada_core::types::token; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::storage::*; pub use namada_proof_of_stake::{staking_token_address, types}; -use rust_decimal::Decimal; pub use vp::PosVP; use crate::types::address::{Address, InternalAddress}; @@ -27,11 +27,13 @@ pub const SLASH_POOL_ADDRESS: Address = /// Calculate voting power in the tendermint context (which is stored as i64) /// from the number of tokens pub fn into_tm_voting_power( - votes_per_token: Decimal, - tokens: impl Into, + votes_per_token: Dec, + tokens: token::Amount, ) -> i64 { - let prod = decimal_mult_u64(votes_per_token, tokens.into()); - i64::try_from(prod).expect("Invalid validator voting power (i64)") + let tokens = tokens.change(); + let prod = votes_per_token * tokens; + let res = i128::try_from(prod).expect("Failed conversion to i128"); + i64::try_from(res).expect("Invalid validator voting power (i64)") } /// Initialize storage in the genesis block. diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index d65826ca68..799a34e5bd 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -878,10 +878,22 @@ mod test_rpc_handlers { b0i, b0ii, b1, - b2i(balance: token::Amount), - b3(a1: token::Amount, a2: token::Amount, a3: token::Amount), - b3i(a1: token::Amount, a2: token::Amount, a3: token::Amount), - b3ii(a1: token::Amount, a2: token::Amount, a3: token::Amount), + b2i(balance: token::DenominatedAmount), + b3( + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: token::DenominatedAmount + ), + b3i( + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: token::DenominatedAmount + ), + b3ii( + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: token::DenominatedAmount + ), x, y(untyped_arg: &str), z(untyped_arg: &str), @@ -891,9 +903,9 @@ mod test_rpc_handlers { /// support optional args. pub fn b3iii( _ctx: RequestCtx<'_, D, H>, - a1: token::Amount, - a2: token::Amount, - a3: Option, + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: Option, ) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -910,9 +922,9 @@ mod test_rpc_handlers { /// support optional args. pub fn b3iiii( _ctx: RequestCtx<'_, D, H>, - a1: token::Amount, - a2: token::Amount, - a3: Option, + a1: token::DenominatedAmount, + a2: token::DenominatedAmount, + a3: Option, a4: Option, ) -> storage_api::Result where @@ -966,14 +978,14 @@ mod test_rpc { }, ( "1" ) -> String = b1, ( "2" ) = { - ( "i" / [balance: token::Amount] ) -> String = b2i, + ( "i" / [balance: token::DenominatedAmount] ) -> String = b2i, }, - ( "3" / [a1: token::Amount] / [a2: token::Amount] ) = { - ( "i" / [a3: token:: Amount] ) -> String = b3i, - ( [a3: token:: Amount] ) -> String = b3, - ( [a3: token:: Amount] / "ii" ) -> String = b3ii, - ( [a3: opt token::Amount] / "iii" ) -> String = b3iii, - ( "iiii" / [a3: opt token::Amount] / "xyz" / [a4: opt Epoch] ) -> String = b3iiii, + ( "3" / [a1: token::DenominatedAmount] / [a2: token::DenominatedAmount] ) = { + ( "i" / [a3: token::DenominatedAmount] ) -> String = b3i, + ( [a3: token::DenominatedAmount] ) -> String = b3, + ( [a3: token::DenominatedAmount] / "ii" ) -> String = b3ii, + ( [a3: opt token::DenominatedAmount] / "iii" ) -> String = b3iii, + ( "iiii" / [a3: opt token::DenominatedAmount] / "xyz" / [a4: opt Epoch] ) -> String = b3iiii, }, }, ( "c" ) -> String = (with_options c), @@ -988,6 +1000,8 @@ mod test_rpc { #[cfg(test)] mod test { + use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; + use super::test_rpc::TEST_RPC; use crate::ledger::queries::testing::TestClient; use crate::ledger::queries::{RequestCtx, RequestQuery, Router}; @@ -1029,13 +1043,25 @@ mod test { let result = TEST_RPC.b1(&client).await.unwrap(); assert_eq!(result, "b1"); - let balance = token::Amount::from(123_000_000); + let balance = token::DenominatedAmount { + amount: token::Amount::native_whole(123_000_000), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; let result = TEST_RPC.b2i(&client, &balance).await.unwrap(); assert_eq!(result, format!("b2i/{balance}")); - let a1 = token::Amount::from(345); - let a2 = token::Amount::from(123_000); - let a3 = token::Amount::from(1_000_999); + let a1 = token::DenominatedAmount { + amount: token::Amount::native_whole(345), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + let a2 = token::DenominatedAmount { + amount: token::Amount::native_whole(123_000), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + let a3 = token::DenominatedAmount { + amount: token::Amount::native_whole(1_000_999), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; let result = TEST_RPC.b3(&client, &a1, &a2, &a3).await.unwrap(); assert_eq!(result, format!("b3/{a1}/{a2}/{a3}")); diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 32c6a16a20..889c6d7428 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -5,7 +5,8 @@ use masp_primitives::sapling::Node; use namada_core::ledger::storage::LastBlock; use namada_core::types::address::Address; use namada_core::types::hash::Hash; -use namada_core::types::storage::{BlockResults, KeySeg}; +use namada_core::types::storage::{BlockResults, Key, KeySeg}; +use namada_core::types::token::MaspDenom; use crate::ibc::core::ics04_channel::packet::Sequence; use crate::ibc::core::ics24_host::identifier::{ChannelId, ClientId, PortId}; @@ -23,6 +24,8 @@ use crate::types::transaction::TxResult; type Conversion = ( Address, + Option, + MaspDenom, Epoch, masp_primitives::transaction::components::Amount, MerklePath, @@ -151,7 +154,7 @@ where H: 'static + StorageHasher + Sync, { // Conversion values are constructed on request - if let Some((addr, epoch, conv, pos)) = ctx + if let Some(((addr, sub_prefix, denom), epoch, conv, pos)) = ctx .wl_storage .storage .conversion_state @@ -160,6 +163,8 @@ where { Ok(( addr.clone(), + sub_prefix.clone(), + *denom, *epoch, Into::::into( conv.clone(), @@ -470,7 +475,7 @@ mod test { // Request dry run tx let mut outer_tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] - // To be able to dry-run testnet faucet withdrawal, pretend + // To be able to dry-run testnet faucet withdrawal, pretend // that we got a valid PoW has_valid_pow: true, })); @@ -515,7 +520,7 @@ mod test { assert!(!has_balance_key); // Then write some balance ... - let balance = token::Amount::from(1000); + let balance = token::Amount::native_whole(1000); StorageWrite::write(&mut client.wl_storage, &balance_key, balance)?; // It has to be committed to be visible in a query client.wl_storage.commit_tx(); diff --git a/shared/src/ledger/queries/types.rs b/shared/src/ledger/queries/types.rs index ec39fe0fdc..64ec5ecf4a 100644 --- a/shared/src/ledger/queries/types.rs +++ b/shared/src/ledger/queries/types.rs @@ -83,7 +83,7 @@ pub trait Router { pub trait Client { /// `std::io::Error` can happen in decoding with /// `BorshDeserialize::try_from_slice` - type Error: From; + type Error: From + std::fmt::Display; /// Send a simple query request at the given path. For more options, use the /// `request` method. diff --git a/shared/src/ledger/queries/vp/mod.rs b/shared/src/ledger/queries/vp/mod.rs index e575790bb4..ad05a2b88b 100644 --- a/shared/src/ledger/queries/vp/mod.rs +++ b/shared/src/ledger/queries/vp/mod.rs @@ -3,11 +3,16 @@ // Re-export to show in rustdoc! pub use pos::Pos; use pos::POS; +pub use token::Token; +use token::TOKEN; + pub mod pos; +mod token; // Validity predicate queries router! {VP, ( "pos" ) = (sub POS), + ( "token" ) = (sub TOKEN), } /// Client-only methods for the router type are composed from router functions. diff --git a/shared/src/ledger/queries/vp/pos.rs b/shared/src/ledger/queries/vp/pos.rs index 66d67a2f35..d872aa5002 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/shared/src/ledger/queries/vp/pos.rs @@ -207,7 +207,7 @@ where /// `None`. The total stake is a sum of validator's self-bonds and delegations /// to their address. /// Returns `None` when the given address is not a validator address. For a -/// validator with `0` stake, this returns `Ok(token::Amount::default())`. +/// validator with `0` stake, this returns `Ok(token::Amount::zero())`. fn validator_stake( ctx: RequestCtx<'_, D, H>, validator: Address, @@ -415,7 +415,7 @@ where let epoch = epoch.unwrap_or(ctx.wl_storage.storage.last_epoch); let handle = unbond_handle(&source, &validator); - let mut total = token::Amount::default(); + let mut total = token::Amount::zero(); for result in handle.iter(ctx.wl_storage)? { let ( lazy_map::NestedSubKey::Data { diff --git a/shared/src/ledger/queries/vp/token.rs b/shared/src/ledger/queries/vp/token.rs new file mode 100644 index 0000000000..cbad27005f --- /dev/null +++ b/shared/src/ledger/queries/vp/token.rs @@ -0,0 +1,43 @@ +use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; +use namada_core::ledger::storage_api; +use namada_core::ledger::storage_api::token::read_denom; +use namada_core::types::address::Address; +use namada_core::types::storage::Key; +use namada_core::types::token; + +use crate::ledger::queries::RequestCtx; + +router! {TOKEN, + ( "denomination" / [addr: Address] / [sub_prefix: opt Key] ) -> Option = denomination, + ( "denomination" / [addr: Address] / "ibc" / [_ibc_junk: String] ) -> Option = denomination_ibc, +} + +/// Get the number of decimal places (in base 10) for a +/// token specified by `addr`. +fn denomination( + ctx: RequestCtx<'_, D, H>, + addr: Address, + sub_prefix: Option, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + read_denom(ctx.wl_storage, &addr, sub_prefix.as_ref()) +} + +// TODO Please fix this + +/// Get the number of decimal places (in base 10) for a +/// token specified by `addr`. +fn denomination_ibc( + ctx: RequestCtx<'_, D, H>, + addr: Address, + _ibc_junk: String, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + read_denom(ctx.wl_storage, &addr, None) +} diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index f8f0ea1e86..f0dd64c995 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -9,11 +9,14 @@ use namada_core::ledger::storage::LastBlock; use namada_core::ledger::testnet_pow; use namada_core::types::address::Address; use namada_core::types::storage::Key; -use namada_core::types::token::Amount; +use namada_core::types::token::{ + Amount, DenominatedAmount, Denomination, MaspDenom, TokenAddress, +}; use namada_proof_of_stake::types::{BondsAndUnbondsDetails, CommissionPair}; use serde::Serialize; use tokio::time::Duration; +use crate::ledger::args::InputAmount; use crate::ledger::events::Event; use crate::ledger::governance::parameters::GovParams; use crate::ledger::governance::storage as gov_storage; @@ -99,8 +102,8 @@ pub async fn query_block( fn unwrap_client_response( response: Result, ) -> T { - response.unwrap_or_else(|_err| { - panic!("Error in the query"); + response.unwrap_or_else(|err| { + panic!("Error in the query: {}", err); }) } @@ -220,6 +223,8 @@ pub async fn query_conversion( asset_type: AssetType, ) -> Option<( Address, + Option, + MaspDenom, Epoch, masp_primitives::transaction::components::Amount, MerklePath, @@ -639,7 +644,8 @@ pub async fn get_proposal_votes( let amount: VotePower = get_validator_stake(client, epoch, &voter_address) .await - .into(); + .try_into() + .expect("Amount of bonds"); yay_validators.insert(voter_address, (amount, vote)); } else if !validators.contains(&voter_address) { let validator_address = @@ -774,15 +780,18 @@ pub async fn query_and_print_unbonds< } } if total_withdrawable != token::Amount::default() { - println!("Total withdrawable now: {total_withdrawable}."); + println!( + "Total withdrawable now: {}.", + total_withdrawable.to_string_native() + ); } if !not_yet_withdrawable.is_empty() { println!("Current epoch: {current_epoch}.") } for (withdraw_epoch, amount) in not_yet_withdrawable { println!( - "Amount {amount} withdrawable starting from epoch \ - {withdraw_epoch}." + "Amount {} withdrawable starting from epoch {withdraw_epoch}.", + amount.to_string_native() ); } } @@ -857,7 +866,8 @@ pub async fn get_governance_parameters< .expect("Parameter should be definied."); GovParams { - min_proposal_fund: u64::from(min_proposal_fund), + min_proposal_fund: u128::try_from(min_proposal_fund) + .expect("Amount out of bounds") as u64, max_proposal_code_size, min_proposal_period, max_proposal_period, @@ -920,3 +930,81 @@ pub async fn enriched_bonds_and_unbonds< .await, ) } + +/// Get the correct representation of the amount given the token type. +pub async fn validate_amount( + client: &C, + amount: InputAmount, + token: &Address, + sub_prefix: &Option, + force: bool, +) -> Option { + let input_amount = match amount { + InputAmount::Unvalidated(amt) => amt.canonical(), + InputAmount::Validated(amt) => return Some(amt), + }; + let denom = unwrap_client_response::>( + RPC.vp() + .token() + .denomination(client, token, sub_prefix) + .await, + ) + .or_else(|| { + if force { + println!( + "No denomination found for token: {token}, but --force was \ + passed. Defaulting to the provided denomination." + ); + Some(input_amount.denom) + } else { + println!( + "No denomination found for token: {token}, the input \ + arguments could not be parsed." + ); + None + } + })?; + if denom < input_amount.denom && !force { + println!( + "The input amount contained a higher precision than allowed by \ + {token}." + ); + None + } else { + match input_amount.increase_precision(denom) { + Ok(res) => Some(res), + Err(_) => { + println!( + "The amount provided requires more the 256 bits to \ + represent." + ); + None + } + } + } +} + +/// Look up the denomination of a token in order to format it +/// correctly as a string. +pub async fn format_denominated_amount< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + token: &TokenAddress, + amount: token::Amount, +) -> String { + let denom = unwrap_client_response::>( + RPC.vp() + .token() + .denomination(client, &token.address, &token.sub_prefix) + .await, + ) + .unwrap_or_else(|| { + println!( + "No denomination found for token: {token}, defaulting to zero \ + decimal places" + ); + 0.into() + }); + DenominatedAmount { amount, denom }.to_string() +} diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index 864ffcef2e..5a50d0470c 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -1,5 +1,5 @@ //! Functions to sign transactions -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; #[cfg(feature = "std")] use std::env; #[cfg(feature = "std")] @@ -16,7 +16,10 @@ use masp_primitives::transaction::components::sapling::fees::{ InputView, OutputView, }; use namada_core::types::address::{masp, Address, ImplicitAddress}; -use namada_core::types::token::{self, Amount}; +use namada_core::types::storage::Key; +use namada_core::types::token::{ + self, Amount, DenominatedAmount, MaspDenom, TokenAddress, +}; use namada_core::types::transaction::{pos, MIN_FEE}; use prost::Message; use serde::{Deserialize, Serialize}; @@ -28,7 +31,9 @@ use crate::ibc::applications::transfer::msgs::transfer::{ use crate::ibc_proto::google::protobuf::Any; use crate::ledger::masp::make_asset_type; use crate::ledger::parameters::storage as parameter_storage; -use crate::ledger::rpc::{query_wasm_code_hash, TxBroadcastData}; +use crate::ledger::rpc::{ + format_denominated_amount, query_wasm_code_hash, TxBroadcastData, +}; use crate::ledger::tx::{ Error, TX_BOND_WASM, TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, @@ -38,7 +43,7 @@ use crate::ledger::tx::{ pub use crate::ledger::wallet::store::AddressVpType; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; -use crate::proto::{Section, Signature, Tx}; +use crate::proto::{MaspBuilder, Section, Signature, Tx}; use crate::types::key::*; use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; use crate::types::storage::Epoch; @@ -195,14 +200,9 @@ pub async fn sign_tx< #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> Result { let keypair = tx_signer::(client, wallet, args, default).await?; - // Sign over the transacttion data - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &keypair, - ))); - // Sign over the transaction code + // Sign over the transaction data and code tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); @@ -249,7 +249,7 @@ pub async fn sign_wrapper< #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> TxBroadcastData { let fee_amount = if cfg!(feature = "mainnet") { - Amount::whole(MIN_FEE) + Amount::native_whole(MIN_FEE) } else { let wrapper_tx_fees_key = parameter_storage::get_wrapper_tx_fees_key(); rpc::query_storage_value::( @@ -268,15 +268,19 @@ pub async fn sign_wrapper< .unwrap_or_default(); let is_bal_sufficient = fee_amount <= balance; if !is_bal_sufficient { - eprintln!( + let token_addr = TokenAddress { + address: args.fee_token.clone(), + sub_prefix: None, + }; + let err_msg = format!( "The wrapper transaction source doesn't have enough balance to \ - pay fee {fee_amount}, got {balance}." + pay fee {}, got {}.", + format_denominated_amount(client, &token_addr, fee_amount).await, + format_denominated_amount(client, &token_addr, balance).await, ); + eprintln!("{}", err_msg); if !args.force && cfg!(feature = "mainnet") { - panic!( - "The wrapper transaction source doesn't have enough balance \ - to pay fee {fee_amount}, got {balance}." - ); + panic!("{}", err_msg); } } @@ -315,11 +319,6 @@ pub async fn sign_wrapper< )))); tx.header.chain_id = args.chain_id.clone().unwrap(); tx.header.expiration = args.expiration; - // Then sign over the bound wrapper - tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), - keypair, - ))); #[cfg(feature = "std")] // Attempt to decode the construction @@ -361,6 +360,8 @@ pub async fn sign_wrapper< tx.protocol_filter(); // Encrypt all sections not relating to the header tx.encrypt(&Default::default()); + // Then sign over the bound wrapper committing to all other sections + tx.add_section(Section::Signature(Signature::new(tx.sechashes(), keypair))); // We use this to determine when the wrapper tx makes it on-chain let wrapper_hash = tx.header_hash().to_string(); // We use this to determine when the decrypted inner tx makes it @@ -377,6 +378,7 @@ pub async fn sign_wrapper< } } +#[allow(clippy::result_large_err)] fn other_err(string: String) -> Result { Err(Error::Other(string)) } @@ -396,15 +398,25 @@ pub struct LedgerVector { fn make_ledger_amount_addr( tokens: &HashMap, output: &mut Vec, - amount: Amount, + amount: DenominatedAmount, token: &Address, + sub_prefix: &Option, prefix: &str, ) { + let token_address = TokenAddress { + address: token.clone(), + sub_prefix: sub_prefix.clone(), + }; if let Some(token) = tokens.get(token) { - output.push(format!("{}Amount: {} {}", prefix, token, amount)); + output.push(format!( + "{}Amount {}: {}", + prefix, + token_address.format_with_alias(token), + amount + )); } else { output.extend(vec![ - format!("{}Token: {}", prefix, token), + format!("{}Token: {}", prefix, token_address), format!("{}Amount: {}", prefix, amount), ]); } @@ -412,34 +424,41 @@ fn make_ledger_amount_addr( /// Adds a Ledger output line describing a given transaction amount and asset /// type -fn make_ledger_amount_asset( +async fn make_ledger_amount_asset( + client: &C, tokens: &HashMap, output: &mut Vec, amount: u64, token: &AssetType, - assets: &HashMap, + assets: &HashMap, MaspDenom, Epoch)>, prefix: &str, ) { - if let Some((token, _epoch)) = assets.get(token) { + if let Some((token, sub_prefix, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees + let token_addr = TokenAddress { + address: token.clone(), + sub_prefix: sub_prefix.clone(), + }; + let formatted_amt = + format_denominated_amount(client, &token_addr, amount.into()).await; if let Some(token) = tokens.get(token) { output.push(format!( "{}Amount: {} {}", prefix, - token, - Amount::from(amount) + token_addr.format_with_alias(token), + formatted_amt, )); } else { output.extend(vec![ - format!("{}Token: {}", prefix, token), - format!("{}Amount: {}", prefix, Amount::from(amount)), + format!("{}Token: {}", prefix, token_addr), + format!("{}Amount: {}", prefix, formatted_amt), ]); } } else { // Otherwise display the raw AssetTypes output.extend(vec![ format!("{}Token: {}", prefix, token), - format!("{}Amount: {}", prefix, Amount::from(amount)), + format!("{}Amount: {}", prefix, amount), ]); } } @@ -505,6 +524,82 @@ fn format_outputs(output: &mut Vec) { } } +/// Adds a Ledger output for the sender and destination for transparent and MASP +/// transactions +pub async fn make_ledger_masp_endpoints( + client: &C, + tokens: &HashMap, + output: &mut Vec, + transfer: &Transfer, + builder: Option<&MaspBuilder>, + assets: &HashMap, MaspDenom, Epoch)>, +) { + if transfer.source != masp() { + output.push(format!("Sender : {}", transfer.source)); + if transfer.target == masp() { + make_ledger_amount_addr( + tokens, + output, + transfer.amount, + &transfer.token, + &transfer.sub_prefix, + "Sending ", + ); + } + } else if let Some(builder) = builder { + for sapling_input in builder.builder.sapling_inputs() { + let vk = ExtendedViewingKey::from(*sapling_input.key()); + output.push(format!("Sender : {}", vk)); + make_ledger_amount_asset( + client, + tokens, + output, + sapling_input.value(), + &sapling_input.asset_type(), + assets, + "Sending ", + ).await; + } + } + if transfer.target != masp() { + output.push(format!("Destination : {}", transfer.target)); + if transfer.source == masp() { + make_ledger_amount_addr( + tokens, + output, + transfer.amount, + &transfer.token, + &transfer.sub_prefix, + "Receiving ", + ); + } + } else if let Some(builder) = builder { + for sapling_output in builder.builder.sapling_outputs() { + let pa = PaymentAddress::from(sapling_output.address()); + output.push(format!("Destination : {}", pa)); + make_ledger_amount_asset( + client, + tokens, + output, + sapling_output.value(), + &sapling_output.asset_type(), + assets, + "Receiving ", + ).await; + } + } + if transfer.source != masp() && transfer.target != masp() { + make_ledger_amount_addr( + tokens, + output, + transfer.amount, + &transfer.token, + &transfer.sub_prefix, + "", + ); + } +} + /// Converts the given transaction to the form that is displayed on the Ledger /// device pub async fn to_ledger_vector< @@ -591,7 +686,7 @@ pub async fn to_ledger_vector< let extra = tx .get_section(&init_account.vp_code_hash) - .and_then(Section::extra_data_sec) + .and_then(|x| Section::extra_data_sec(x.as_ref())) .expect("unable to load vp code") .code .hash(); @@ -621,7 +716,7 @@ pub async fn to_ledger_vector< let extra = tx .get_section(&init_validator.validator_vp_code_hash) - .and_then(Section::extra_data_sec) + .and_then(|x| Section::extra_data_sec(x.as_ref())) .expect("unable to load vp code") .code .hash(); @@ -683,16 +778,8 @@ pub async fn to_ledger_vector< init_proposal_data.voting_end_epoch ), format!("Grace epoch : {}", init_proposal_data.grace_epoch), + format!("Content: {}", init_proposal_data.content), ]); - let content: BTreeMap = - BorshDeserialize::try_from_slice(&init_proposal_data.content)?; - if !content.is_empty() { - for (key, value) in &content { - tv.output.push(format!("Content {} : {}", key, value)); - } - } else { - tv.output.push("Content : (none)".to_string()); - } tv.output_expert.extend(vec![ format!("ID : {}", init_proposal_data_id), @@ -706,15 +793,8 @@ pub async fn to_ledger_vector< init_proposal_data.voting_end_epoch ), format!("Grace epoch : {}", init_proposal_data.grace_epoch), + format!("Content: {}", init_proposal_data.content), ]); - if !content.is_empty() { - for (key, value) in content { - tv.output_expert - .push(format!("Content {} : {}", key, value)); - } - } else { - tv.output_expert.push("Content : none".to_string()); - } } else if code_hash == vote_proposal_hash { let vote_proposal = VoteProposalData::try_from_slice(&tx.data().ok_or_else(|| { @@ -767,7 +847,7 @@ pub async fn to_ledger_vector< let extra = tx .get_section(&transfer.vp_code_hash) - .and_then(Section::extra_data_sec) + .and_then(|x| Section::extra_data_sec(x.as_ref())) .expect("unable to load vp code") .code .hash(); @@ -799,10 +879,16 @@ pub async fn to_ledger_vector< Section::MaspBuilder(builder) if builder.target == shielded_hash => { - for (addr, epoch) in &builder.asset_types { + for (addr, sub_prefix, denom, epoch) in &builder.asset_types + { asset_types.insert( - make_asset_type(*epoch, addr), - (addr.clone(), *epoch), + make_asset_type( + Some(*epoch), + addr, + sub_prefix, + *denom, + ), + (addr.clone(), sub_prefix.clone(), *denom, *epoch), ); } Some(builder) @@ -816,72 +902,22 @@ pub async fn to_ledger_vector< tv.name = "Transfer 0".to_string(); tv.output.push("Type : Transfer".to_string()); - if transfer.source != masp() { - tv.output.push(format!("Sender : {}", transfer.source)); - if transfer.target == masp() { - make_ledger_amount_addr( - &tokens, - &mut tv.output, - transfer.amount, - &transfer.token, - "Sending ", - ); - } - } else if let Some(builder) = builder { - for input in builder.builder.sapling_inputs() { - let vk = ExtendedViewingKey::from(*input.key()); - tv.output.push(format!("Sender : {}", vk)); - make_ledger_amount_asset( - &tokens, - &mut tv.output, - input.value(), - &input.asset_type(), - &asset_types, - "Sending ", - ); - } - } - if transfer.target != masp() { - tv.output.push(format!("Destination : {}", transfer.target)); - if transfer.source == masp() { - make_ledger_amount_addr( - &tokens, - &mut tv.output, - transfer.amount, - &transfer.token, - "Receiving ", - ); - } - } else if let Some(builder) = builder { - for output in builder.builder.sapling_outputs() { - let pa = PaymentAddress::from(output.address()); - tv.output.push(format!("Destination : {}", pa)); - make_ledger_amount_asset( - &tokens, - &mut tv.output, - output.value(), - &output.asset_type(), - &asset_types, - "Receiving ", - ); - } - } - if transfer.source != masp() && transfer.target != masp() { - make_ledger_amount_addr( - &tokens, - &mut tv.output, - transfer.amount, - &transfer.token, - "", - ); - } - - tv.output_expert.extend(vec![ - format!("Source : {}", transfer.source), - format!("Target : {}", transfer.target), - format!("Token : {}", transfer.token), - format!("Amount : {}", transfer.amount), - ]); + make_ledger_masp_endpoints( + client, + &tokens, + &mut tv.output, + &transfer, + builder, + &asset_types, + ).await; + make_ledger_masp_endpoints( + client, + &tokens, + &mut tv.output_expert, + &transfer, + builder, + &asset_types, + ).await; } else if code_hash == ibc_hash { let msg = Any::decode( tx.data() @@ -958,13 +994,13 @@ pub async fn to_ledger_vector< format!("Type : Bond"), format!("Source : {}", bond_source), format!("Validator : {}", bond.validator), - format!("Amount : {}", bond.amount), + format!("Amount : {}", bond.amount.to_string_native()), ]); tv.output_expert.extend(vec![ format!("Source : {}", bond_source), format!("Validator : {}", bond.validator), - format!("Amount : {}", bond.amount), + format!("Amount : {}", bond.amount.to_string_native()), ]); } else if code_hash == unbond_hash { let unbond = @@ -983,13 +1019,13 @@ pub async fn to_ledger_vector< format!("Code : Unbond"), format!("Source : {}", unbond_source), format!("Validator : {}", unbond.validator), - format!("Amount : {}", unbond.amount), + format!("Amount : {}", unbond.amount.to_string_native()), ]); tv.output_expert.extend(vec![ format!("Source : {}", unbond_source), format!("Validator : {}", unbond.validator), - format!("Amount : {}", unbond.amount), + format!("Amount : {}", unbond.amount.to_string_native()), ]); } else if code_hash == withdraw_hash { let withdraw = @@ -1035,19 +1071,32 @@ pub async fn to_ledger_vector< } if let Some(wrapper) = tx.header.wrapper() { + let gas_token = TokenAddress { + address: wrapper.fee.token.clone(), + sub_prefix: None, + }; + let gas_limit = format_denominated_amount( + client, + &gas_token, + Amount::from(wrapper.gas_limit), + ) + .await; + let gas_amount = + format_denominated_amount(client, &gas_token, wrapper.fee.amount) + .await; tv.output_expert.extend(vec![ format!("Timestamp : {}", tx.header.timestamp.0), format!("PK : {}", wrapper.pk), format!("Epoch : {}", wrapper.epoch), - format!("Gas limit : {}", Amount::from(wrapper.gas_limit)), - format!("Fee token : {}", wrapper.fee.token), + format!("Gas limit : {}", gas_limit), + format!("Fee token : {}", gas_token), ]); if let Some(token) = tokens.get(&wrapper.fee.token) { tv.output_expert - .push(format!("Fee amount : {} {}", token, wrapper.fee.amount)); + .push(format!("Fee amount : {} {}", token, gas_amount)); } else { tv.output_expert - .push(format!("Fee amount : {}", wrapper.fee.amount)); + .push(format!("Fee amount : {}", gas_amount)); } } diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 628bcd8bfd..738c5d756d 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -1,6 +1,6 @@ //! SDK functions to construct different types of transactions use std::borrow::Cow; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::str::FromStr; use borsh::BorshSerialize; @@ -16,11 +16,12 @@ use masp_primitives::transaction::components::transparent::fees::{ }; use masp_primitives::transaction::components::Amount; use namada_core::types::address::{masp, masp_tx_key, Address}; +use namada_core::types::dec::Dec; +use namada_core::types::storage::Key; +use namada_core::types::token::{MaspDenom, TokenAddress}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::CommissionPair; use prost::EncodeError; -use rust_decimal::Decimal; -use sha2::{Digest as Sha2Digest, Sha256}; use thiserror::Error; use tokio::time::Duration; @@ -32,16 +33,18 @@ use crate::ibc::timestamp::Timestamp as IbcTimestamp; use crate::ibc::tx_msg::Msg; use crate::ibc::Height as IbcHeight; use crate::ibc_proto::cosmos::base::v1beta1::Coin; -use crate::ledger::args; +use crate::ledger::args::{self, InputAmount}; use crate::ledger::governance::storage as gov_storage; use crate::ledger::masp::{ShieldedContext, ShieldedUtils}; -use crate::ledger::rpc::{self, TxBroadcastData, TxResponse}; +use crate::ledger::rpc::{ + self, format_denominated_amount, validate_amount, TxBroadcastData, + TxResponse, +}; use crate::ledger::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::proto::{Code, Data, MaspBuilder, Section, Signature, Tx}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; -use crate::types::hash::Hash; use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; @@ -100,7 +103,7 @@ pub enum Error { TxBroadcast(RpcError), /// Invalid comission rate set #[error("Invalid new commission rate, received {0}")] - InvalidCommissionRate(Decimal), + InvalidCommissionRate(Dec), /// Invalid validator address #[error("The address {0} doesn't belong to any known validator account.")] InvalidValidatorAddress(Address), @@ -109,7 +112,7 @@ pub enum Error { "New rate, {0}, is too large of a change with respect to the \ predecessor epoch in which the rate will take effect." )] - TooLargeOfChange(Decimal), + TooLargeOfChange(Dec), /// Error retrieving from storage #[error("Error retrieving from storage")] Retrieval, @@ -130,13 +133,13 @@ pub enum Error { "The total bonds of the source {0} is lower than the amount to be \ unbonded. Amount to unbond is {1} and the total bonds is {2}." )] - LowerBondThanUnbond(Address, token::Amount, token::Amount), + LowerBondThanUnbond(Address, String, String), /// Balance is too low #[error( "The balance of the source {0} of token {1} is lower than the amount \ to be transferred. Amount to transfer is {2} and the balance is {3}." )] - BalanceTooLow(Address, Address, token::Amount, token::Amount), + BalanceTooLow(Address, Address, String, String), /// Token Address does not exist on chain #[error("The token address {0} doesn't exist on chain.")] TokenDoesNotExist(Address), @@ -158,13 +161,7 @@ pub enum Error { transferred and fees. Amount to transfer is {1} {2} and fees are {3} \ {4}." )] - NegativeBalanceAfterTransfer( - Address, - token::Amount, - Address, - token::Amount, - Address, - ), + NegativeBalanceAfterTransfer(Address, String, Address, String, Address), /// No Balance found for token #[error("{0}")] MaspError(builder::Error), @@ -367,11 +364,7 @@ pub async fn submit_reveal_pk_aux< find_keypair(client, wallet, &addr, args.password.clone()).await }?; tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &keypair, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let epoch = rpc::query_epoch(client).await; @@ -527,6 +520,24 @@ pub async fn submit_tx( parsed } +/// decode components of a masp note +pub fn decode_component( + (addr, sub, denom, epoch): (Address, Option, MaspDenom, Epoch), + val: i128, + res: &mut HashMap, + mk_key: F, +) where + F: FnOnce(Address, Option, Epoch) -> K, + K: Eq + std::hash::Hash, +{ + let decoded_change = token::Change::from_masp_denominated(val, denom) + .expect("expected this to fit"); + + res.entry(mk_key(addr, sub, epoch)) + .and_modify(|val| *val += decoded_change) + .or_insert(decoded_change); +} + /// Save accounts initialized from a tx into the wallet, if any. pub async fn save_initialized_accounts( wallet: &mut Wallet, @@ -585,7 +596,7 @@ pub async fn submit_validator_commission_change< >( client: &C, wallet: &mut Wallet, - args: args::TxCommissionRateChange, + args: args::CommissionRateChange, ) -> Result<(), Error> { let epoch = rpc::query_epoch(client).await; @@ -602,11 +613,9 @@ pub async fn submit_validator_commission_change< let validator = args.validator.clone(); if rpc::is_validator(client, &validator).await { - if args.rate < Decimal::ZERO || args.rate > Decimal::ONE { + if args.rate < Dec::zero() || args.rate > Dec::one() { eprintln!("Invalid new commission rate, received {}", args.rate); - if !args.tx.force { - return Err(Error::InvalidCommissionRate(args.rate)); - } + return Err(Error::InvalidCommissionRate(args.rate)); } let pipeline_epoch_minus_one = epoch + params.pipeline_len - 1; @@ -622,7 +631,7 @@ pub async fn submit_validator_commission_change< commission_rate, max_commission_change_per_epoch, }) => { - if (args.rate - commission_rate).abs() + if args.rate.abs_diff(&commission_rate) > max_commission_change_per_epoch { eprintln!( @@ -752,7 +761,7 @@ pub async fn submit_withdraw< Some(epoch), ) .await; - if tokens == 0.into() { + if tokens.is_zero() { eprintln!( "There are no unbonded bonds ready to withdraw in the current \ epoch {}.", @@ -763,7 +772,10 @@ pub async fn submit_withdraw< return Err(Error::NoUnbondReady(epoch)); } } else { - println!("Found {tokens} tokens that can be withdrawn."); + println!( + "Found {} tokens that can be withdrawn.", + tokens.to_string_native() + ); println!("Submitting transaction to withdraw them..."); } @@ -814,20 +826,25 @@ pub async fn submit_unbond< let bond_amount = rpc::query_bond(client, &bond_source, &args.validator, None).await; - println!("Bond amount available for unbonding: {} NAM", bond_amount); + println!( + "Bond amount available for unbonding: {} NAM", + bond_amount.to_string_native() + ); if args.amount > bond_amount { eprintln!( "The total bonds of the source {} is lower than the amount to \ be unbonded. Amount to unbond is {} and the total bonds is \ {}.", - bond_source, args.amount, bond_amount + bond_source, + args.amount.to_string_native(), + bond_amount.to_string_native() ); if !args.tx.force { return Err(Error::LowerBondThanUnbond( bond_source, - args.amount, - bond_amount, + args.amount.to_string_native(), + bond_amount.to_string_native(), )); } } @@ -898,21 +915,24 @@ pub async fn submit_unbond< std::cmp::Ordering::Equal => { println!( "Amount {} withdrawable starting from epoch {}", - latest_withdraw_amount_post - latest_withdraw_amount_pre, + (latest_withdraw_amount_post - latest_withdraw_amount_pre) + .to_string_native(), latest_withdraw_epoch_post ); } std::cmp::Ordering::Greater => { println!( "Amount {} withdrawable starting from epoch {}", - latest_withdraw_amount_post, latest_withdraw_epoch_post + latest_withdraw_amount_post.to_string_native(), + latest_withdraw_epoch_post, ); } } } else { println!( "Amount {} withdrawable starting from epoch {}", - latest_withdraw_amount_post, latest_withdraw_epoch_post + latest_withdraw_amount_post.to_string_native(), + latest_withdraw_epoch_post, ); } @@ -947,7 +967,10 @@ pub async fn submit_bond< // TODO Should we state the same error message for the native token? check_balance_too_low_err( - &args.native_token, + &TokenAddress { + address: args.native_token, + sub_prefix: None, + }, bond_source, args.amount, balance_key, @@ -1049,7 +1072,10 @@ pub async fn submit_ibc_transfer< }; check_balance_too_low_err( - &token, + &TokenAddress { + address: token.clone(), + sub_prefix: sub_prefix.clone(), + }, &source, args.amount, balance_key, @@ -1068,10 +1094,14 @@ pub async fn submit_ibc_transfer< Some(sp) => sp.to_string().replace(RESERVED_ADDRESS_PREFIX, ""), None => token.to_string(), }; - let token = Coin { - denom, - amount: args.amount.to_string(), - }; + let amount = args + .amount + .to_string_native() + .split('.') + .next() + .expect("invalid amount") + .to_string(); + let token = Coin { denom, amount }; // this height should be that of the destination chain, not this chain let timeout_height = match args.timeout_height { @@ -1132,7 +1162,7 @@ async fn add_asset_type< C: crate::ledger::queries::Client + Sync, U: ShieldedUtils, >( - asset_types: &mut HashSet<(Address, Epoch)>, + asset_types: &mut HashSet<(Address, Option, MaspDenom, Epoch)>, shielded: &mut ShieldedContext, client: &C, asset_type: AssetType, @@ -1160,7 +1190,7 @@ async fn used_asset_types< shielded: &mut ShieldedContext, client: &C, builder: &Builder, -) -> Result, RpcError> { +) -> Result, MaspDenom, Epoch)>, RpcError> { let mut asset_types = HashSet::new(); // Collect all the asset types used in the Sapling inputs for input in builder.sapling_inputs() { @@ -1208,7 +1238,7 @@ pub async fn submit_transfer< client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, - args: args::TxTransfer, + mut args: args::TxTransfer, ) -> Result<(), Error> { let source = args.source.effective_address(); let target = args.target.effective_address(); @@ -1222,7 +1252,7 @@ pub async fn submit_transfer< token_exists_or_err(token.clone(), args.tx.force, client).await?; // Check source balance let (sub_prefix, balance_key) = match &args.sub_prefix { - Some(sub_prefix) => { + Some(ref sub_prefix) => { let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); ( @@ -1232,10 +1262,41 @@ pub async fn submit_transfer< } None => (None, token::balance_key(&token, &source)), }; - check_balance_too_low_err::( + + // validate the amount given + let validated_amount = validate_amount( + client, + args.amount, &token, + &sub_prefix, + args.tx.force, + ) + .await + .expect("expected to validate amount"); + let validate_fee = validate_amount( + client, + args.tx.fee_amount, + &args.tx.fee_token, + // TODO: Currently multi-tokens cannot be used to pay fees + &None, + args.tx.force, + ) + .await + .expect("expected to be able to validate fee"); + + args.amount = InputAmount::Validated(validated_amount); + args.tx.fee_amount = InputAmount::Validated(validate_fee); + let sub_prefix = args + .sub_prefix + .as_ref() + .map(|k| k.parse().expect("Could not parse multi-token sub-prefix")); + check_balance_too_low_err::( + &TokenAddress { + address: token.clone(), + sub_prefix: sub_prefix.clone(), + }, &source, - args.amount, + validated_amount.amount, balance_key, args.tx.force, client, @@ -1247,25 +1308,25 @@ pub async fn submit_transfer< // signer. Also, if the transaction is shielded, redact the amount and token // types by setting the transparent value to 0 and token type to a constant. // This has no side-effect because transaction is to self. - let (default_signer, amount, token) = + let (default_signer, _amount, token) = if source == masp_addr && target == masp_addr { // TODO Refactor me, we shouldn't rely on any specific token here. ( TxSigningKey::SecretKey(masp_tx_key()), - 0.into(), + token::Amount::default(), args.native_token.clone(), ) } else if source == masp_addr { ( TxSigningKey::SecretKey(masp_tx_key()), - args.amount, + validated_amount.amount, token.clone(), ) } else { ( TxSigningKey::WalletAddress(args.source.effective_address()), - args.amount, - token, + validated_amount.amount, + token.clone(), ) }; // If our chosen signer is the MASP sentinel key, then our shielded inputs @@ -1293,7 +1354,7 @@ pub async fn submit_transfer< for _ in 0..2 { // Construct the shielded part of the transaction, if any let stx_result = shielded - .gen_shielded_transfer(client, args.clone(), shielded_gas) + .gen_shielded_transfer(client, &args, shielded_gas) .await; let shielded_parts = match stx_result { @@ -1301,9 +1362,9 @@ pub async fn submit_transfer< Err(builder::Error::InsufficientFunds(_)) => { Err(Error::NegativeBalanceAfterTransfer( source.clone(), - args.amount, + validated_amount.amount.to_string_native(), token.clone(), - args.tx.fee_amount, + validate_fee.amount.to_string_native(), args.tx.fee_token.clone(), )) } @@ -1314,42 +1375,40 @@ pub async fn submit_transfer< tx.header.chain_id = args.tx.chain_id.clone().unwrap(); tx.header.expiration = args.tx.expiration; // Add the MASP Transaction and its Builder to facilitate validation - let (masp_hash, shielded_tx_epoch) = if let Some(shielded_parts) = - shielded_parts - { - // Add a MASP Transaction section to the Tx - let masp_tx = tx.add_section(Section::MaspTx(shielded_parts.1)); - // Get the hash of the MASP Transaction section - let masp_hash = - Hash(masp_tx.hash(&mut Sha256::new()).finalize_reset().into()); - // Get the decoded asset types used in the transaction to give - // offline wallet users more information - let asset_types = - used_asset_types(shielded, client, &shielded_parts.0) - .await - .unwrap_or_default(); - // Add the MASP Transaction's Builder to the Tx - tx.add_section(Section::MaspBuilder(MaspBuilder { - asset_types, - // Store how the Info objects map to Descriptors/Outputs - metadata: shielded_parts.2, - // Store the data that was used to construct the Transaction - builder: shielded_parts.0, - // Link the Builder to the Transaction by hash code - target: masp_hash, - })); - // The MASP Transaction section hash will be used in Transfer - (Some(masp_hash), Some(shielded_parts.3)) - } else { - (None, None) - }; + let (masp_hash, shielded_tx_epoch) = + if let Some(shielded_parts) = shielded_parts { + // Add a MASP Transaction section to the Tx + let masp_tx = tx.add_section(Section::MaspTx(shielded_parts.1)); + // Get the hash of the MASP Transaction section + let masp_hash = masp_tx.get_hash(); + // Get the decoded asset types used in the transaction to give + // offline wallet users more information + let asset_types = + used_asset_types(shielded, client, &shielded_parts.0) + .await + .unwrap_or_default(); + // Add the MASP Transaction's Builder to the Tx + tx.add_section(Section::MaspBuilder(MaspBuilder { + asset_types, + // Store how the Info objects map to Descriptors/Outputs + metadata: shielded_parts.2, + // Store the data that was used to construct the Transaction + builder: shielded_parts.0, + // Link the Builder to the Transaction by hash code + target: masp_hash, + })); + // The MASP Transaction section hash will be used in Transfer + (Some(masp_hash), Some(shielded_parts.3)) + } else { + (None, None) + }; // Construct the corresponding transparent Transfer object let transfer = token::Transfer { source: source.clone(), target: target.clone(), token: token.clone(), sub_prefix: sub_prefix.clone(), - amount, + amount: validated_amount, key: key.clone(), // Link the Transfer to the MASP Transaction by hash code shielded: masp_hash, @@ -1428,11 +1487,9 @@ pub async fn submit_init_account< tx.header.expiration = args.tx.expiration; let extra = tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); - let extra_hash = - Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = InitAccount { public_key, - vp_code_hash: extra_hash, + vp_code_hash: extra.get_hash(), }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; tx.set_data(Data::new(data)); @@ -1522,11 +1579,9 @@ pub async fn submit_update_vp< tx.header.expiration = args.tx.expiration; let extra = tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); - let extra_hash = - Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = UpdateVp { addr, - vp_code_hash: extra_hash, + vp_code_hash: extra.get_hash(), }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; tx.set_data(Data::new(data)); @@ -1652,7 +1707,7 @@ where /// Returns the given token if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain -async fn token_exists_or_err( +pub async fn token_exists_or_err( token: Address, force: bool, client: &C, @@ -1713,7 +1768,7 @@ async fn target_exists_or_err( /// given amount, along with the balance even existing. force /// overrides this async fn check_balance_too_low_err( - token: &Address, + token: &TokenAddress, source: &Address, amount: token::Amount, balance_key: storage::Key, @@ -1730,15 +1785,18 @@ async fn check_balance_too_low_err( "The balance of the source {} of token {} is lower \ than the amount to be transferred. Amount to \ transfer is {} and the balance is {}.", - source, token, amount, balance + source, + token, + format_denominated_amount(client, token, amount).await, + format_denominated_amount(client, token, balance).await, ); Ok(()) } else { Err(Error::BalanceTooLow( source.clone(), - token.clone(), - amount, - balance, + token.address.clone(), + amount.to_string_native(), + balance.to_string_native(), )) } } else { @@ -1753,7 +1811,10 @@ async fn check_balance_too_low_err( ); Ok(()) } else { - Err(Error::NoBalanceForToken(source.clone(), token.clone())) + Err(Error::NoBalanceForToken( + source.clone(), + token.address.clone(), + )) } } } diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 5d89e4006d..92c8e19e1c 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -289,7 +289,7 @@ pub fn get_tx_code_hash( ) -> EnvResult> { let hash = tx .get_section(tx.code_sechash()) - .and_then(Section::code_sec) + .and_then(|x| Section::code_sec(x.as_ref())) .map(|x| x.code.hash()); add_gas(gas_meter, MIN_STORAGE_GAS)?; Ok(hash) diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 1b73329efe..4d50ee4ac7 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -4,6 +4,6 @@ pub mod ibc; pub mod key; pub use namada_core::types::{ - address, chain, governance, hash, internal, masp, storage, time, token, - transaction, validity_predicate, + address, chain, dec, governance, hash, internal, masp, storage, time, + token, transaction, uint, validity_predicate, }; diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 6252596676..5415f0eb5a 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -464,7 +464,7 @@ where /// Called from tx wasm to request to use the given gas amount pub fn tx_charge_gas( env: &TxVmEnv, - used_gas: i32, + used_gas: i64, ) -> TxResult<()> where MEM: VmMemory, @@ -506,7 +506,7 @@ where /// Called from VP wasm to request to use the given gas amount pub fn vp_charge_gas( env: &VpVmEnv, - used_gas: i32, + used_gas: i64, ) -> vp_host_fns::EnvResult<()> where MEM: VmMemory, diff --git a/shared/src/vm/mod.rs b/shared/src/vm/mod.rs index 70fae7a3d4..2ca6d81a91 100644 --- a/shared/src/vm/mod.rs +++ b/shared/src/vm/mod.rs @@ -15,22 +15,25 @@ pub mod wasm; use thiserror::Error; const UNTRUSTED_WASM_FEATURES: WasmFeatures = WasmFeatures { + mutable_global: false, + saturating_float_to_int: false, + sign_extension: true, reference_types: false, multi_value: false, bulk_memory: false, - module_linking: false, simd: false, + relaxed_simd: false, threads: false, tail_call: false, - deterministic_only: true, + floats: false, multi_memory: false, exceptions: false, memory64: false, - mutable_global: false, - saturating_float_to_int: false, - sign_extension: true, - relaxed_simd: false, extended_const: false, + component_model: false, + function_references: false, + memory_control: false, + gc: false, }; #[allow(missing_docs)] @@ -232,9 +235,9 @@ impl<'a, T: 'a> MutHostSlice<'a, &[T]> { pub fn validate_untrusted_wasm( wasm_code: impl AsRef<[u8]>, ) -> Result<(), WasmValidationError> { - let mut validator = Validator::new(); - validator.wasm_features(UNTRUSTED_WASM_FEATURES); - validator + let mut validator = Validator::new_with_features(UNTRUSTED_WASM_FEATURES); + let _types = validator .validate_all(wasm_code.as_ref()) - .map_err(WasmValidationError::ForbiddenWasmFeatures) + .map_err(WasmValidationError::ForbiddenWasmFeatures)?; + Ok(()) } diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index ae20b9e6d2..c006cb2f16 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -4,7 +4,6 @@ use std::collections::BTreeSet; use std::marker::PhantomData; use parity_wasm::elements; -use pwasm_utils::{self, rules}; use thiserror::Error; use wasmer::{BaseTunables, Module, Store}; @@ -97,7 +96,7 @@ where { let tx_code = tx .get_section(tx.code_sechash()) - .and_then(Section::code_sec) + .and_then(|x| Section::code_sec(x.as_ref())) .ok_or(Error::MissingCode)?; let (module, store) = match tx_code.code { Commitment::Hash(code_hash) => { @@ -400,11 +399,16 @@ pub fn untrusted_wasm_store(limit: Limit) -> wasmer::Store { pub fn prepare_wasm_code>(code: T) -> Result> { let module: elements::Module = elements::deserialize_buffer(code.as_ref()) .map_err(Error::DeserializationError)?; + let module = wasm_instrument::gas_metering::inject( + module, + wasm_instrument::gas_metering::host_function::Injector::new( + "env", "gas", + ), + &get_gas_rules(), + ) + .map_err(|_original_module| Error::GasMeterInjection)?; let module = - pwasm_utils::inject_gas_counter(module, &get_gas_rules(), "env") - .map_err(|_original_module| Error::GasMeterInjection)?; - let module = - pwasm_utils::stack_height::inject_limiter(module, WASM_STACK_LIMIT) + wasm_instrument::inject_stack_limiter(module, WASM_STACK_LIMIT) .map_err(|_original_module| Error::StackLimiterInjection)?; elements::serialize(module).map_err(Error::SerializationError) } @@ -461,8 +465,15 @@ where } /// Get the gas rules used to meter wasm operations -fn get_gas_rules() -> rules::Set { - rules::Set::default().with_grow_cost(1) +fn get_gas_rules() -> wasm_instrument::gas_metering::ConstantCostRules { + let instruction_cost = 1; + let memory_grow_cost = 1; + let call_per_local_cost = 1; + wasm_instrument::gas_metering::ConstantCostRules::new( + instruction_cost, + memory_grow_cost, + call_per_local_cost, + ) } #[cfg(test)] @@ -485,10 +496,10 @@ mod tests { /// execution is aborted. #[test] fn test_tx_stack_limiter() { - // Because each call into `$loop` inside the wasm consumes 3 stack - // heights, this should hit the stack limit. If we were to subtract - // one from this value, we should be just under the limit. - let loops = WASM_STACK_LIMIT / 3 - 1; + // Because each call into `$loop` inside the wasm consumes 5 stack + // heights except for the terminal call, this should hit the stack + // limit. + let loops = WASM_STACK_LIMIT / 5 - 1; let error = loop_in_tx_wasm(loops).expect_err(&format!( "Expecting runtime error \"unreachable\" caused by stack-height \ @@ -506,10 +517,10 @@ mod tests { /// is aborted. #[test] fn test_vp_stack_limiter() { - // Because each call into `$loop` inside the wasm consumes 3 stack - // heights, this should hit the stack limit. If we were to subtract - // one from this value, we should be just under the limit. - let loops = WASM_STACK_LIMIT / 3 - 1; + // Because each call into `$loop` inside the wasm consumes 5 stack + // heights except for the terminal call, this should hit the stack + // limit. + let loops = WASM_STACK_LIMIT / 5 - 1; let error = loop_in_vp_wasm(loops).expect_err( "Expecting runtime error caused by stack-height overflow. Got", diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 6f36d8f61f..64cd8a7e7c 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -20,6 +20,7 @@ mainnet = [ abciplus = [ "namada/abciplus", "namada/ibc-mocks", + "namada_apps/abciplus", "namada_vp_prelude/abciplus", "namada_tx_prelude/abciplus", ] @@ -38,8 +39,6 @@ ibc-relayer-types.workspace = true ibc-relayer.workspace = true prost.workspace = true regex.workspace = true -rust_decimal_macros.workspace = true -rust_decimal.workspace = true serde_json.workspace = true sha2.workspace = true tempfile.workspace = true diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 60dc119051..6d9fee4298 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -17,6 +17,7 @@ use namada::types::token; use namada_apps::config::genesis::genesis_config; use namada_apps::config::utils::convert_tm_addr_to_socket_addr; use namada_apps::config::{Config, TendermintMode}; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use super::setup::{ self, sleep, NamadaBgCmd, NamadaCmd, Test, ENV_VAR_DEBUG, @@ -192,12 +193,13 @@ pub fn find_bonded_stake( .rsplit_once(' ') .unwrap() .1; - token::Amount::from_str(bonded_stake_str).map_err(|e| { - eyre!(format!( - "Bonded stake: {} parsed from {}, Error: {}\n\nOutput: {}", - bonded_stake_str, matched, e, unread - )) - }) + token::Amount::from_str(bonded_stake_str, NATIVE_MAX_DECIMAL_PLACES) + .map_err(|e| { + eyre!(format!( + "Bonded stake: {} parsed from {}, Error: {}\n\nOutput: {}", + bonded_stake_str, matched, e, unread + )) + }) } /// Get the last committed epoch. diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 9e25f22d15..f217a59e6f 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -699,7 +699,7 @@ fn transfer_token( ALBERT, &receiver, NAM, - &Amount::whole(100000), + &Amount::native_whole(100000), port_channel_id_a, None, None, @@ -767,6 +767,7 @@ fn transfer_received_token( .to_string(); let rpc = get_actor_rpc(test, &Who::Validator(0)); + let amount = Amount::native_whole(50000).to_string_native(); let tx_args = [ "transfer", "--source", @@ -778,7 +779,7 @@ fn transfer_received_token( "--sub-prefix", &sub_prefix, "--amount", - "50000", + &amount, "--gas-amount", "0", "--gas-limit", @@ -824,7 +825,7 @@ fn transfer_back( BERTHA, &receiver, NAM, - &Amount::whole(50000), + &Amount::native_whole(50000), port_channel_id_b, Some(sub_prefix), None, @@ -883,7 +884,7 @@ fn transfer_timeout( ALBERT, &receiver, NAM, - &Amount::whole(100000), + &Amount::native_whole(100000), port_channel_id_a, None, Some(Duration::new(5, 0)), @@ -1025,7 +1026,7 @@ fn transfer( let rpc = get_actor_rpc(test, &Who::Validator(0)); let receiver = receiver.to_string(); - let amount = amount.to_string(); + let amount = amount.to_string_native(); let port_id = port_channel_id.port_id.to_string(); let channel_id = port_channel_id.channel_id.to_string(); let mut tx_args = vec![ diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 0b084e6f92..9120a303b6 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -29,6 +29,7 @@ use namada_apps::config::genesis::genesis_config::{ }; use namada_apps::config::utils::convert_tm_addr_to_socket_addr; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; +use namada_core::types::token::{DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES}; use namada_test_utils::TestWasms; use serde_json::json; use setup::constants::*; @@ -372,7 +373,10 @@ fn ledger_txs_and_queries() -> Result<()> { target: find_address(&test, ALBERT).unwrap(), token: find_address(&test, NAM).unwrap(), sub_prefix: None, - amount: token::Amount::whole(10), + amount: token::DenominatedAmount { + amount: token::Amount::native_whole(10), + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }, key: None, shielded: None, } @@ -405,7 +409,7 @@ fn ledger_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], - // Submit a token transfer tx (from an implicit account) + // Submit a token transfer tx (from an ed25519 implicit account) vec![ "transfer", "--source", @@ -425,6 +429,26 @@ fn ledger_txs_and_queries() -> Result<()> { "--node", &validator_one_rpc, ], + // Submit a token transfer tx (from a secp256k1 implicit account) + vec![ + "transfer", + "--source", + ESTER, + "--target", + ALBERT, + "--token", + NAM, + "--amount", + "10.1", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--node", + &validator_one_rpc, + ], // 3. Submit a transaction to update an account's validity // predicate vec![ @@ -543,7 +567,7 @@ fn ledger_txs_and_queries() -> Result<()> { } let christel = find_address(&test, CHRISTEL)?; // as setup in `genesis/e2e-tests-single-node.toml` - let christel_balance = token::Amount::whole(1000000); + let christel_balance = token::Amount::native_whole(1000000); let nam = find_address(&test, NAM)?; let storage_key = token::balance_key(&nam, &christel).to_string(); let query_args_and_expected_response = vec![ @@ -619,6 +643,7 @@ fn masp_txs_and_queries() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let txs_args = vec![ // 2. Attempt to spend 10 BTC at SK(A) to PA(B) ( @@ -823,10 +848,13 @@ fn masp_txs_and_queries() -> Result<()> { "Transaction is valid", ), ]; - // Wait till epoch boundary - let _ep0 = epoch_sleep(&test, &validator_one_rpc, 720)?; - + loop { + let ep = epoch_sleep(&test, &validator_one_rpc, 60)?; + if ep >= Epoch(1) { + break; + } + } for (tx_args, tx_result) in &txs_args { for &dry_run in &[true, false] { let tx_args = if dry_run && tx_args[0] == "transfer" { @@ -834,7 +862,7 @@ fn masp_txs_and_queries() -> Result<()> { } else { tx_args.clone() }; - let mut client = run!(test, Bin::Client, tx_args, Some(300))?; + let mut client = run!(test, Bin::Client, tx_args, Some(720))?; if *tx_result == "Transaction is valid" && !dry_run { client.exp_string("Transaction accepted")?; @@ -864,7 +892,7 @@ fn masp_pinned_txs() -> Result<()> { let test = setup::network( |genesis| { let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(60), + epochs_per_year: epochs_per_year_from_min_duration(120), ..genesis.parameters }; GenesisConfig { @@ -926,6 +954,9 @@ fn masp_pinned_txs() -> Result<()> { client.exp_string("has not yet been consumed")?; client.assert_success(); + // Wait till epoch boundary + let _ep1 = epoch_sleep(&test, &validator_one_rpc, 720)?; + // Send 20 BTC from Albert to PPA(C) let mut client = run!( test, @@ -945,9 +976,15 @@ fn masp_pinned_txs() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); + // Wait till epoch boundary + // This makes it more consistent for some reason? + let _ep2 = epoch_sleep(&test, &validator_one_rpc, 720)?; + // Assert PPA(C) has the 20 BTC transaction pinned to it let mut client = run!( test, @@ -1016,6 +1053,10 @@ fn masp_pinned_txs() -> Result<()> { #[test] fn masp_incentives() -> Result<()> { + // The number of decimal places used by BTC amounts. + const BTC_DENOMINATION: u8 = 8; + // The number of decimal places used by ETH amounts. + const ETH_DENOMINATION: u8 = 18; // Download the shielded pool parameters before starting node let _ = CLIShieldedUtils::new(PathBuf::new()); // Lengthen epoch to ensure that a transaction can be constructed and @@ -1049,8 +1090,13 @@ fn masp_incentives() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); // Wait till epoch boundary - let ep0 = epoch_sleep(&test, &validator_one_rpc, 720)?; - + loop { + let ep = epoch_sleep(&test, &validator_one_rpc, 60)?; + if ep >= Epoch(1) { + break; + } + } + let ep0 = get_epoch(&test, &validator_one_rpc)?; // Send 20 BTC from Albert to PA(A) let mut client = run!( test, @@ -1070,6 +1116,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1086,7 +1134,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("btc: 20")?; client.assert_success(); @@ -1104,7 +1152,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded nam balance found")?; client.assert_success(); @@ -1127,13 +1175,13 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("btc: 20")?; client.assert_success(); - let amt20 = token::Amount::from_str("20").unwrap(); - let amt30 = token::Amount::from_str("30").unwrap(); + let amt20 = token::Amount::from_uint(20, BTC_DENOMINATION).unwrap(); + let amt10 = token::Amount::from_uint(10, ETH_DENOMINATION).unwrap(); // Assert NAM balance at VK(A) is 20*BTC_reward*(epoch_1-epoch_0) let mut client = run!( @@ -1148,12 +1196,14 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) - ))?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is 20*BTC_reward*(epoch_1-epoch_0) @@ -1169,12 +1219,14 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0) - ))?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary @@ -1193,7 +1245,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("btc: 20")?; client.assert_success(); @@ -1211,12 +1263,14 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) - ))?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is 20*BTC_reward*(epoch_2-epoch_0) @@ -1232,18 +1286,20 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0) - ))?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary let ep3 = epoch_sleep(&test, &validator_one_rpc, 720)?; - // Send 30 ETH from Albert to PA(B) + // Send 10 ETH from Albert to PA(B) let mut client = run!( test, Bin::Client, @@ -1256,16 +1312,18 @@ fn masp_incentives() -> Result<()> { "--token", ETH, "--amount", - "30", + "10", "--node", &validator_one_rpc ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); - // Assert ETH balance at VK(B) is 30 + // Assert ETH balance at VK(B) is 10 let mut client = run!( test, Bin::Client, @@ -1278,9 +1336,9 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string("eth: 30")?; + client.exp_string("eth: 10")?; client.assert_success(); // Assert NAM balance at VK(B) is 0 @@ -1296,7 +1354,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded nam balance found")?; client.assert_success(); @@ -1304,7 +1362,7 @@ fn masp_incentives() -> Result<()> { // Wait till epoch boundary let ep4 = epoch_sleep(&test, &validator_one_rpc, 720)?; - // Assert ETH balance at VK(B) is 30 + // Assert ETH balance at VK(B) is 10 let mut client = run!( test, Bin::Client, @@ -1317,12 +1375,12 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string("eth: 30")?; + client.exp_string("eth: 10")?; client.assert_success(); - // Assert NAM balance at VK(B) is 30*ETH_reward*(epoch_4-epoch_3) + // Assert NAM balance at VK(B) is 10*ETH_reward*(epoch_4-epoch_3) let mut client = run!( test, Bin::Client, @@ -1335,16 +1393,18 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0) - ))?; + let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_4-epoch_0)+30*ETH_reward*(epoch_4-epoch_3) + // 20*BTC_reward*(epoch_4-epoch_0)+10*ETH_reward*(epoch_4-epoch_3) let mut client = run!( test, Bin::Client, @@ -1357,19 +1417,21 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&btc()]).0 * (ep4.0 - ep0.0)) - + ((amt30 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0)) - ))?; + let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) + + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); // Wait till epoch boundary let ep5 = epoch_sleep(&test, &validator_one_rpc, 720)?; - // Send 30 ETH from SK(B) to Christel + // Send 10 ETH from SK(B) to Christel let mut client = run!( test, Bin::Client, @@ -1382,7 +1444,7 @@ fn masp_incentives() -> Result<()> { "--token", ETH, "--amount", - "30", + "10", "--signer", BERTHA, "--node", @@ -1390,6 +1452,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1406,14 +1470,14 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded eth balance found")?; client.assert_success(); let mut ep = get_epoch(&test, &validator_one_rpc)?; - // Assert NAM balance at VK(B) is 30*ETH_reward*(ep-epoch_3) + // Assert NAM balance at VK(B) is 10*ETH_reward*(ep-epoch_3) let mut client = run!( test, Bin::Client, @@ -1426,17 +1490,19 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt30 * masp_rewards[ð()]).0 * (ep.0 - ep3.0) - ))?; + let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); ep = get_epoch(&test, &validator_one_rpc)?; // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_5-epoch_0)+30*ETH_reward*(epoch_5-epoch_3) + // 20*BTC_reward*(epoch_5-epoch_0)+10*ETH_reward*(epoch_5-epoch_3) let mut client = run!( test, Bin::Client, @@ -1449,13 +1515,15 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&btc()]).0 * (ep.0 - ep0.0)) - + ((amt30 * masp_rewards[ð()]).0 * (ep.0 - ep3.0)) - ))?; + let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep.0 - ep0.0)) + + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep.0 - ep3.0)); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); // Wait till epoch boundary @@ -1482,6 +1550,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1498,7 +1568,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded btc balance found")?; client.assert_success(); @@ -1516,12 +1586,14 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) - ))?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is @@ -1538,13 +1610,15 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) - ))?; + let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary @@ -1563,15 +1637,17 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0) - ))?; + let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated))?; client.assert_success(); - // Assert NAM balance at VK(B) is 30*ETH_reward*(epoch_5-epoch_3) + // Assert NAM balance at VK(B) is 10*ETH_reward*(epoch_5-epoch_3) let mut client = run!( test, Bin::Client, @@ -1584,16 +1660,18 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - (amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0) - ))?; + let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Assert NAM balance at MASP pool is - // 20*BTC_reward*(epoch_6-epoch_0)+30*ETH_reward*(epoch_5-epoch_3) + // 20*BTC_reward*(epoch_6-epoch_0)+10*ETH_reward*(epoch_5-epoch_3) let mut client = run!( test, Bin::Client, @@ -1606,20 +1684,22 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; - client.exp_string(&format!( - "nam: {}", - ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) - + ((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) - ))?; + let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let denominated = DenominatedAmount { + amount: amt, + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }; + client.exp_string(&format!("nam: {}", denominated,))?; client.assert_success(); // Wait till epoch boundary to prevent conversion expiry during transaction // construction let _ep8 = epoch_sleep(&test, &validator_one_rpc, 720)?; - // Send 30*ETH_reward*(epoch_5-epoch_3) NAM from SK(B) to Christel + // Send 10*ETH_reward*(epoch_5-epoch_3) NAM from SK(B) to Christel let mut client = run!( test, Bin::Client, @@ -1632,7 +1712,8 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt30 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)).to_string(), + &((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)) + .to_string_native(), "--signer", BERTHA, "--node", @@ -1640,6 +1721,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1659,7 +1742,8 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)).to_string(), + &((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + .to_string_native(), "--signer", ALBERT, "--node", @@ -1667,6 +1751,8 @@ fn masp_incentives() -> Result<()> { ], Some(300) )?; + client.exp_string("Transaction accepted")?; + client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid")?; client.assert_success(); @@ -1683,7 +1769,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded nam balance found")?; client.assert_success(); @@ -1701,7 +1787,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("No shielded nam balance found")?; client.assert_success(); @@ -1719,7 +1805,7 @@ fn masp_incentives() -> Result<()> { "--node", &validator_one_rpc ], - Some(300) + Some(60) )?; client.exp_string("nam: 0")?; client.assert_success(); @@ -1817,7 +1903,7 @@ fn invalid_transactions() -> Result<()> { "--token", BERTHA, "--amount", - "1_000_000.1", + "1000000.1", "--gas-amount", "0", "--gas-limit", @@ -1949,7 +2035,8 @@ fn pos_bonds() -> Result<()> { ]; let mut client = run_as!(test, Who::Validator(0), Bin::Client, tx_args, Some(40))?; - client.exp_string("Amount 5100 withdrawable starting from epoch ")?; + client + .exp_string("Amount 5100.000000 withdrawable starting from epoch ")?; client.assert_success(); // 5. Submit an unbond of the delegation @@ -1971,7 +2058,7 @@ fn pos_bonds() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - let expected = "Amount 3200 withdrawable starting from epoch "; + let expected = "Amount 3200.000000 withdrawable starting from epoch "; let (_unread, matched) = client.exp_regex(&format!("{expected}.*\n"))?; let epoch_raw = matched.trim().split_once(expected).unwrap().1; let delegation_withdrawable_epoch = Epoch::from_str(epoch_raw).unwrap(); @@ -2317,17 +2404,17 @@ fn test_bond_queries() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; + let (_, res) = client + .exp_regex(r"withdrawable starting from epoch [0-9]+") + .unwrap(); + let withdraw_epoch = + Epoch::from_str(res.split(' ').last().unwrap()).unwrap(); client.assert_success(); - // 6. Wait for epoch 7 - let start = Instant::now(); - let loop_timeout = Duration::new(20, 0); + // 6. Wait for withdraw_epoch loop { - if Instant::now().duration_since(start) > loop_timeout { - panic!("Timed out waiting for epoch: {}", 7); - } - let epoch = get_epoch(&test, &validator_one_rpc)?; - if epoch >= Epoch(7) { + let epoch = epoch_sleep(&test, &validator_one_rpc, 120)?; + if epoch >= withdraw_epoch { break; } } @@ -2336,11 +2423,11 @@ fn test_bond_queries() -> Result<()> { let tx_args = vec!["bonds", "--ledger-address", &validator_one_rpc]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string( - "All bonds total active: 200088\r -All bonds total: 200088\r -All unbonds total active: 412\r -All unbonds total: 412\r -All unbonds total withdrawable: 412\r", + "All bonds total active: 200088.000000\r +All bonds total: 200088.000000\r +All unbonds total active: 412.000000\r +All unbonds total: 412.000000\r +All unbonds total withdrawable: 412.000000\r", )?; client.assert_success(); @@ -2577,7 +2664,7 @@ fn pos_init_validator() -> Result<()> { find_bonded_stake(&test, new_validator, &non_validator_rpc)?; assert_eq!( bonded_stake, - token::Amount::whole(validator_stake + delegation) + token::Amount::native_whole(validator_stake + delegation) ); Ok(()) @@ -2899,7 +2986,7 @@ fn proposal_submission() -> Result<()> { // 9. Send a yay vote from a validator let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 13 { - sleep(1); + sleep(10); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -2964,7 +3051,7 @@ fn proposal_submission() -> Result<()> { // 11. Query the proposal and check the result let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 <= 25 { - sleep(1); + sleep(10); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -2983,7 +3070,7 @@ fn proposal_submission() -> Result<()> { // 12. Wait proposal grace and check proposal author funds let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); while epoch.0 < 31 { - sleep(1); + sleep(10); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } diff --git a/tests/src/e2e/multitoken_tests.rs b/tests/src/e2e/multitoken_tests.rs index 460395cc59..0f2b15d877 100644 --- a/tests/src/e2e/multitoken_tests.rs +++ b/tests/src/e2e/multitoken_tests.rs @@ -26,7 +26,7 @@ fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { println!("Fake multitoken VP established at {}", multitoken_vp_addr); let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; - let albert_starting_red_balance = token::Amount::from(100_000_000); + let albert_starting_red_balance = token::Amount::native_whole(100_000_000); helpers::mint_red_tokens( &test, &rpc_addr, @@ -35,7 +35,7 @@ fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { &albert_starting_red_balance, )?; - let transfer_amount = token::Amount::from(10_000_000); + let transfer_amount = token::Amount::native_whole(10_000_000); // make a transfer from Albert to Bertha, signed by Christel - this should // be rejected @@ -70,7 +70,7 @@ fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { ALBERT, BERTHA, ALBERT, - &token::Amount::from(10_000_000), + &token::Amount::native_whole(10_000_000), )?; authorized_transfer.exp_string("Transaction applied with result")?; authorized_transfer.exp_string("Transaction is valid")?; @@ -110,7 +110,8 @@ fn test_multitoken_transfer_established_to_implicit() -> Result<()> { established_alias, )?; - let established_starting_red_balance = token::Amount::from(100_000_000); + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); // mint some red tokens for the established account let established_addr = e2e::helpers::find_address(&test, established_alias)?; @@ -122,7 +123,7 @@ fn test_multitoken_transfer_established_to_implicit() -> Result<()> { &established_starting_red_balance, )?; - let transfer_amount = token::Amount::from(10_000_000); + let transfer_amount = token::Amount::native_whole(10_000_000); // attempt an unauthorized transfer to Albert from the established account let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( &test, @@ -197,7 +198,7 @@ fn test_multitoken_transfer_implicit_to_established() -> Result<()> { )?; let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; - let albert_starting_red_balance = token::Amount::from(100_000_000); + let albert_starting_red_balance = token::Amount::native_whole(100_000_000); helpers::mint_red_tokens( &test, &rpc_addr, @@ -206,7 +207,7 @@ fn test_multitoken_transfer_implicit_to_established() -> Result<()> { &albert_starting_red_balance, )?; - let transfer_amount = token::Amount::from(10_000_000); + let transfer_amount = token::Amount::native_whole(10_000_000); // attempt an unauthorized transfer from Albert to the established account let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( @@ -280,7 +281,8 @@ fn test_multitoken_transfer_established_to_established() -> Result<()> { established_alias, )?; - let established_starting_red_balance = token::Amount::from(100_000_000); + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); // mint some red tokens for the established account let established_addr = e2e::helpers::find_address(&test, established_alias)?; @@ -302,7 +304,8 @@ fn test_multitoken_transfer_established_to_established() -> Result<()> { receiver_alias, )?; - let established_starting_red_balance = token::Amount::from(100_000_000); + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); // mint some red tokens for the established account let established_addr = e2e::helpers::find_address(&test, established_alias)?; @@ -314,7 +317,7 @@ fn test_multitoken_transfer_established_to_established() -> Result<()> { &established_starting_red_balance, )?; - let transfer_amount = token::Amount::from(10_000_000); + let transfer_amount = token::Amount::native_whole(10_000_000); // attempt an unauthorized transfer let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs index 7008910b5e..27228cf266 100644 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -1,11 +1,11 @@ //! Helpers for use in multitoken tests. use std::path::PathBuf; -use std::str::FromStr; use borsh::BorshSerialize; use color_eyre::eyre::Result; use eyre::Context; use namada_core::types::address::Address; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use namada_core::types::{storage, token}; use namada_test_utils::tx_data::TxWriteData; use namada_test_utils::TestWasms; @@ -138,7 +138,7 @@ pub fn attempt_red_tokens_transfer( signer: &str, amount: &token::Amount, ) -> Result { - let amount = amount.to_string(); + let amount = amount.to_string_native(); let transfer_args = vec![ "transfer", "--token", @@ -184,6 +184,6 @@ pub fn fetch_red_token_balance( println!("Got balance for {}: {}", owner_alias, matched); let decimal = decimal_regex.find(&matched).unwrap().as_str(); client_balance.assert_success(); - token::Amount::from_str(decimal) + token::Amount::from_str(decimal, NATIVE_MAX_DECIMAL_PLACES) .wrap_err(format!("Failed to parse {}", matched)) } diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 47cde7a28e..1d119bcce0 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -21,6 +21,7 @@ use eyre::{eyre, Context}; use itertools::{Either, Itertools}; use namada::types::chain::ChainId; use namada_apps::client::utils; +use namada_apps::client::utils::REDUCED_CLI_PRINTING; use namada_apps::config::genesis::genesis_config::{self, GenesisConfig}; use namada_apps::{config, wallet}; use once_cell::sync::Lazy; @@ -118,7 +119,7 @@ pub fn network( eprintln!("Failed setting up colorful error reports {}", err); } }); - + env::set_var(REDUCED_CLI_PRINTING, "true"); let working_dir = working_dir(); let test_dir = TestDir::new(); @@ -795,6 +796,7 @@ pub mod constants { pub const CHRISTEL: &str = "Christel"; pub const CHRISTEL_KEY: &str = "Christel-key"; pub const DAEWON: &str = "Daewon"; + pub const ESTER: &str = "Ester"; pub const MATCHMAKER_KEY: &str = "matchmaker-key"; pub const MASP: &str = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5"; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index f4ba9de6f6..b76b4dd041 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -502,7 +502,7 @@ mod tests { validator: &Address, amount: token::Amount, ) -> bool { - let raw_amount: u64 = amount.into(); + let raw_amount: u128 = amount.try_into().unwrap(); let mut total_bonds: u64 = 0; for action in self.all_valid_actions().into_iter() { match action { @@ -513,8 +513,8 @@ mod tests { } => { if owner == &bond_owner && validator == &bond_validator { - let raw_amount: u64 = amount.into(); - total_bonds += raw_amount; + let raw_amount: u128 = amount.try_into().unwrap(); + total_bonds += raw_amount as u64; } } ValidPosAction::Unbond { @@ -524,15 +524,15 @@ mod tests { } => { if owner == &bond_owner && validator == &bond_validator { - let raw_amount: u64 = amount.into(); - total_bonds -= raw_amount; + let raw_amount: u128 = amount.try_into().unwrap(); + total_bonds -= raw_amount as u64; } } _ => {} } } - total_bonds >= raw_amount + total_bonds as u128 >= raw_amount } /// Find if the given owner and validator has unbonds that are ready to @@ -576,9 +576,10 @@ pub mod testing { use namada::types::key::RefTo; use namada::types::storage::Epoch; use namada::types::{address, key, token}; + use namada_core::types::dec::Dec; + use namada_core::types::token::{Amount, Change}; use namada_tx_prelude::{Address, StorageRead, StorageWrite}; use proptest::prelude::*; - use rust_decimal::Decimal; use crate::tx::{self, tx_host_env}; @@ -599,8 +600,8 @@ pub mod testing { InitValidator { address: Address, consensus_key: PublicKey, - commission_rate: Decimal, - max_commission_rate_change: Decimal, + commission_rate: Dec, + max_commission_rate_change: Dec, }, Bond { amount: token::Amount, @@ -640,14 +641,14 @@ pub mod testing { Bond { owner: Address, validator: Address, - delta: i128, + delta: Change, offset: DynEpochOffset, }, /// Add tokens unbonded from a bond at unbonding offset Unbond { owner: Address, validator: Address, - delta: i128, + delta: Change, }, /// Withdraw tokens from an unbond at the current epoch WithdrawUnbond { @@ -655,12 +656,12 @@ pub mod testing { validator: Address, }, TotalDeltas { - delta: i128, + delta: Change, offset: Either, }, ValidatorSet { validator: Address, - token_delta: i128, + token_delta: Change, offset: DynEpochOffset, }, ValidatorConsensusKey { @@ -670,7 +671,7 @@ pub mod testing { }, ValidatorDeltas { validator: Address, - delta: i128, + delta: Change, offset: DynEpochOffset, }, ValidatorState { @@ -678,7 +679,7 @@ pub mod testing { state: ValidatorState, }, StakingTokenPosBalance { - delta: i128, + delta: Change, }, ValidatorAddressRawHash { address: Address, @@ -687,11 +688,11 @@ pub mod testing { }, ValidatorCommissionRate { address: Address, - rate: Decimal, + rate: Dec, }, ValidatorMaxCommissionRateChange { address: Address, - change: Decimal, + change: Dec, }, } @@ -749,7 +750,7 @@ pub mod testing { arb_validator, ) .prop_map(|(amount, owner, validator)| ValidPosAction::Bond { - amount: amount.into(), + amount: Amount::from_uint(amount, 0).unwrap(), owner, validator, }); @@ -779,12 +780,15 @@ pub mod testing { // them let arb_unbond = arb_current_bond.prop_flat_map( |(bond_id, current_bond_amount)| { - let current_bond_amount: u64 = - current_bond_amount.into(); + let current_bond_amount = + >::try_into( + current_bond_amount, + ) + .unwrap() as u64; // Unbond an arbitrary amount up to what's available (0..current_bond_amount).prop_map(move |amount| { ValidPosAction::Unbond { - amount: amount.into(), + amount: Amount::from_uint(amount, 0).unwrap(), owner: bond_id.source.clone(), validator: bond_id.validator.clone(), } @@ -883,7 +887,7 @@ pub mod testing { }, PosStorageChange::ValidatorSet { validator: address.clone(), - token_delta: 0, + token_delta: 0.into(), offset, }, PosStorageChange::ValidatorConsensusKey { @@ -896,7 +900,7 @@ pub mod testing { }, PosStorageChange::ValidatorDeltas { validator: address.clone(), - delta: 0, + delta: 0.into(), offset, }, PosStorageChange::ValidatorCommissionRate { @@ -1302,13 +1306,11 @@ pub mod testing { ); let mut balance: token::Amount = tx::ctx().read(&balance_key).unwrap().unwrap_or_default(); - if delta < 0 { - let to_spend: u64 = (-delta).try_into().unwrap(); - let to_spend: token::Amount = to_spend.into(); + if !delta.non_negative() { + let to_spend = token::Amount::from_change(delta); balance.spend(&to_spend); } else { - let to_recv: u64 = delta.try_into().unwrap(); - let to_recv: token::Amount = to_recv.into(); + let to_recv = token::Amount::from_change(delta); balance.receive(&to_recv); } tx::ctx().write(&balance_key, balance).unwrap(); @@ -1363,7 +1365,7 @@ pub mod testing { pub fn apply_validator_set_change( _validator: Address, - _token_delta: i128, + _token_delta: Change, _offset: DynEpochOffset, _current_epoch: Epoch, _params: &PosParams, @@ -1542,7 +1544,7 @@ pub mod testing { PosStorageChange::Bond { owner, validator, - delta, + delta: delta.into(), offset, }, ] diff --git a/tests/src/storage_api/testnet_pow.rs b/tests/src/storage_api/testnet_pow.rs index 88d391796a..5e54188c1b 100644 --- a/tests/src/storage_api/testnet_pow.rs +++ b/tests/src/storage_api/testnet_pow.rs @@ -11,7 +11,7 @@ use crate::vp; fn test_challenge_and_solution() -> storage_api::Result<()> { let faucet_address = address::testing::established_address_1(); let difficulty = Difficulty::try_new(1).unwrap(); - let withdrawal_limit = token::Amount::whole(1_000); + let withdrawal_limit = token::Amount::native_whole(1_000); let mut tx_env = TestTxEnv::default(); diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index a442b5a6cd..240bc63e1f 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -238,7 +238,7 @@ pub fn init_storage() -> (Address, Address) { // initialize an account let account = tx::ctx().init_account(code_hash).unwrap(); let key = token::balance_key(&token, &account); - let init_bal = Amount::whole(100); + let init_bal = Amount::native_whole(100); let bytes = init_bal.try_to_vec().expect("encoding failed"); tx_host_env::with(|env| { env.wl_storage.storage.write(&key, &bytes).unwrap(); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index f4344e33b2..1766d7bad3 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -461,11 +461,7 @@ mod tests { tx.set_code(Code::new(code.clone())); tx.set_data(Data::new(data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair, ))); env.tx = tx; @@ -474,7 +470,13 @@ mod tests { assert_eq!(signed_tx_data.data().as_ref(), Some(data)); assert!( signed_tx_data - .verify_signature(&pk, signed_tx_data.data_sechash()) + .verify_signature( + &pk, + &[ + *signed_tx_data.data_sechash(), + *signed_tx_data.code_sechash(), + ], + ) .is_ok() ); @@ -483,7 +485,10 @@ mod tests { signed_tx_data .verify_signature( &other_keypair.ref_to(), - signed_tx_data.data_sechash() + &[ + *signed_tx_data.data_sechash(), + *signed_tx_data.code_sechash(), + ], ) .is_err() ); @@ -541,11 +546,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); let result = vp::CTX.eval(empty_code, tx).unwrap(); @@ -564,11 +565,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); let result = vp::CTX.eval(code_hash, tx).unwrap(); @@ -588,11 +585,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); let result = vp::CTX.eval(code_hash, tx).unwrap(); @@ -614,11 +607,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); @@ -654,11 +643,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // update the client with the message @@ -697,11 +682,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // init a connection with the message @@ -736,11 +717,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open the connection with the message @@ -780,11 +757,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open try a connection with the message @@ -819,11 +792,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open the connection with the mssage @@ -865,11 +834,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // init a channel with the message @@ -904,11 +869,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open the channle with the message @@ -950,11 +911,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // try open a channel with the message @@ -990,11 +947,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open a channel with the message @@ -1039,11 +992,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // close the channel with the message @@ -1096,11 +1045,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); @@ -1150,11 +1095,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // send the token and a packet with the data @@ -1203,11 +1144,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // ack the packet with the message @@ -1225,7 +1162,7 @@ mod tests { let balance: Option = tx_host_env::with(|env| { env.wl_storage.read(&balance_key).expect("read error") }); - assert_eq!(balance, Some(Amount::whole(0))); + assert_eq!(balance, Some(Amount::native_whole(0))); let escrow_key = token::balance_key( &token, &address::Address::Internal(address::InternalAddress::IbcEscrow), @@ -1233,7 +1170,7 @@ mod tests { let escrow: Option = tx_host_env::with(|env| { env.wl_storage.read(&escrow_key).expect("read error") }); - assert_eq!(escrow, Some(Amount::whole(100))); + assert_eq!(escrow, Some(Amount::native_whole(100))); } #[test] @@ -1253,7 +1190,7 @@ mod tests { let denom = format!("{}/{}/{}", port_id, channel_id, token); let key_prefix = ibc_storage::ibc_token_prefix(&denom).unwrap(); let balance_key = token::multitoken_balance_key(&key_prefix, &sender); - let init_bal = Amount::whole(100); + let init_bal = Amount::native_whole(100); writes.insert(balance_key.clone(), init_bal.try_to_vec().unwrap()); // original denom let hash = ibc_storage::calc_hash(&denom); @@ -1283,11 +1220,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // send the token and a packet with the data @@ -1311,7 +1244,7 @@ mod tests { let balance: Option = tx_host_env::with(|env| { env.wl_storage.read(&balance_key).expect("read error") }); - assert_eq!(balance, Some(Amount::whole(0))); + assert_eq!(balance, Some(Amount::native_whole(0))); let burn_key = token::balance_key( &token, &address::Address::Internal(address::InternalAddress::IbcBurn), @@ -1319,7 +1252,7 @@ mod tests { let burn: Option = tx_host_env::with(|env| { env.wl_storage.read(&burn_key).expect("read error") }); - assert_eq!(burn, Some(Amount::whole(100))); + assert_eq!(burn, Some(Amount::native_whole(100))); } #[test] @@ -1363,11 +1296,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // receive a packet with the message @@ -1393,7 +1322,7 @@ mod tests { let balance: Option = tx_host_env::with(|env| { env.wl_storage.read(&key).expect("read error") }); - assert_eq!(balance, Some(Amount::whole(100))); + assert_eq!(balance, Some(Amount::native_whole(100))); } #[test] @@ -1422,7 +1351,7 @@ mod tests { &token, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); - let val = Amount::whole(100).try_to_vec().unwrap(); + let val = Amount::native_whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { env.wl_storage .storage @@ -1455,11 +1384,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // receive a packet with the message @@ -1480,11 +1405,11 @@ mod tests { let balance: Option = tx_host_env::with(|env| { env.wl_storage.read(&key).expect("read error") }); - assert_eq!(balance, Some(Amount::whole(200))); + assert_eq!(balance, Some(Amount::native_whole(200))); let escrow: Option = tx_host_env::with(|env| { env.wl_storage.read(&escrow_key).expect("read error") }); - assert_eq!(escrow, Some(Amount::whole(0))); + assert_eq!(escrow, Some(Amount::native_whole(0))); } #[test] @@ -1513,7 +1438,7 @@ mod tests { &token, &address::Address::Internal(address::InternalAddress::IbcEscrow), ); - let val = Amount::whole(100).try_to_vec().unwrap(); + let val = Amount::native_whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { env.wl_storage .storage @@ -1551,11 +1476,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // receive a packet with the message @@ -1579,11 +1500,11 @@ mod tests { let balance: Option = tx_host_env::with(|env| { env.wl_storage.read(&key).expect("read error") }); - assert_eq!(balance, Some(Amount::whole(100))); + assert_eq!(balance, Some(Amount::native_whole(100))); let escrow: Option = tx_host_env::with(|env| { env.wl_storage.read(&escrow_key).expect("read error") }); - assert_eq!(escrow, Some(Amount::whole(0))); + assert_eq!(escrow, Some(Amount::native_whole(0))); } #[test] @@ -1650,11 +1571,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); @@ -1739,11 +1656,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 4df5ee78a0..5fa5d24d68 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -27,6 +27,5 @@ namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} namada_vm_env = {path = "../vm_env", default-features = false} borsh.workspace = true masp_primitives.workspace = true -rust_decimal.workspace = true sha2.workspace = true thiserror.workspace = true diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 25c4328272..27d2cd2962 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -1,5 +1,6 @@ //! Proof of Stake system integration with functions for transactions +use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; use namada_core::types::transaction::InitValidator; use namada_core::types::{key, token}; @@ -9,7 +10,6 @@ use namada_proof_of_stake::{ read_pos_params, unbond_tokens, unjail_validator, withdraw_tokens, }; pub use namada_proof_of_stake::{parameters, types}; -use rust_decimal::Decimal; use super::*; @@ -56,7 +56,7 @@ impl Ctx { pub fn change_validator_commission_rate( &mut self, validator: &Address, - rate: &Decimal, + rate: &Dec, ) -> TxResult { let current_epoch = self.get_block_epoch()?; change_validator_commission_rate(self, validator, *rate, current_epoch) diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 251d9a21f9..685a2e51a6 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -15,12 +15,12 @@ pub fn transfer( dest: &Address, token: &Address, sub_prefix: Option, - amount: Amount, + amount: DenominatedAmount, key: &Option, shielded_hash: &Option, shielded: &Option, ) -> TxResult { - if amount != Amount::default() { + if amount.amount != Amount::default() { let src_key = match &sub_prefix { Some(sub_prefix) => { let prefix = @@ -37,28 +37,31 @@ pub fn transfer( } None => token::balance_key(token, dest), }; - let src_bal: Option = match src { - Address::Internal(InternalAddress::IbcMint) => Some(Amount::max()), - Address::Internal(InternalAddress::IbcBurn) => { - log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => ctx.read(&src_key)?, - }; - let mut src_bal = src_bal.unwrap_or_else(|| { - log_string(format!("src {} has no balance", src_key)); - unreachable!() - }); - src_bal.spend(&amount); - let mut dest_bal: Amount = match dest { - Address::Internal(InternalAddress::IbcMint) => { - log_string("invalid transfer to the mint address"); - unreachable!() - } - _ => ctx.read(&dest_key)?.unwrap_or_default(), - }; - dest_bal.receive(&amount); if src != dest { + let src_bal: Option = match src { + Address::Internal(InternalAddress::IbcMint) => { + Some(Amount::max_signed()) + } + Address::Internal(InternalAddress::IbcBurn) => { + log_string("invalid transfer from the burn address"); + unreachable!() + } + _ => ctx.read(&src_key)?, + }; + let mut src_bal = src_bal.unwrap_or_else(|| { + log_string(format!("src {} has no balance", src_key)); + unreachable!() + }); + src_bal.spend(&amount.amount); + let mut dest_bal: Amount = match dest { + Address::Internal(InternalAddress::IbcMint) => { + log_string("invalid transfer to the mint address"); + unreachable!() + } + _ => ctx.read(&dest_key)?.unwrap_or_default(), + }; + dest_bal.receive(&amount.amount); + match src { Address::Internal(InternalAddress::IbcMint) => { ctx.write_temp(&src_key, src_bal)?; @@ -139,12 +142,12 @@ pub fn transfer_with_keys( dest_key: &storage::Key, amount: Amount, ) -> TxResult { - let src_owner = is_any_token_balance_key(src_key); + let src_owner = is_any_token_or_multitoken_balance_key(src_key); let src_bal: Option = match src_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { - Some(Amount::max()) + Some([_, Address::Internal(InternalAddress::IbcMint)]) => { + Some(Amount::max_signed()) } - Some(Address::Internal(InternalAddress::IbcBurn)) => { + Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { log_string("invalid transfer from the burn address"); unreachable!() } @@ -157,7 +160,7 @@ pub fn transfer_with_keys( src_bal.spend(&amount); let dest_owner = is_any_token_balance_key(dest_key); let mut dest_bal: Amount = match dest_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { + Some([_, Address::Internal(InternalAddress::IbcMint)]) => { log_string("invalid transfer to the mint address"); unreachable!() } @@ -165,13 +168,13 @@ pub fn transfer_with_keys( }; dest_bal.receive(&amount); match src_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { + Some([_, Address::Internal(InternalAddress::IbcMint)]) => { ctx.write_temp(src_key, src_bal)?; } _ => ctx.write(src_key, src_bal)?, } match dest_owner { - Some(Address::Internal(InternalAddress::IbcBurn)) => { + Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { ctx.write_temp(dest_key, dest_bal)?; } _ => ctx.write(dest_key, dest_bal)?, diff --git a/vp_prelude/src/token.rs b/vp_prelude/src/token.rs new file mode 100644 index 0000000000..1e302204a7 --- /dev/null +++ b/vp_prelude/src/token.rs @@ -0,0 +1,69 @@ +//! A fungible token validity predicate. + +use std::collections::BTreeSet; + +use namada_core::types::address::{self, Address, InternalAddress}; +use namada_core::types::storage::Key; +/// Vp imports and functions. +use namada_core::types::storage::KeySeg; +use namada_core::types::token; +pub use namada_core::types::token::*; + +use super::*; + +/// A token validity predicate. +pub fn vp( + ctx: &Ctx, + token: &Address, + keys_changed: &BTreeSet, + verifiers: &BTreeSet
, +) -> VpResult { + let mut change: Change = Change::default(); + for key in keys_changed.iter() { + let owner: Option<&Address> = + match token::is_multitoken_balance_key(token, key) { + Some((_, o)) => Some(o), + None => token::is_balance_key(token, key), + }; + match owner { + None => { + // Unknown changes to this address space are disallowed, but + // unknown changes anywhere else are permitted + if key.segments.get(0) == Some(&token.to_db_key()) { + return reject(); + } + } + Some(owner) => { + // accumulate the change + let pre: Amount = match owner { + Address::Internal(InternalAddress::IbcMint) => { + Amount::max_signed() + } + Address::Internal(InternalAddress::IbcBurn) => { + Amount::default() + } + _ => ctx.read_pre(key)?.unwrap_or_default(), + }; + let post: Amount = match owner { + Address::Internal(InternalAddress::IbcMint) => { + ctx.read_temp(key)?.unwrap_or_else(Amount::max_signed) + } + Address::Internal(InternalAddress::IbcBurn) => { + ctx.read_temp(key)?.unwrap_or_default() + } + _ => ctx.read_post(key)?.unwrap_or_default(), + }; + let this_change = post.change() - pre.change(); + change += this_change; + // make sure that the spender approved the transaction + if !(this_change.non_negative() + || verifiers.contains(owner) + || *owner == address::masp()) + { + return reject(); + } + } + } + } + Ok(change.is_zero()) +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 91d9705943..a476158ce5 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -995,6 +995,19 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "corosensei" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "libc", + "scopeguard", + "windows-sys 0.33.0", +] + [[package]] name = "cpufeatures" version = "0.2.7" @@ -1006,24 +1019,24 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" +checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" +checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" dependencies = [ "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli 0.25.0", + "gimli 0.26.2", "log", "regalloc", "smallvec", @@ -1032,31 +1045,30 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" +checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" dependencies = [ "cranelift-codegen-shared", - "cranelift-entity", ] [[package]] name = "cranelift-codegen-shared" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" +checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" [[package]] name = "cranelift-entity" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" +checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" [[package]] name = "cranelift-frontend" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" +checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ "cranelift-codegen", "log", @@ -1917,9 +1929,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", "indexmap", @@ -2510,6 +2522,17 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint", +] + [[package]] name = "impl-serde" version = "0.4.0" @@ -2595,6 +2618,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -2827,7 +2859,7 @@ dependencies = [ [[package]] name = "masp_note_encryption" version = "0.2.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "borsh", "chacha20 0.9.1", @@ -2840,7 +2872,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.9.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "aes", "bip0039", @@ -2870,7 +2902,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.9.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "bellman", "blake2b_simd", @@ -3087,13 +3119,10 @@ dependencies = [ "paste", "proptest", "prost", - "pwasm-utils", "rand 0.8.5", "rand_core 0.6.4", "rayon", "ripemd", - "rust_decimal", - "rust_decimal_macros", "serde", "serde_json", "sha2 0.9.9", @@ -3106,13 +3135,14 @@ dependencies = [ "tokio", "toml", "tracing", + "wasm-instrument", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", "wasmer-engine-dylib", "wasmer-engine-universal", "wasmer-vm", - "wasmparser 0.83.0", + "wasmparser 0.107.0", "zeroize", ] @@ -3129,25 +3159,26 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", + "impl-num-traits", "index-set", "itertools", "libsecp256k1 0.7.0", "masp_primitives", "namada_macros", + "num-traits", "proptest", "prost", "prost-types", "rand 0.8.5", "rand_core 0.6.4", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde", "serde_json", "sha2 0.9.9", @@ -3157,6 +3188,7 @@ dependencies = [ "thiserror", "tonic-build", "tracing", + "uint", "zeroize", ] @@ -3179,8 +3211,7 @@ dependencies = [ "namada_core", "once_cell", "proptest", - "rust_decimal", - "rust_decimal_macros", + "rand 0.8.5", "thiserror", "tracing", ] @@ -3210,8 +3241,6 @@ dependencies = [ "namada_vp_prelude", "prost", "regex", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3231,7 +3260,6 @@ dependencies = [ "namada_macros", "namada_proof_of_stake", "namada_vm_env", - "rust_decimal", "sha2 0.9.9", "thiserror", ] @@ -3854,16 +3882,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "pwasm-utils" -version = "0.20.0" -source = "git+https://github.com/heliaxdev/wasm-utils?tag=v0.20.0#782bfa7fb5e513b602e66af492cbc4cb1b06f2ba" -dependencies = [ - "byteorder", - "log", - "parity-wasm", -] - [[package]] name = "quanta" version = "0.10.1" @@ -4061,9 +4079,9 @@ dependencies = [ [[package]] name = "regalloc" -version = "0.0.31" +version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ "log", "rustc-hash", @@ -4207,21 +4225,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", - "borsh", "num-traits", "serde", ] -[[package]] -name = "rust_decimal_macros" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" -dependencies = [ - "quote", - "rust_decimal", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4694,9 +4701,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", "rand_core 0.6.4", @@ -5562,9 +5569,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle 2.4.1", @@ -5610,9 +5617,9 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" [[package]] name = "version_check" @@ -5759,11 +5766,20 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-instrument" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" +dependencies = [ + "parity-wasm", +] + [[package]] name = "wasmer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc7dff846db3f38f8ed0be4a009fdfeb729cf1f94a2c7fb6ff2fec01cefa110" +checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" dependencies = [ "cfg-if 1.0.0", "indexmap", @@ -5773,6 +5789,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-bindgen", + "wasmer-artifact", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-derive", @@ -5785,11 +5802,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmer-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aaf9428c29c1d8ad2ac0e45889ba8a568a835e33fd058964e5e500f2f7ce325" +dependencies = [ + "enumset", + "loupe", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-cache" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "834a0de78bf30b9bce61c4c236344b9d8f2f4a3b7713f8de8a8274fbc2d4e9d5" +checksum = "0def391ee1631deac5ac1e6ce919c07a5ccb936ad0fd44708cdc2365c49561a4" dependencies = [ "blake3", "hex", @@ -5799,9 +5829,9 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c91abf22b16dad3826ec0d0e3ec0a8304262a6c7a14e16528c536131b80e63d" +checksum = "e67a6cd866aed456656db2cfea96c18baabbd33f676578482b85c51e1ee19d2c" dependencies = [ "enumset", "loupe", @@ -5812,20 +5842,19 @@ dependencies = [ "target-lexicon", "thiserror", "wasmer-types", - "wasmer-vm", - "wasmparser 0.78.2", + "wasmparser 0.83.0", ] [[package]] name = "wasmer-compiler-cranelift" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7624a1f496b163139a7e0b442426cad805bec70486900287506f9d15a29323ab" +checksum = "48be2f9f6495f08649e4f8b946a2cbbe119faf5a654aa1457f9504a99d23dae0" dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "gimli 0.25.0", + "gimli 0.26.2", "loupe", "more-asserts", "rayon", @@ -5834,18 +5863,18 @@ dependencies = [ "tracing", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-compiler-singlepass" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b63c1538ffb4b0e09edaebfcac35c34141d5944c52f77d137cbe0b634bd40fa" +checksum = "29ca2a35204d8befa85062bc7aac259a8db8070b801b8a783770ba58231d729e" dependencies = [ "byteorder", "dynasm", "dynasmrt", + "gimli 0.26.2", "lazy_static", "loupe", "more-asserts", @@ -5853,14 +5882,13 @@ dependencies = [ "smallvec", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-derive" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "933b23b5cee0f58aa6c17c6de7e1f3007279357e0d555f22e24d6b395cfe7f89" +checksum = "00e50405cc2a2f74ff574584710a5f2c1d5c93744acce2ca0866084739284b51" dependencies = [ "proc-macro-error", "proc-macro2", @@ -5870,9 +5898,9 @@ dependencies = [ [[package]] name = "wasmer-engine" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" +checksum = "3f98f010978c244db431b392aeab0661df7ea0822343334f8f2a920763548e45" dependencies = [ "backtrace", "enumset", @@ -5885,6 +5913,7 @@ dependencies = [ "serde_bytes", "target-lexicon", "thiserror", + "wasmer-artifact", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -5892,9 +5921,9 @@ dependencies = [ [[package]] name = "wasmer-engine-dylib" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591683f3356ac31cc88aaecaf77ac2cc9f456014348b01af46c164f44f531162" +checksum = "ad0358af9c154724587731175553805648d9acb8f6657880d165e378672b7e53" dependencies = [ "cfg-if 1.0.0", "enum-iterator", @@ -5907,6 +5936,7 @@ dependencies = [ "serde", "tempfile", "tracing", + "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -5917,12 +5947,11 @@ dependencies = [ [[package]] name = "wasmer-engine-universal" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dccfde103e9b87427099a6de344b7c791574f307d035c8c7dbbc00974c1af0c1" +checksum = "440dc3d93c9ca47865a4f4edd037ea81bf983b5796b59b3d712d844b32dbef15" dependencies = [ "cfg-if 1.0.0", - "enum-iterator", "enumset", "leb128", "loupe", @@ -5930,16 +5959,33 @@ dependencies = [ "rkyv", "wasmer-compiler", "wasmer-engine", + "wasmer-engine-universal-artifact", "wasmer-types", "wasmer-vm", "winapi", ] +[[package]] +name = "wasmer-engine-universal-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f1db3f54152657eb6e86c44b66525ff7801dad8328fe677da48dd06af9ad41" +dependencies = [ + "enum-iterator", + "enumset", + "loupe", + "rkyv", + "thiserror", + "wasmer-artifact", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-object" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" +checksum = "8d831335ff3a44ecf451303f6f891175c642488036b92ceceb24ac8623a8fa8b" dependencies = [ "object 0.28.4", "thiserror", @@ -5949,12 +5995,15 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4deb854f178265a76b59823c41547d259c65da3687b606b0b9c12d80ab950e3e" +checksum = "39df01ea05dc0a9bab67e054c7cb01521e53b35a7bb90bd02eca564ed0b2667f" dependencies = [ + "backtrace", + "enum-iterator", "indexmap", "loupe", + "more-asserts", "rkyv", "serde", "thiserror", @@ -5962,38 +6011,47 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbc5c989cb14a102433927e630473da52f83d82c469acd5cfa8fc7efacc1e70" +checksum = "30d965fa61f4dc4cdb35a54daaf7ecec3563fbb94154a6c35433f879466247dd" dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", + "corosensei", "enum-iterator", "indexmap", + "lazy_static", "libc", "loupe", + "mach", "memoffset 0.6.5", "more-asserts", "region", "rkyv", + "scopeguard", "serde", "thiserror", + "wasmer-artifact", "wasmer-types", "winapi", ] [[package]] name = "wasmparser" -version = "0.78.2" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" +checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wasmparser" -version = "0.83.0" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" +checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" +dependencies = [ + "indexmap", + "semver 1.0.17", +] [[package]] name = "wast" @@ -6127,6 +6185,19 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +dependencies = [ + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -6202,6 +6273,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -6214,6 +6291,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6226,6 +6309,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6238,6 +6327,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6262,6 +6357,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" diff --git a/wasm/checksums.json b/wasm/checksums.json index 3d4916ecf1..fdcbe78350 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,21 +1,21 @@ { - "tx_bond.wasm": "tx_bond.b4916d2177dd1f9ca69e5cc99b5703adaf69be109694d2a333c6ac37ab12edd9.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.469463cd762b36ee1c63a734d8e4b4091964eb97b1414f7d6e0cd6a374e97b69.wasm", - "tx_ibc.wasm": "tx_ibc.8db1e3c43e75ab4175cf1cbee07b18f8bf17da7bcd5da050e581f96cec5f3a78.wasm", - "tx_init_account.wasm": "tx_init_account.15093b244594a80d9901474a4e8c5524983ef57fbd0dc54bc939a30e7946c2fb.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.fb71c72d31b747e1057b66bb21535a9b1d855b00a02c88edd0f85bdab827375e.wasm", - "tx_init_validator.wasm": "tx_init_validator.bfb5935d535f7a5b73cf56041cef4c9c84ead3604412915250844695f96f48dc.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.c5a7f7ceeb2ef53584d009c0b6872c1b904e66cdba1f0489ba9f68cc6a66eaee.wasm", - "tx_transfer.wasm": "tx_transfer.e36b981d1cd2a341d5a246a33c5bc6a6105d1e5d1f515c4e3b7ead2d5c7165bf.wasm", - "tx_unbond.wasm": "tx_unbond.a0e7026d046587cdf83e3cb7a031a6f761e6d84ec21edfabec3e90ae60394cc9.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.d9e31886b86c1f14d877bb070b0210b75cf936ddb99276ac48eb09b041b2eba6.wasm", - "tx_update_vp.wasm": "tx_update_vp.1859826f598d1e19c07df13e2501c69da6657d3168a43e5c86a3a7087a0d4d34.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d557b3e44a3a523d00d0b70df6de9974d7904bfeb1d3844c1a8b45a159b9404f.wasm", - "tx_withdraw.wasm": "tx_withdraw.de2968f1246eaefe2ea4a07ac01bf6691c88a8e2c63b13b7e8bfa7ed819dd297.wasm", - "vp_implicit.wasm": "vp_implicit.c9a519b03fee9074add41dd77637648116f779800dc8d54792774b62873ca9e4.wasm", - "vp_masp.wasm": "vp_masp.e01e9d15c877d8c47ef96520399c74dce80e840906337d048e628f544a092963.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.808fd709893c89fb112ffacf2fcffee8b387f59f3c0b4ac1710188d251608262.wasm", - "vp_token.wasm": "vp_token.51b63b1af3d3667cd337f32adfb6e811b707eb37cbfbb94dc0647e526f3a3123.wasm", - "vp_user.wasm": "vp_user.f24d32d8e14a168eedf1394aee7cdf9d5ebc928f030ee841fd9d4da80ef8b223.wasm", - "vp_validator.wasm": "vp_validator.e179dc2e2465a4b4ff2005b0a3dc400e988572574779d885b243460624097049.wasm" + "tx_bond.wasm": "tx_bond.58da593200f6cf53a01504885ad360aa09158939da840ae5c8f44cd31c842f98.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.bd641efba60253831ba729f06786e7c8e26e20ca61477e6f7371ae0e3bd71643.wasm", + "tx_ibc.wasm": "tx_ibc.619f7172f1daea9ba0b5c1bea4c2daebbc4fb8c0bfb76a33bc142931da11226c.wasm", + "tx_init_account.wasm": "tx_init_account.51e1677f5a2dfe956604ed9136a09b58f32db08fdecdcf1243780ad4ade1fc2e.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.260b842de0ad056c211cd831440bf8d2cadbae86e7aed0bda31a167fed5d0f67.wasm", + "tx_init_validator.wasm": "tx_init_validator.73e87f7ff25d48a5088e7500f70828933c30899651cc46d036ae13b638414f78.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.883a0e48c94c76f22b99d6f0e8452683f14f73b6acd9ff94e1b9b37055ae6084.wasm", + "tx_transfer.wasm": "tx_transfer.a2c7ef548e8f9ab3a3f6278ce648f42376f0c839e9851de6d28af56cffb86ae3.wasm", + "tx_unbond.wasm": "tx_unbond.869f362d5f1adb35c99099fecbad1072a7b8faf3a28b94e6a97b322a1ea31881.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.c75e5e3b866bd5ec992d7914653ecb93c654f3cf1deba9e251662e5c4e2207f2.wasm", + "tx_update_vp.wasm": "tx_update_vp.40f1929d3720bd27a1d46a3d4d19f8c95a61b41c1b75b5ab01959500b55e056c.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.50141041c105eaed2e7d4c82ecab6fde5f6e280c1975f8801751cedee92bf3c5.wasm", + "tx_withdraw.wasm": "tx_withdraw.0bc58f5dd3cd8d1ae8952d1a03de072a2a8a48e097f5c62a9ee619ec7a75185a.wasm", + "vp_implicit.wasm": "vp_implicit.38024a6f0bdfc97821bac256230f36dadb341bc39b29ba18e4824c4f3cd50fa8.wasm", + "vp_masp.wasm": "vp_masp.f0d9b224820a3c4d4d2e7306e903d82cd9f35f60ad0f7fc9de48b811f73a84f2.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.86460e127cd7867092e881612302b844c072141932b5fd58959d9ae37be46fd7.wasm", + "vp_token.wasm": "vp_token.8432b994e030b7065f2e4a871c7c9e9deb07e158873a9942daf3d1fb51d9eebc.wasm", + "vp_user.wasm": "vp_user.46f9544a32f0e59509fe571b66520797af40ea66284feb02dd1f0abfdebdac24.wasm", + "vp_validator.wasm": "vp_validator.d5db921d254fdb70545391ab9767fe9ab731070949120fcf40d0c9ea0f20fb13.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 49a47c095c..ef3b7dfa5e 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -40,8 +40,8 @@ once_cell = {version = "1.8.0", optional = true} rust_decimal = {version = "=1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } -# branch = "murisi/namada-integration" -masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd", optional = true } +#masp_proofs = { git = "https://github.com/anoma/masp", rev = "64caae74dc71dd20e6e7fbf3d87770bffe5c4789", optional = true } +masp_primitives = { git = "https://github.com/anoma/masp", rev = "9320c6b69b5d2e97134866871e960f0a31703813", optional = true } ripemd = "0.1" [dev-dependencies] diff --git a/wasm/wasm_source/proptest-regressions/tx_bond.txt b/wasm/wasm_source/proptest-regressions/tx_bond.txt deleted file mode 100644 index 3a88756618..0000000000 --- a/wasm/wasm_source/proptest-regressions/tx_bond.txt +++ /dev/null @@ -1 +0,0 @@ -cc e54347c5114ef29538127ba9ad68d1572af839ec63c015318fc0827818853a22 diff --git a/wasm/wasm_source/proptest-regressions/tx_unbond.txt b/wasm/wasm_source/proptest-regressions/tx_unbond.txt deleted file mode 100644 index 8c589d1abd..0000000000 --- a/wasm/wasm_source/proptest-regressions/tx_unbond.txt +++ /dev/null @@ -1 +0,0 @@ -cc f22e874350910b197cb02a4a07ec5bef18e16c0d1a39eaabaee43d1fc05ce11d diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 35e8ba7fac..29546f98e5 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -23,6 +23,7 @@ mod tests { read_total_stake, read_validator_stake, }; use namada::proto::{Code, Data, Signature, Tx}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; use namada_tests::log::test; @@ -39,7 +40,6 @@ mod tests { use namada_tx_prelude::token; use namada_vp_prelude::proof_of_stake::WeightedValidator; use proptest::prelude::*; - use rust_decimal; use super::*; @@ -67,12 +67,18 @@ mod tests { key: key::common::SecretKey, pos_params: PosParams, ) -> TxResult { + // Remove the validator stake threshold for simplicity + let pos_params = PosParams { + validator_stake_threshold: token::Amount::default(), + ..pos_params + }; + dbg!(&initial_stake, &bond); let is_delegation = matches!(&bond.source, Some(source) if *source != bond.validator); let consensus_key = key::testing::keypair_1().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).expect("Cannot fail"); + let max_commission_rate_change = Dec::new(1, 2).expect("Cannot fail"); let genesis_validators = [GenesisValidator { address: bond.validator.clone(), @@ -102,11 +108,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &key, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &key, ))); let signed_tx = tx.clone(); @@ -194,7 +196,7 @@ mod tests { // Check that the validator set and deltas are unchanged before pipeline // length and that they are updated between the pipeline and // unbonding lengths - if bond.amount == token::Amount::from(0) { + if bond.amount.is_zero() { // None of the optional storage fields should have been updated assert_eq!(epoched_validator_set_pre, epoched_validator_set_post); assert_eq!( @@ -225,7 +227,7 @@ mod tests { ..=pos_params.unbonding_len as usize { let expected_stake = - i128::from(initial_stake) + i128::from(bond.amount); + initial_stake.change() + bond.amount.change(); assert_eq!( epoched_validator_stake_post[epoch], token::Amount::from_change(expected_stake), @@ -349,7 +351,7 @@ mod tests { // Generate initial stake (initial_stake in token::testing::arb_amount_ceiled((i64::MAX/8) as u64)) // Use the initial stake to limit the bond amount - (bond in arb_bond(((i64::MAX/8) as u64) - u64::from(initial_stake)), + (bond in arb_bond(((i64::MAX/8) as u64) - u128::try_from(initial_stake).unwrap() as u64), // Use the generated initial stake too initial_stake in Just(initial_stake), ) -> (token::Amount, transaction::pos::Bond) { diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 7668114c79..06efc9bbf7 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -11,7 +11,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { validator, new_rate, } = transaction::pos::CommissionChange::try_from_slice(&data[..]) - .wrap_err("failed to decode Decimal value")?; + .wrap_err("failed to decode Dec value")?; ctx.change_validator_commission_rate(&validator, &new_rate) } @@ -22,6 +22,7 @@ mod tests { use namada::ledger::pos::{PosParams, PosVP}; use namada::proof_of_stake::validator_commission_rate_handle; use namada::proto::{Code, Data, Signature, Tx}; + use namada::types::dec::{Dec, POS_DECIMAL_PRECISION}; use namada::types::storage::Epoch; use namada::types::transaction::TxType; use namada_tests::log::test; @@ -35,8 +36,6 @@ mod tests { use namada_tx_prelude::token; use namada_vp_prelude::proof_of_stake::GenesisValidator; use proptest::prelude::*; - use rust_decimal::prelude::ToPrimitive; - use rust_decimal::Decimal; use super::*; @@ -61,8 +60,8 @@ mod tests { } fn test_tx_change_validator_commission_aux( - initial_rate: Decimal, - max_change: Decimal, + initial_rate: Dec, + max_change: Dec, commission_change: transaction::pos::CommissionChange, key: key::common::SecretKey, pos_params: PosParams, @@ -70,7 +69,7 @@ mod tests { let consensus_key = key::testing::keypair_1().ref_to(); let genesis_validators = [GenesisValidator { address: commission_change.validator.clone(), - tokens: token::Amount::from(1_000_000), + tokens: token::Amount::from_uint(1_000_000, 0).unwrap(), consensus_key, commission_rate: initial_rate, max_commission_rate_change: max_change, @@ -84,11 +83,7 @@ mod tests { tx.set_data(Data::new(tx_data)); tx.set_code(Code::new(tx_code)); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &key, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &key, ))); let signed_tx = tx.clone(); @@ -97,7 +92,7 @@ mod tests { let commission_rate_handle = validator_commission_rate_handle(&commission_change.validator); - let mut commission_rates_pre = Vec::>::new(); + let mut commission_rates_pre = Vec::>::new(); for epoch in Epoch::default().iter_range(pos_params.unbonding_len + 1) { commission_rates_pre.push(commission_rate_handle.get( ctx(), @@ -156,35 +151,33 @@ mod tests { Ok(()) } - fn arb_rate(min: Decimal, max: Decimal) -> impl Strategy { - let int_min: u64 = (min * scale()).to_u64().unwrap_or_default(); - let int_max: u64 = (max * scale()).to_u64().unwrap(); - (int_min..=int_max).prop_map(|num| Decimal::from(num) / scale()) + fn arb_rate(min: Dec, max: Dec) -> impl Strategy { + let int_min: i128 = (min * scale()).try_into().unwrap(); + let int_max: i128 = (max * scale()).try_into().unwrap(); + (int_min..=int_max).prop_map(|num| { + Dec::new(num, POS_DECIMAL_PRECISION).unwrap() / scale() + }) } fn arb_new_rate( - rate_pre: Decimal, - max_change: Decimal, - ) -> impl Strategy { - assert!(max_change > Decimal::ZERO); - + rate_pre: Dec, + max_change: Dec, + ) -> impl Strategy { + assert!(max_change > Dec::zero()); // Arbitrary non-zero change - let arb_change = |ceil: Decimal| { + let arb_change = |ceil: Dec| { // Clamp the `ceil` to `max_change` and convert to an int - let ceil = (cmp::min(max_change, ceil) * scale()) - .abs() - .to_u64() - .unwrap(); - (0..ceil).prop_map(|c| + let ceil = (cmp::min(max_change, ceil) * scale()).abs().as_u128(); + (1..ceil).prop_map(|c| // Convert back from an int - Decimal::from(c) / scale()) + Dec::new(c as i128, POS_DECIMAL_PRECISION).unwrap() / scale()) }; // Addition let arb_add = || { arb_change( // Addition must not go over 1 - Decimal::ONE - rate_pre, + Dec::one() - rate_pre, ) .prop_map(move |c| rate_pre + c) }; @@ -198,9 +191,9 @@ mod tests { }; // Add or subtract from the previous rate - if rate_pre == Decimal::ZERO { + if rate_pre == Dec::zero() { arb_add().boxed() - } else if rate_pre == Decimal::ONE { + } else if rate_pre == Dec::one() { arb_sub().boxed() } else { prop_oneof![arb_add(), arb_sub()].boxed() @@ -208,13 +201,13 @@ mod tests { } fn arb_commission_change( - rate_pre: Decimal, - max_change: Decimal, + rate_pre: Dec, + max_change: Dec, ) -> impl Strategy { ( arb_established_address(), - if max_change == Decimal::ZERO { - Just(Decimal::ZERO).boxed() + if max_change.is_zero() { + Just(Dec::zero()).boxed() } else { arb_new_rate(rate_pre, max_change).boxed() }, @@ -228,11 +221,12 @@ mod tests { } fn arb_commission_info() - -> impl Strategy + -> impl Strategy { - let min = Decimal::ZERO; - let max = Decimal::ONE; - (arb_rate(min, max), arb_rate(min, max)).prop_flat_map( + let min = Dec::zero(); + let max = Dec::one(); + let non_zero_min = Dec::one() / scale(); + (arb_rate(min, max), arb_rate(non_zero_min, max)).prop_flat_map( |(rate, max_change)| { ( Just(rate), @@ -243,7 +237,7 @@ mod tests { ) } - fn scale() -> Decimal { - Decimal::from(100_000) + fn scale() -> Dec { + Dec::new(100_000, 0).unwrap() } } diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index 2507a18d21..c0da1d9316 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -3,13 +3,28 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { - let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { + let data = tx.data().ok_or_err_msg("Missing data")?; let tx_data = transaction::governance::InitProposalData::try_from_slice(&data[..]) .wrap_err("failed to decode InitProposalData")?; + // Get the content from the referred to section + let content = tx + .get_section(&tx_data.content) + .ok_or_err_msg("Missing proposal content")? + .extra_data() + .ok_or_err_msg("Missing full proposal content")?; + // Get the code from the referred to section + let code = match tx_data.r#type { + transaction::governance::ProposalType::Default(Some(hash)) => Some( + tx.get_section(&hash) + .ok_or_err_msg("Missing proposal code")? + .extra_data() + .ok_or_err_msg("Missing full proposal code")?, + ), + _ => None, + }; log_string("apply_tx called to create a new governance proposal"); - governance::init_proposal(ctx, tx_data) + governance::init_proposal(ctx, tx_data, content, code) } diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index 1727035d2f..d2e3dc314f 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -25,7 +25,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { .map(|hash| { signed .get_section(hash) - .and_then(Section::masp_tx) + .and_then(|x| x.as_ref().masp_tx()) .ok_or_err_msg("unable to find shielded section") }) .transpose()?; diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 5b4c5f3859..9d2787bee5 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -24,6 +24,7 @@ mod tests { read_total_stake, read_validator_stake, unbond_handle, }; use namada::proto::{Code, Data, Signature, Tx}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; use namada_tests::log::test; @@ -64,13 +65,19 @@ mod tests { key: key::common::SecretKey, pos_params: PosParams, ) -> TxResult { + // Remove the validator stake threshold for simplicity + let pos_params = PosParams { + validator_stake_threshold: token::Amount::default(), + ..pos_params + }; + dbg!(&initial_stake, &unbond); let is_delegation = matches!( &unbond.source, Some(source) if *source != unbond.validator); let consensus_key = key::testing::keypair_1().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).expect("Cannot fail"); + let max_commission_rate_change = Dec::new(1, 2).expect("Cannot fail"); let genesis_validators = [GenesisValidator { address: unbond.validator.clone(), @@ -125,11 +132,7 @@ mod tests { tx.set_data(Data::new(tx_data)); tx.set_code(Code::new(tx_code)); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &key, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &key, ))); let signed_tx = tx.clone(); @@ -211,7 +214,7 @@ mod tests { let expected_amount_before_pipeline = if is_delegation { // When this is a delegation, there will be no bond until pipeline - 0.into() + token::Amount::default() } else { // Before pipeline offset, there can only be self-bond initial_stake @@ -285,7 +288,7 @@ mod tests { { let epoch = pos_params.unbonding_len + 1; let expected_stake = - i128::from(initial_stake) - i128::from(unbond.amount); + initial_stake.change() - unbond.amount.change(); assert_eq!( read_validator_stake( ctx(), @@ -418,7 +421,8 @@ mod tests { token::testing::arb_amount_ceiled((i64::MAX / 8) as u64).prop_flat_map( |initial_stake| { // Use the initial stake to limit the bond amount - let unbond = arb_unbond(u64::from(initial_stake)); + let unbond = + arb_unbond(u128::try_from(initial_stake).unwrap() as u64); // Use the generated initial stake too too (Just(initial_stake), unbond) }, diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 267f180c94..32e56be462 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -13,7 +13,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let slashed = ctx.withdraw_tokens(withdraw.source.as_ref(), &withdraw.validator)?; if slashed != token::Amount::default() { - debug_log!("New withdrawal slashed for {}", slashed); + debug_log!("New withdrawal slashed for {}", slashed.to_string_native()); } Ok(()) } @@ -23,6 +23,7 @@ mod tests { use namada::ledger::pos::{GenesisValidator, PosParams, PosVP}; use namada::proof_of_stake::unbond_handle; use namada::proto::{Code, Data, Signature, Tx}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; use namada_tests::log::test; @@ -69,11 +70,17 @@ mod tests { key: key::common::SecretKey, pos_params: PosParams, ) -> TxResult { + // Remove the validator stake threshold for simplicity + let pos_params = PosParams { + validator_stake_threshold: token::Amount::default(), + ..pos_params + }; + let is_delegation = matches!( &withdraw.source, Some(source) if *source != withdraw.validator); let consensus_key = key::testing::keypair_1().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).expect("Cannot fail"); + let max_commission_rate_change = Dec::new(1, 2).expect("Cannot fail"); let genesis_validators = [GenesisValidator { address: withdraw.validator.clone(), @@ -169,11 +176,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &key, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &key, ))); let signed_tx = tx.clone(); @@ -236,7 +239,7 @@ mod tests { // stake let unbonded_amount = token::testing::arb_amount_non_zero_ceiled( - initial_stake.into(), + u128::try_from(initial_stake).unwrap() as u64, ); // Use the generated initial stake too too (Just(initial_stake), unbonded_amount) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index fa5b7c8945..7524a96e65 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -18,7 +18,9 @@ use once_cell::unsync::Lazy; enum KeyType<'a> { /// Public key - written once revealed Pk(&'a Address), - Token(&'a Address), + Token { + owner: &'a Address, + }, PoS, GovernanceVote(&'a Address), Unknown, @@ -28,12 +30,12 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = key::is_pk_key(key) { Self::Pk(address) - } else if let Some(address) = token::is_any_token_balance_key(key) { - Self::Token(address) - } else if let Some((_, address)) = + } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { + Self::Token { owner } + } else if let Some((_, [_, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token(address) + Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -68,7 +70,10 @@ fn validate_tx( let pk = key::get(ctx, &addr); match pk { Ok(Some(pk)) => tx_data - .verify_signature(&pk, tx_data.data_sechash()) + .verify_signature( + &pk, + &[*tx_data.data_sechash(), *tx_data.code_sechash()], + ) .is_ok(), _ => false, } @@ -106,7 +111,7 @@ fn validate_tx( } true } - KeyType::Token(owner) => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -114,11 +119,13 @@ fn validate_tx( ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || *valid_sig; + let valid = change.non_negative() || *valid_sig; + let sign = if change.non_negative() { "" } else { "-" }; debug_log!( - "token key: {}, change: {}, valid_sig: {}, valid \ + "token key: {}, change: {}{:?}, valid_sig: {}, valid \ modification: {}", key, + sign, change, *valid_sig, valid @@ -194,6 +201,7 @@ mod tests { // Use this as `#[test]` annotation to enable logging use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::proto::{Code, Data, Signature}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; use namada_test_utils::TestWasms; @@ -202,7 +210,7 @@ mod tests { use namada_tests::tx::{self, tx_host_env, TestTxEnv}; use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; - use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_tx_prelude::{storage_api, StorageWrite, TxEnv}; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -333,15 +341,26 @@ mod tests { let vp_owner: Address = (&public_key).into(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Credit the tokens to the source before running the transaction to be // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -379,10 +398,10 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -402,9 +421,9 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -412,7 +431,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); - + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -444,10 +470,10 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -467,9 +493,9 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -477,6 +503,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); @@ -493,8 +527,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -520,7 +555,7 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -528,6 +563,19 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); + + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -570,7 +618,7 @@ mod tests { let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -578,9 +626,21 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -600,8 +660,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -628,7 +689,7 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); @@ -636,6 +697,19 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); + + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -754,7 +828,8 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); - tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &secret_key))); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash()], &secret_key))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -841,7 +916,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -890,7 +965,7 @@ mod tests { tx.set_code(Code::from_hash(vp_hash)); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &secret_key, ))); let signed_tx = tx.clone(); diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index b3ccda6286..6fa86b510c 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use masp_primitives::asset_type::AssetType; -use masp_primitives::transaction::components::{Amount, TxOut}; +use masp_primitives::transaction::components::Amount; /// Multi-asset shielded pool VP. use namada_vp_prelude::address::masp; use namada_vp_prelude::storage::Epoch; @@ -10,9 +10,14 @@ use ripemd::{Digest, Ripemd160}; /// Generates the current asset type given the current epoch and an /// unique token address -fn asset_type_from_epoched_address(epoch: Epoch, token: &Address) -> AssetType { +fn asset_type_from_epoched_address( + epoch: Epoch, + token: &Address, + sub_prefix: String, + denom: token::MaspDenom, +) -> AssetType { // Timestamp the chosen token with the current epoch - let token_bytes = (token, epoch.0) + let token_bytes = (token, sub_prefix, denom, epoch.0) .try_to_vec() .expect("token should serialize"); // Generate the unique asset identifier from the unique token address @@ -49,7 +54,7 @@ fn valid_transfer_amount( transparented value {}", unshielded_transfer_value, reporeted_transparent_value - ) + ); } res } @@ -58,11 +63,21 @@ fn valid_transfer_amount( fn convert_amount( epoch: Epoch, token: &Address, + sub_prefix: &Option, val: token::Amount, + denom: token::MaspDenom, ) -> (AssetType, Amount) { - let asset_type = asset_type_from_epoched_address(epoch, token); + let asset_type = asset_type_from_epoched_address( + epoch, + token, + sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + ); // Combine the value and unit into one amount - let amount = Amount::from_nonnegative(asset_type, u64::from(val)) + let amount = Amount::from_nonnegative(asset_type, denom.denominate(&val)) .expect("invalid value or asset type for amount"); (asset_type, amount) } @@ -94,7 +109,7 @@ fn validate_tx( .map(|hash| { signed .get_section(hash) - .and_then(Section::masp_tx) + .and_then(|x| x.as_ref().masp_tx()) .ok_or_err_msg("unable to find shielded section") }) .transpose()?; @@ -108,18 +123,22 @@ fn validate_tx( // Note that the asset type is timestamped so shields // where the shielded value has an incorrect timestamp // are automatically rejected - let (_transp_asset, transp_amt) = convert_amount( - ctx.get_block_epoch().unwrap(), - &transfer.token, - transfer.amount, - ); - - // Non-masp sources add to transparent tx pool - transparent_tx_pool += transp_amt; + for denom in token::MaspDenom::iter() { + let (_transp_asset, transp_amt) = convert_amount( + ctx.get_block_epoch().unwrap(), + &transfer.token, + &transfer.sub_prefix, + transfer.amount.into(), + denom, + ); + + // Non-masp sources add to transparent tx pool + transparent_tx_pool += transp_amt; + } } else { // Handle shielded input // The following boundary conditions must be satisfied - // 1. Zero transparent inupt + // 1. Zero transparent input // 2. the transparent transaction value pool's amount must equal the // containing wrapper transaction's fee amount // Satisfies 1. @@ -138,7 +157,7 @@ fn validate_tx( if transfer.target != masp() { // Handle transparent output // The following boundary conditions must be satisfied - // 1. One transparent output + // 1. One to 4 transparent outputs // 2. Asset type must be properly derived // 3. Value from the output must be the same as the containing // transfer @@ -155,55 +174,90 @@ fn validate_tx( be 1 but is {}", transp_bundle.vin.len() ); + return reject(); } - - let out: &TxOut = &transp_bundle.vout[0]; - - let expected_asset_type: AssetType = - asset_type_from_epoched_address( - ctx.get_block_epoch().unwrap(), - &transfer.token, + let out_length = transp_bundle.vout.len(); + if !(1..=4).contains(&out_length) { + debug_log!( + "Transparent output to a transaction to the masp must be \ + beteween 1 and 4 but is {}", + transp_bundle.vin.len() ); - // Satisfies 2. and 3. - if !(valid_asset_type(&expected_asset_type, &out.asset_type) - && valid_transfer_amount( - out.value as u64, - u64::from(transfer.amount), - )) - { return reject(); } + let mut outs = transp_bundle.vout.iter(); + let mut valid_count = 0; + for denom in token::MaspDenom::iter() { + let out = match outs.next() { + Some(out) => out, + None => continue, + }; - let (_transp_asset, transp_amt) = convert_amount( - ctx.get_block_epoch().unwrap(), - &transfer.token, - transfer.amount, - ); + let expected_asset_type: AssetType = + asset_type_from_epoched_address( + ctx.get_block_epoch().unwrap(), + &transfer.token, + transfer + .sub_prefix + .as_ref() + .map(|k| k.to_string()) + .unwrap_or_default(), + denom, + ); - // Non-masp destinations subtract from transparent tx pool - transparent_tx_pool -= transp_amt; + // Satisfies 2. and 3. + if !valid_asset_type(&expected_asset_type, &out.asset_type) { + // we don't know which masp denoms are necessary apriori. + // This is encoded via the asset types. + continue; + } + if !valid_transfer_amount( + out.value as u64, + denom.denominate(&transfer.amount.amount), + ) { + return reject(); + } - // Satisfies 4. - let target_enc = transfer - .target - .try_to_vec() - .expect("target address encoding"); + let (_transp_asset, transp_amt) = convert_amount( + ctx.get_block_epoch().unwrap(), + &transfer.token, + &transfer.sub_prefix, + transfer.amount.amount, + denom, + ); - let hash = Ripemd160::digest(sha256(&target_enc).as_slice()); + // Non-masp destinations subtract from transparent tx pool + transparent_tx_pool -= transp_amt; - if <[u8; 20]>::from(hash) != out.address.0 { - debug_log!( - "the public key of the output account does not match the \ - transfer target" - ); + // Satisfies 4. + let target_enc = transfer + .target + .try_to_vec() + .expect("target address encoding"); + + let hash = Ripemd160::digest(sha256(&target_enc).as_slice()); + + if <[u8; 20]>::from(hash) != out.address.0 { + debug_log!( + "the public key of the output account does not match \ + the transfer target" + ); + return reject(); + } + valid_count += 1; + } + // one or more of the denoms in the batch failed to verify + // the asset derivation. + if valid_count != out_length { return reject(); } } else { // Handle shielded output // The following boundary conditions must be satisfied // 1. Zero transparent output + // Satisfies 1. if let Some(transp_bundle) = shielded_tx.transparent_bundle() { if !transp_bundle.vout.is_empty() { diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 430cb55b21..626d3be010 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -29,7 +29,10 @@ fn validate_tx( let pk = key::get(ctx, &addr); match pk { Ok(Some(pk)) => tx_data - .verify_signature(&pk, tx_data.data_sechash()) + .verify_signature( + &pk, + &[*tx_data.data_sechash(), *tx_data.code_sechash()], + ) .is_ok(), _ => false, } @@ -40,7 +43,8 @@ fn validate_tx( } for key in keys_changed.iter() { - let is_valid = if let Some(owner) = token::is_any_token_balance_key(key) + let is_valid = if let Some([_, owner]) = + token::is_any_token_balance_key(key) { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -48,7 +52,7 @@ fn validate_tx( ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); - if change < 0 { + if !change.non_negative() { // Allow to withdraw without a sig if there's a valid PoW if ctx.has_valid_pow() { let max_free_debit = @@ -144,7 +148,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -153,6 +157,11 @@ mod tests { // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -252,7 +261,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -293,12 +302,12 @@ mod tests { // Init the VP let vp_owner = address::testing::established_address_1(); let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(amount); + let amount = token::Amount::from_uint(amount, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -307,6 +316,10 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); tx_env.commit_genesis(); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into() + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -334,13 +347,13 @@ mod tests { // Init the VP let vp_owner = address::testing::established_address_1(); let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let target = address::testing::established_address_2(); let target_key = key::testing::keypair_1(); let token = address::nam(); - let amount = token::Amount::from(amount); + let amount = token::Amount::from_uint(amount, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -348,6 +361,8 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom(&mut tx_env.wl_storage, &token, None, token::NATIVE_MAX_DECIMAL_PLACES.into()).unwrap(); tx_env.commit_genesis(); // Construct a PoW solution like a client would @@ -355,6 +370,11 @@ mod tests { let solution = challenge.solve(); let solution_bytes = solution.try_to_vec().unwrap(); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Don't call `Solution::invalidate_if_valid` - this is done by the @@ -370,7 +390,7 @@ mod tests { vp_env.has_valid_pow = true; let mut tx_data = Tx::new(TxType::Raw); tx_data.set_data(Data::new(solution_bytes)); - tx_data.add_section(Section::Signature(Signature::new(tx_data.data_sechash(), &target_key))); + tx_data.add_section(Section::Signature(Signature::new(vec![*tx_data.data_sechash()], &target_key))); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -391,7 +411,7 @@ mod tests { // Init the VP let difficulty = testnet_pow::Difficulty::try_new(0).unwrap(); - let withdrawal_limit = token::Amount::from(MAX_FREE_DEBIT as u64); + let withdrawal_limit = token::Amount::from_uint(MAX_FREE_DEBIT as u64, 0).unwrap(); testnet_pow::init_faucet_storage(&mut tx_env.wl_storage, &vp_owner, difficulty, withdrawal_limit).unwrap(); let keypair = key::testing::keypair_1(); @@ -417,7 +437,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash()], &keypair))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 8ccc2c9053..29b639bd56 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -51,7 +51,7 @@ fn token_checks( keys_touched: &BTreeSet, verifiers: &BTreeSet
, ) -> VpResult { - let mut change: token::Change = 0; + let mut change = token::Change::default(); for key in keys_touched.iter() { let owner: Option<&Address> = token::is_balance_key(token, key) .or_else(|| { @@ -77,36 +77,45 @@ fn token_checks( } Some(owner) => { // accumulate the change - let pre: token::Amount = match owner { + let pre: token::Change = match owner { Address::Internal(InternalAddress::IbcMint) => { - token::Amount::max() + token::Change::maximum() } Address::Internal(InternalAddress::IbcBurn) => { - token::Amount::default() + token::Change::default() } - _ => ctx.read_pre(key)?.unwrap_or_default(), + _ => ctx + .read_pre::(key)? + .unwrap_or_default() + .change(), }; - let post: token::Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - ctx.read_temp(key)?.unwrap_or_else(token::Amount::max) - } - Address::Internal(InternalAddress::IbcBurn) => { - ctx.read_temp(key)?.unwrap_or_default() - } - _ => ctx.read_post(key)?.unwrap_or_default(), + let post: token::Change = match owner { + Address::Internal(InternalAddress::IbcMint) => ctx + .read_temp::(key)? + .map(|x| x.change()) + .unwrap_or_else(token::Change::maximum), + Address::Internal(InternalAddress::IbcBurn) => ctx + .read_temp::(key)? + .unwrap_or_default() + .change(), + _ => ctx + .read_post::(key)? + .unwrap_or_default() + .change(), }; - let this_change = post.change() - pre.change(); + let this_change = post - pre; change += this_change; // make sure that the spender approved the transaction - if this_change < 0 - && !(verifiers.contains(owner) || *owner == address::masp()) + if !(this_change.non_negative() + || verifiers.contains(owner) + || *owner == address::masp()) { return reject(); } } } } - Ok(change == 0) + Ok(change.is_zero()) } #[cfg(test)] @@ -129,7 +138,8 @@ mod tests { let token = address::nam(); let src = address::testing::established_address_1(); let dest = address::testing::established_address_2(); - let total_supply = token::Amount::from(10_098_123); + let total_supply = + token::Amount::from_uint(10_098_123, 0).expect("Test failed"); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&token, &src, &dest]); @@ -147,7 +157,7 @@ mod tests { vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { // Apply a transfer - let amount = token::Amount::from(100); + let amount = token::Amount::from_uint(100, 0).expect("Test failed"); token::transfer(tx::ctx(), &token, &src, &dest, amount).unwrap(); }); @@ -172,7 +182,8 @@ mod tests { let token = address::nam(); let src = address::testing::established_address_1(); let dest = address::testing::established_address_2(); - let total_supply = token::Amount::from(10_098_123); + let total_supply = + token::Amount::from_uint(10_098_123, 0).expect("Test failed"); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&token, &src, &dest]); @@ -190,8 +201,10 @@ mod tests { vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { // Apply a transfer - let amount_in = token::Amount::from(100); - let amount_out = token::Amount::from(900); + let amount_in = + token::Amount::from_uint(100, 0).expect("Test failed"); + let amount_out = + token::Amount::from_uint(900, 0).expect("Test failed"); let src_key = token::balance_key(&token, &src); let src_balance = @@ -226,7 +239,8 @@ mod tests { let mut tx_env = TestTxEnv::default(); let token = address::nam(); let owner = address::testing::established_address_1(); - let total_supply = token::Amount::from(10_098_123); + let total_supply = + token::Amount::from_uint(10_098_123, 0).expect("Test failed"); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&token, &owner]); @@ -253,7 +267,8 @@ mod tests { tx::ctx() .write( &total_supply_key, - current_supply + token::Amount::from(1), + current_supply + + token::Amount::from_uint(1, 0).expect("Test failed"), ) .unwrap(); }); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 79132efaf2..00ee7ae8d0 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -14,7 +14,7 @@ use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { - Token(&'a Address), + Token { owner: &'a Address }, PoS, Vp(&'a Address), Masp, @@ -24,12 +24,12 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = token::is_any_token_balance_key(key) { - Self::Token(address) - } else if let Some((_, address)) = + if let Some([_, owner]) = token::is_any_token_balance_key(key) { + Self::Token { owner } + } else if let Some((_, [_, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token(address) + Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -68,7 +68,10 @@ fn validate_tx( let pk = key::get(ctx, &addr); match pk { Ok(Some(pk)) => tx_data - .verify_signature(&pk, tx_data.data_sechash()) + .verify_signature( + &pk, + &[*tx_data.data_sechash(), *tx_data.code_sechash()], + ) .is_ok(), _ => false, } @@ -81,7 +84,7 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token(owner) => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -89,9 +92,10 @@ fn validate_tx( ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || addr == masp() || *valid_sig; + let valid = + change.non_negative() || addr == masp() || *valid_sig; debug_log!( - "token key: {}, change: {}, valid_sig: {}, valid \ + "token key: {}, change: {:?}, valid_sig: {}, valid \ modification: {}", key, change, @@ -185,6 +189,7 @@ mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::proto::{Code, Data, Signature}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; use namada_test_utils::TestWasms; @@ -227,7 +232,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -235,7 +240,19 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -275,15 +292,27 @@ mod tests { let vp_owner = address::testing::established_address_1(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -325,7 +354,7 @@ mod tests { let public_key = keypair.ref_to(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -333,9 +362,22 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -357,7 +399,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -379,10 +421,10 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -402,12 +444,20 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -444,10 +494,10 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = rust_decimal::Decimal::new(5, 2); - let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -467,12 +517,20 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -495,7 +553,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -520,7 +578,7 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); @@ -529,6 +587,11 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { tx::ctx().insert_verifier(address).unwrap(); @@ -649,7 +712,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash()], &keypair))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -730,7 +793,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -777,7 +840,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -825,7 +888,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -877,7 +940,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -926,7 +989,7 @@ mod tests { tx.set_code(Code::from_hash(vp_hash)); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 05bc46aff9..6fd4e8e77b 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -17,7 +17,7 @@ use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { - Token(&'a Address), + Token { owner: &'a Address }, PoS, Vp(&'a Address), GovernanceVote(&'a Address), @@ -26,12 +26,12 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = token::is_any_token_balance_key(key) { - Self::Token(address) - } else if let Some((_, address)) = + if let Some([_, owner]) = token::is_any_token_balance_key(key) { + Self::Token { owner } + } else if let Some((_, [_, owner])) = token::is_any_multitoken_balance_key(key) { - Self::Token(address) + Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -68,7 +68,10 @@ fn validate_tx( let pk = key::get(ctx, &addr); match pk { Ok(Some(pk)) => tx_data - .verify_signature(&pk, tx_data.data_sechash()) + .verify_signature( + &pk, + &[*tx_data.data_sechash(), *tx_data.code_sechash()], + ) .is_ok(), _ => false, } @@ -81,7 +84,7 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { - KeyType::Token(owner) => { + KeyType::Token { owner, .. } => { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -89,9 +92,9 @@ fn validate_tx( ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || *valid_sig; + let valid = change.non_negative() || *valid_sig; debug_log!( - "token key: {}, change: {}, valid_sig: {}, valid \ + "token key: {}, change: {:?}, valid_sig: {}, valid \ modification: {}", key, change, @@ -193,6 +196,7 @@ mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::proto::{Code, Data, Signature}; + use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; use namada_test_utils::TestWasms; @@ -205,7 +209,6 @@ mod tests { use namada_tx_prelude::{StorageWrite, TxEnv}; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; - use rust_decimal::Decimal; use storage::testing::arb_account_storage_key_no_vp; use super::*; @@ -236,7 +239,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let source = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &token]); @@ -244,7 +247,19 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction @@ -284,7 +299,7 @@ mod tests { let vp_owner = address::testing::established_address_1(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -292,6 +307,18 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -334,7 +361,7 @@ mod tests { let public_key = keypair.ref_to(); let target = address::testing::established_address_2(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); @@ -342,8 +369,20 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -366,7 +405,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -388,10 +427,10 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = Decimal::new(5, 2); - let max_commission_rate_change = Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -411,9 +450,9 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -421,6 +460,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -434,7 +481,7 @@ mod tests { tx::ctx() .change_validator_commission_rate( &validator, - &Decimal::new(6, 2), + &Dec::new(6, 2).unwrap(), ) .unwrap(); }); @@ -459,10 +506,10 @@ mod tests { // Init PoS genesis let pos_params = PosParams::default(); let validator = address::testing::established_address_3(); - let initial_stake = token::Amount::from(10_098_123); + let initial_stake = token::Amount::from_uint(10_098_123, 0).unwrap(); let consensus_key = key::testing::keypair_2().ref_to(); - let commission_rate = Decimal::new(5, 2); - let max_commission_rate_change = Decimal::new(1, 2); + let commission_rate = Dec::new(5, 2).unwrap(); + let max_commission_rate_change = Dec::new(1, 2).unwrap(); let genesis_validators = [GenesisValidator { address: validator.clone(), @@ -482,9 +529,9 @@ mod tests { let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); - let bond_amount = token::Amount::from(5_098_123); - let unbond_amount = token::Amount::from(3_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + let bond_amount = token::Amount::from_uint(5_098_123, 0).unwrap(); + let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); @@ -492,6 +539,14 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, None, amount); + // write the denomination of NAM into storage + storage_api::token::write_denom( + &mut tx_env.wl_storage, + &token, + None, + token::NATIVE_MAX_DECIMAL_PLACES.into(), + ) + .unwrap(); tx_env.write_public_key(&vp_owner, &public_key); @@ -507,7 +562,7 @@ mod tests { tx::ctx() .change_validator_commission_rate( &validator, - &Decimal::new(6, 2), + &Dec::new(6, 2).unwrap(), ) .unwrap(); }); @@ -516,7 +571,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -541,7 +596,7 @@ mod tests { let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); - let amount = token::Amount::from(10_098_123); + let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); @@ -549,6 +604,10 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it tx_env.credit_tokens(&source, &token, None, amount); + let amount = token::DenominatedAmount { + amount, + denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), + }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -670,7 +729,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash()], &keypair))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -751,7 +810,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -798,7 +857,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -846,7 +905,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -898,7 +957,7 @@ mod tests { let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -947,7 +1006,7 @@ mod tests { tx.set_code(Code::from_hash(vp_hash)); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index d450b0de54..2a2a3a89ad 100755 Binary files a/wasm_for_tests/tx_memory_limit.wasm and b/wasm_for_tests/tx_memory_limit.wasm differ diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 86db03f4fa..5cf2015483 100755 Binary files a/wasm_for_tests/tx_mint_tokens.wasm and b/wasm_for_tests/tx_mint_tokens.wasm differ diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index 23575cc268..0d34431c24 100755 Binary files a/wasm_for_tests/tx_no_op.wasm and b/wasm_for_tests/tx_no_op.wasm differ diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 7d6706c765..d9ea03672d 100755 Binary files a/wasm_for_tests/tx_proposal_code.wasm and b/wasm_for_tests/tx_proposal_code.wasm differ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 36ea6e223c..395203ce44 100755 Binary files a/wasm_for_tests/tx_read_storage_key.wasm and b/wasm_for_tests/tx_read_storage_key.wasm differ diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index 256588b53c..0f2aff5ada 100755 Binary files a/wasm_for_tests/tx_write.wasm and b/wasm_for_tests/tx_write.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 6e29a627b2..c3c0e51a91 100755 Binary files a/wasm_for_tests/vp_always_false.wasm and b/wasm_for_tests/vp_always_false.wasm differ diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 8cdaf7eb21..5ffa417e2e 100755 Binary files a/wasm_for_tests/vp_always_true.wasm and b/wasm_for_tests/vp_always_true.wasm differ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 189765f0ed..619df75259 100755 Binary files a/wasm_for_tests/vp_eval.wasm and b/wasm_for_tests/vp_eval.wasm differ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 5833a02f4a..4dcd6e2d7b 100755 Binary files a/wasm_for_tests/vp_memory_limit.wasm and b/wasm_for_tests/vp_memory_limit.wasm differ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 0543a61536..8bf2d2689f 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index d76f374644..b8f21862df 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -995,6 +995,19 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "corosensei" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "libc", + "scopeguard", + "windows-sys 0.33.0", +] + [[package]] name = "cpufeatures" version = "0.2.7" @@ -1006,24 +1019,24 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" +checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" +checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" dependencies = [ "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli 0.25.0", + "gimli 0.26.2", "log", "regalloc", "smallvec", @@ -1032,31 +1045,30 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" +checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" dependencies = [ "cranelift-codegen-shared", - "cranelift-entity", ] [[package]] name = "cranelift-codegen-shared" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" +checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" [[package]] name = "cranelift-entity" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" +checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" [[package]] name = "cranelift-frontend" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" +checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ "cranelift-codegen", "log", @@ -1917,9 +1929,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", "indexmap", @@ -2510,6 +2522,17 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-num-traits" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951641f13f873bff03d4bf19ae8bec531935ac0ac2cc775f84d7edfdcfed3f17" +dependencies = [ + "integer-sqrt", + "num-traits", + "uint", +] + [[package]] name = "impl-serde" version = "0.4.0" @@ -2595,6 +2618,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -2827,7 +2859,7 @@ dependencies = [ [[package]] name = "masp_note_encryption" version = "0.2.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "borsh", "chacha20 0.9.1", @@ -2840,7 +2872,7 @@ dependencies = [ [[package]] name = "masp_primitives" version = "0.9.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "aes", "bip0039", @@ -2870,7 +2902,7 @@ dependencies = [ [[package]] name = "masp_proofs" version = "0.9.0" -source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +source = "git+https://github.com/anoma/masp?rev=9320c6b69b5d2e97134866871e960f0a31703813#9320c6b69b5d2e97134866871e960f0a31703813" dependencies = [ "bellman", "blake2b_simd", @@ -3087,13 +3119,10 @@ dependencies = [ "paste", "proptest", "prost", - "pwasm-utils", "rand 0.8.5", "rand_core 0.6.4", "rayon", "ripemd", - "rust_decimal", - "rust_decimal_macros", "serde", "serde_json", "sha2 0.9.9", @@ -3106,13 +3135,14 @@ dependencies = [ "tokio", "toml", "tracing", + "wasm-instrument", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", "wasmer-engine-dylib", "wasmer-engine-universal", "wasmer-vm", - "wasmparser 0.83.0", + "wasmparser 0.107.0", "zeroize", ] @@ -3129,25 +3159,26 @@ dependencies = [ "data-encoding", "derivative", "ed25519-consensus", + "eyre", "ferveo", "ferveo-common", "group-threshold-cryptography", "ibc", "ibc-proto", "ics23", + "impl-num-traits", "index-set", "itertools", "libsecp256k1 0.7.0", "masp_primitives", "namada_macros", + "num-traits", "proptest", "prost", "prost-types", "rand 0.8.5", "rand_core 0.6.4", "rayon", - "rust_decimal", - "rust_decimal_macros", "serde", "serde_json", "sha2 0.9.9", @@ -3157,6 +3188,7 @@ dependencies = [ "thiserror", "tonic-build", "tracing", + "uint", "zeroize", ] @@ -3179,8 +3211,7 @@ dependencies = [ "namada_core", "once_cell", "proptest", - "rust_decimal", - "rust_decimal_macros", + "rand 0.8.5", "thiserror", "tracing", ] @@ -3210,8 +3241,6 @@ dependencies = [ "namada_vp_prelude", "prost", "regex", - "rust_decimal", - "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", @@ -3231,7 +3260,6 @@ dependencies = [ "namada_macros", "namada_proof_of_stake", "namada_vm_env", - "rust_decimal", "sha2 0.9.9", "thiserror", ] @@ -3846,16 +3874,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "pwasm-utils" -version = "0.20.0" -source = "git+https://github.com/heliaxdev/wasm-utils?tag=v0.20.0#782bfa7fb5e513b602e66af492cbc4cb1b06f2ba" -dependencies = [ - "byteorder", - "log", - "parity-wasm", -] - [[package]] name = "quanta" version = "0.10.1" @@ -4053,9 +4071,9 @@ dependencies = [ [[package]] name = "regalloc" -version = "0.0.31" +version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ "log", "rustc-hash", @@ -4192,28 +4210,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rust_decimal" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" -dependencies = [ - "arrayvec", - "borsh", - "num-traits", - "serde", -] - -[[package]] -name = "rust_decimal_macros" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" -dependencies = [ - "quote", - "rust_decimal", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -4686,9 +4682,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", "rand_core 0.6.4", @@ -5543,9 +5539,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle 2.4.1", @@ -5591,9 +5587,9 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" +checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" [[package]] name = "version_check" @@ -5729,11 +5725,20 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-instrument" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a47ecb37b9734d1085eaa5ae1a81e60801fd8c28d4cabdd8aedb982021918bc" +dependencies = [ + "parity-wasm", +] + [[package]] name = "wasmer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc7dff846db3f38f8ed0be4a009fdfeb729cf1f94a2c7fb6ff2fec01cefa110" +checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" dependencies = [ "cfg-if 1.0.0", "indexmap", @@ -5743,6 +5748,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-bindgen", + "wasmer-artifact", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-derive", @@ -5755,11 +5761,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmer-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aaf9428c29c1d8ad2ac0e45889ba8a568a835e33fd058964e5e500f2f7ce325" +dependencies = [ + "enumset", + "loupe", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-cache" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "834a0de78bf30b9bce61c4c236344b9d8f2f4a3b7713f8de8a8274fbc2d4e9d5" +checksum = "0def391ee1631deac5ac1e6ce919c07a5ccb936ad0fd44708cdc2365c49561a4" dependencies = [ "blake3", "hex", @@ -5769,9 +5788,9 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c91abf22b16dad3826ec0d0e3ec0a8304262a6c7a14e16528c536131b80e63d" +checksum = "e67a6cd866aed456656db2cfea96c18baabbd33f676578482b85c51e1ee19d2c" dependencies = [ "enumset", "loupe", @@ -5782,20 +5801,19 @@ dependencies = [ "target-lexicon", "thiserror", "wasmer-types", - "wasmer-vm", - "wasmparser 0.78.2", + "wasmparser 0.83.0", ] [[package]] name = "wasmer-compiler-cranelift" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7624a1f496b163139a7e0b442426cad805bec70486900287506f9d15a29323ab" +checksum = "48be2f9f6495f08649e4f8b946a2cbbe119faf5a654aa1457f9504a99d23dae0" dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "gimli 0.25.0", + "gimli 0.26.2", "loupe", "more-asserts", "rayon", @@ -5804,18 +5822,18 @@ dependencies = [ "tracing", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-compiler-singlepass" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b63c1538ffb4b0e09edaebfcac35c34141d5944c52f77d137cbe0b634bd40fa" +checksum = "29ca2a35204d8befa85062bc7aac259a8db8070b801b8a783770ba58231d729e" dependencies = [ "byteorder", "dynasm", "dynasmrt", + "gimli 0.26.2", "lazy_static", "loupe", "more-asserts", @@ -5823,14 +5841,13 @@ dependencies = [ "smallvec", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-derive" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "933b23b5cee0f58aa6c17c6de7e1f3007279357e0d555f22e24d6b395cfe7f89" +checksum = "00e50405cc2a2f74ff574584710a5f2c1d5c93744acce2ca0866084739284b51" dependencies = [ "proc-macro-error", "proc-macro2", @@ -5840,9 +5857,9 @@ dependencies = [ [[package]] name = "wasmer-engine" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" +checksum = "3f98f010978c244db431b392aeab0661df7ea0822343334f8f2a920763548e45" dependencies = [ "backtrace", "enumset", @@ -5855,6 +5872,7 @@ dependencies = [ "serde_bytes", "target-lexicon", "thiserror", + "wasmer-artifact", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -5862,9 +5880,9 @@ dependencies = [ [[package]] name = "wasmer-engine-dylib" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591683f3356ac31cc88aaecaf77ac2cc9f456014348b01af46c164f44f531162" +checksum = "ad0358af9c154724587731175553805648d9acb8f6657880d165e378672b7e53" dependencies = [ "cfg-if 1.0.0", "enum-iterator", @@ -5877,6 +5895,7 @@ dependencies = [ "serde", "tempfile", "tracing", + "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -5887,12 +5906,11 @@ dependencies = [ [[package]] name = "wasmer-engine-universal" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dccfde103e9b87427099a6de344b7c791574f307d035c8c7dbbc00974c1af0c1" +checksum = "440dc3d93c9ca47865a4f4edd037ea81bf983b5796b59b3d712d844b32dbef15" dependencies = [ "cfg-if 1.0.0", - "enum-iterator", "enumset", "leb128", "loupe", @@ -5900,16 +5918,33 @@ dependencies = [ "rkyv", "wasmer-compiler", "wasmer-engine", + "wasmer-engine-universal-artifact", "wasmer-types", "wasmer-vm", "winapi", ] +[[package]] +name = "wasmer-engine-universal-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f1db3f54152657eb6e86c44b66525ff7801dad8328fe677da48dd06af9ad41" +dependencies = [ + "enum-iterator", + "enumset", + "loupe", + "rkyv", + "thiserror", + "wasmer-artifact", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-object" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" +checksum = "8d831335ff3a44ecf451303f6f891175c642488036b92ceceb24ac8623a8fa8b" dependencies = [ "object 0.28.4", "thiserror", @@ -5919,12 +5954,15 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4deb854f178265a76b59823c41547d259c65da3687b606b0b9c12d80ab950e3e" +checksum = "39df01ea05dc0a9bab67e054c7cb01521e53b35a7bb90bd02eca564ed0b2667f" dependencies = [ + "backtrace", + "enum-iterator", "indexmap", "loupe", + "more-asserts", "rkyv", "serde", "thiserror", @@ -5932,38 +5970,47 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbc5c989cb14a102433927e630473da52f83d82c469acd5cfa8fc7efacc1e70" +checksum = "30d965fa61f4dc4cdb35a54daaf7ecec3563fbb94154a6c35433f879466247dd" dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", + "corosensei", "enum-iterator", "indexmap", + "lazy_static", "libc", "loupe", + "mach", "memoffset 0.6.5", "more-asserts", "region", "rkyv", + "scopeguard", "serde", "thiserror", + "wasmer-artifact", "wasmer-types", "winapi", ] [[package]] name = "wasmparser" -version = "0.78.2" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" +checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wasmparser" -version = "0.83.0" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" +checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" +dependencies = [ + "indexmap", + "semver 1.0.17", +] [[package]] name = "wast" @@ -6097,6 +6144,19 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +dependencies = [ + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -6172,6 +6232,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -6184,6 +6250,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -6196,6 +6268,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -6208,6 +6286,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -6232,6 +6316,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index a37039e9c4..3822a8a01f 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -149,7 +149,7 @@ pub mod main { let target_key = token::balance_key(&token, &target); let mut target_bal: token::Amount = ctx.read(&target_key)?.unwrap_or_default(); - target_bal.receive(&amount); + target_bal.receive(&amount.amount); ctx.write(&target_key, target_bal)?; Ok(()) }