diff --git a/.changelog/v0.21.0/bug-fixes/1774-fix-dec-display.md b/.changelog/v0.21.0/bug-fixes/1774-fix-dec-display.md new file mode 100644 index 0000000000..c53de24bee --- /dev/null +++ b/.changelog/v0.21.0/bug-fixes/1774-fix-dec-display.md @@ -0,0 +1,2 @@ +- Fixes buggy Display for the Dec type when the number is some multiple of 10 + ([\#1774](https://github.com/anoma/namada/pull/1774)) \ No newline at end of file diff --git a/.changelog/v0.21.0/bug-fixes/1775-downgrade-sysinfo.md b/.changelog/v0.21.0/bug-fixes/1775-downgrade-sysinfo.md new file mode 100644 index 0000000000..4d45e30a0a --- /dev/null +++ b/.changelog/v0.21.0/bug-fixes/1775-downgrade-sysinfo.md @@ -0,0 +1,2 @@ +- Downgraded sysinfo back to v0.27.8 with a working available memory report on + Mac M1. ([\#1775](https://github.com/anoma/namada/pull/1775)) \ No newline at end of file diff --git a/.changelog/v0.21.0/bug-fixes/1793-fix-unjail-bug.md b/.changelog/v0.21.0/bug-fixes/1793-fix-unjail-bug.md new file mode 100644 index 0000000000..11a2b1e545 --- /dev/null +++ b/.changelog/v0.21.0/bug-fixes/1793-fix-unjail-bug.md @@ -0,0 +1,4 @@ +- Fixes buggy error handling in pos unjail_validator. Now properly enforces that + if an unjail tx is submitted when the validator state is something other than + Jailed in any of the current or future epochs, the tx will error out and fail. + ([\#1793](https://github.com/anoma/namada/pull/1793)) \ No newline at end of file diff --git a/.changelog/v0.21.0/bug-fixes/1801-fix-available-memory.md b/.changelog/v0.21.0/bug-fixes/1801-fix-available-memory.md new file mode 100644 index 0000000000..4f4e1b39ca --- /dev/null +++ b/.changelog/v0.21.0/bug-fixes/1801-fix-available-memory.md @@ -0,0 +1,2 @@ +- Fix available_memory size + ([\#1801](https://github.com/anoma/namada/issues/1801)) \ No newline at end of file diff --git a/.changelog/v0.21.0/features/1765-multisignature-draft-rebase-0.20.0.md b/.changelog/v0.21.0/features/1765-multisignature-draft-rebase-0.20.0.md new file mode 100644 index 0000000000..25697effb8 --- /dev/null +++ b/.changelog/v0.21.0/features/1765-multisignature-draft-rebase-0.20.0.md @@ -0,0 +1,4 @@ +- Introduce multisignature accounts and transaction format. It is now possible + to supply multiple public keys when creating a new account/validator and + specify the minimum number of signatures required to authorize a transaction. + ([\#1765](https://github.com/anoma/namada/pull/1765)) \ No newline at end of file diff --git a/.changelog/v0.21.0/features/1803-refactor-governance.md b/.changelog/v0.21.0/features/1803-refactor-governance.md new file mode 100644 index 0000000000..5ef7160505 --- /dev/null +++ b/.changelog/v0.21.0/features/1803-refactor-governance.md @@ -0,0 +1,2 @@ +- Introduce a simplified version of Public Good Fundings. + ([\#1803](https://github.com/anoma/namada/pull/1803)) \ No newline at end of file diff --git a/.changelog/v0.21.0/summary.md b/.changelog/v0.21.0/summary.md new file mode 100644 index 0000000000..bd8061518c --- /dev/null +++ b/.changelog/v0.21.0/summary.md @@ -0,0 +1,2 @@ +Namada 0.21.0 is a minor release introducing a first version of the PGF mechanism, addressing several +improvements to the PoS and Governance system and some changes to the ledger stability. diff --git a/.changelog/v0.21.0/testing/1768-pregen-masp-proofs.md b/.changelog/v0.21.0/testing/1768-pregen-masp-proofs.md new file mode 100644 index 0000000000..7f86b1621f --- /dev/null +++ b/.changelog/v0.21.0/testing/1768-pregen-masp-proofs.md @@ -0,0 +1,2 @@ +- Added pre-built MASP proofs for integration tests. + ([\#1768](https://github.com/anoma/namada/pull/1768)) \ No newline at end of file diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 485e4b43ad..15b871e61b 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -8,10 +8,10 @@ "e2e::ledger_tests::ledger_txs_and_queries": 30, "e2e::ledger_tests::masp_txs_and_queries": 82, "e2e::ledger_tests::pos_bonds": 77, + "e2e::ledger_tests::implicit_account_reveal_pk": 30, "e2e::ledger_tests::pos_init_validator": 40, "e2e::ledger_tests::proposal_offline": 21, "e2e::ledger_tests::pgf_governance_proposal": 100, - "e2e::ledger_tests::eth_governance_proposal": 100, "e2e::ledger_tests::proposal_submission": 200, "e2e::ledger_tests::run_ledger": 5, "e2e::ledger_tests::run_ledger_load_state_and_reset": 23, diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad3c0eff0..0632900ab9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # CHANGELOG +## v0.21.0 + +Namada 0.21.0 is a minor release introducing a first version of the PGF mechanism, addressing several +improvements to the PoS and Governance system and some changes to the ledger stability. + +### BUG FIXES + +- Fixes buggy Display for the Dec type when the number is some multiple of 10 + ([\#1774](https://github.com/anoma/namada/pull/1774)) +- Downgraded sysinfo back to v0.27.8 with a working available memory report on + Mac M1. ([\#1775](https://github.com/anoma/namada/pull/1775)) +- Fixes buggy error handling in pos unjail_validator. Now properly enforces that + if an unjail tx is submitted when the validator state is something other than + Jailed in any of the current or future epochs, the tx will error out and fail. + ([\#1793](https://github.com/anoma/namada/pull/1793)) +- Fix available_memory size + ([\#1801](https://github.com/anoma/namada/issues/1801)) + +### FEATURES + +- Introduce multisignature accounts and transaction format. It is now possible + to supply multiple public keys when creating a new account/validator and + specify the minimum number of signatures required to authorize a transaction. + ([\#1765](https://github.com/anoma/namada/pull/1765)) +- Introduce a simplified version of Public Good Fundings. + ([\#1803](https://github.com/anoma/namada/pull/1803)) + +### TESTING + +- Added pre-built MASP proofs for integration tests. + ([\#1768](https://github.com/anoma/namada/pull/1768)) + ## v0.20.1 Namada 0.20.1 is a patch release addressing a bug in the inflation mechanism and minor ledger improvements. diff --git a/Cargo.lock b/Cargo.lock index 837788dba2..4476f3651d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,11 +14,11 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ - "gimli", + "gimli 0.27.2", ] [[package]] @@ -34,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if 1.0.0", "cipher 0.4.4", @@ -66,7 +66,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", "once_cell", "version_check", ] @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "ark-bls12-381" @@ -277,9 +277,9 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" @@ -321,34 +321,35 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite", ] [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -364,9 +365,9 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" dependencies = [ "proc-macro-error", "proc-macro2", @@ -382,13 +383,13 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.7" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", + "bitflags 1.2.1", "bytes", "futures-util", "http", @@ -401,19 +402,18 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustversion", - "serde 1.0.147", + "serde 1.0.163", "sync_wrapper", "tower", - "tower-http", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", "bytes", @@ -428,24 +428,24 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide", - "object 0.29.0", + "miniz_oxide 0.6.2", + "object 0.30.3", "rustc-demangle", ] [[package]] name = "base16ct" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base58" @@ -459,22 +459,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" -[[package]] -name = "base58check" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee2fe4c9a0c84515f136aaae2466744a721af6d63339c18689d9e995d74d99b" -dependencies = [ - "base58 0.1.0", - "sha2 0.8.2", -] - -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.1" @@ -514,9 +498,9 @@ dependencies = [ "bitvec 1.0.1", "blake2s_simd", "byteorder", - "crossbeam-channel 0.5.6", - "ff", - "group", + "crossbeam-channel 0.5.8", + "ff 0.12.1", + "group 0.12.1", "lazy_static", "log", "num_cpus", @@ -528,11 +512,11 @@ dependencies = [ [[package]] name = "bimap" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -541,7 +525,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -550,19 +534,19 @@ version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "cexpr", "clang-sys", "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.6", + "prettyplease 0.2.4", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -596,9 +580,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitflags" @@ -630,11 +614,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", ] [[package]] @@ -649,9 +633,9 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" dependencies = [ "arrayref", "arrayvec 0.7.2", @@ -660,9 +644,9 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" +checksum = "6637f448b9e61dfadbdcbae9a885fadee1f3eaffb1f8d3c1965d3ade8bdfd44f" dependencies = [ "arrayref", "arrayvec 0.7.2", @@ -671,16 +655,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" dependencies = [ "arrayref", "arrayvec 0.7.2", "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.7", + "digest 0.10.6", ] [[package]] @@ -701,16 +685,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -744,8 +728,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" dependencies = [ - "ff", - "group", + "ff 0.12.1", + "group 0.12.1", "pairing", "rand_core 0.6.4", "subtle 2.4.1", @@ -767,7 +751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" dependencies = [ "borsh-derive 0.10.3", - "hashbrown 0.12.3", + "hashbrown 0.11.2", ] [[package]] @@ -842,6 +826,9 @@ name = "bs58" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +dependencies = [ + "sha2 0.9.9", +] [[package]] name = "bstr" @@ -854,21 +841,11 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" [[package]] name = "byte-slice-cast" @@ -884,29 +861,30 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "byte-unit" -version = "4.0.17" +version = "4.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581ad4b3d627b0c09a0ccb2912148f839acaca0b93cf54cbe42b6c674e86079c" +checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", "utf8-width", ] [[package]] name = "bytecheck" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" dependencies = [ "bytecheck_derive", "ptr_meta", + "simdutf8", ] [[package]] name = "bytecheck_derive" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" dependencies = [ "proc-macro2", "quote", @@ -925,7 +903,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -941,11 +919,11 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.1" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" +checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -954,28 +932,28 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] name = "cargo_metadata" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", "semver 1.0.17", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "thiserror", ] [[package]] name = "cc" -version = "1.0.76" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] @@ -986,7 +964,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 7.1.1", + "nom 7.1.3", ] [[package]] @@ -1038,9 +1016,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ "iana-time-zone", "num-integer", @@ -1050,9 +1028,9 @@ dependencies = [ [[package]] name = "chunked_transfer" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" +checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a" [[package]] name = "cipher" @@ -1060,7 +1038,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -1085,9 +1063,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -1096,21 +1074,22 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.10" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384e169cc618c613d5e3ca6404dda77a8685a63e08660dcc64abaf7da7cb0c7a" +checksum = "2686c4115cb0810d9a984776e197823d08ec94f176549a89a9efded477c456dc" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.10" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef137bbe35aab78bdb468ccfba75a5f4d8321ae011d34063770780545176af2d" +checksum = "2e53afce1efce6ed1f633cf0e57612fe51db54a1ee4fd8f8503d078fe02d69ae" dependencies = [ "anstream", "anstyle", + "bitflags 1.2.1", "clap_lex", "strsim", ] @@ -1126,47 +1105,37 @@ name = "clru" version = "0.5.0" source = "git+https://github.com/marmeladema/clru-rs.git?rev=71ca566#71ca566915f21f3c308091ca7756a91b0f8b5afc" -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "coins-bip32" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634c509653de24b439672164bbf56f5f582a2ab0e313d3b0f6af0b7345cf2560" +checksum = "b30a84aab436fcb256a2ab3c80663d8aec686e6bae12827bb05fef3e1e439c9f" dependencies = [ "bincode", "bs58", "coins-core", - "digest 0.10.7", - "getrandom 0.2.8", + "digest 0.10.6", + "getrandom 0.2.9", "hmac 0.12.1", "k256", "lazy_static", - "serde 1.0.147", + "serde 1.0.163", "sha2 0.10.6", "thiserror", ] [[package]] name = "coins-bip39" -version = "0.7.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a11892bcac83b4c6e95ab84b5b06c76d9d70ad73548dd07418269c5c7977171" +checksum = "84f4d04ee18e58356accd644896aeb2094ddeafb6a713e056cef0c0a8e468c15" dependencies = [ "bitvec 0.17.4", "coins-bip32", - "getrandom 0.2.8", - "hex", + "getrandom 0.2.9", "hmac 0.12.1", - "pbkdf2 0.11.0", + "once_cell", + "pbkdf2 0.12.1", "rand 0.8.5", "sha2 0.10.6", "thiserror", @@ -1174,19 +1143,18 @@ dependencies = [ [[package]] name = "coins-core" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94090a6663f224feae66ab01e41a2555a8296ee07b5f20dab8888bdefc9f617" +checksum = "9b949a1c63fb7eb591eb7ba438746326aedf0ae843e51ec92ba6bec5bb382c4f" dependencies = [ - "base58check", - "base64 0.12.3", + "base64 0.21.0", "bech32 0.7.3", - "blake2", - "digest 0.10.7", - "generic-array 0.14.6", + "bs58", + "digest 0.10.6", + "generic-array 0.14.7", "hex", "ripemd", - "serde 1.0.147", + "serde 1.0.163", "serde_derive", "sha2 0.10.6", "sha3", @@ -1216,7 +1184,7 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors 1.3.0", - "tracing-core 0.1.30", + "tracing-core 0.1.31", "tracing-error", ] @@ -1243,12 +1211,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ "lazy_static", - "nom 5.1.2", + "nom 5.1.3", "rust-ini", - "serde 1.0.147", + "serde 1.0.163", "serde-hjson", "serde_json", - "toml", + "toml 0.5.9", "yaml-rust", ] @@ -1258,20 +1226,20 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b72b06487a0d4683349ad74d62e87ad639b09667082b3c495c5b6bab7d84b3da" dependencies = [ - "windows", + "windows 0.44.0", ] [[package]] name = "const-oid" -version = "0.9.1" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" [[package]] name = "contracts" @@ -1284,15 +1252,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -1305,9 +1264,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "corosensei" @@ -1324,9 +1283,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -1350,7 +1309,7 @@ dependencies = [ "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", - "gimli", + "gimli 0.26.2", "log", "regalloc", "smallvec", @@ -1411,35 +1370,35 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.15", ] [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.15", ] [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", "cfg-if 1.0.0", - "crossbeam-utils 0.8.12", - "memoffset 0.6.5", + "crossbeam-utils 0.8.15", + "memoffset 0.8.0", "scopeguard", ] @@ -1456,9 +1415,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if 1.0.0", ] @@ -1471,11 +1430,11 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.4.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "rand_core 0.6.4", "subtle 2.4.1", "zeroize", @@ -1487,7 +1446,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "typenum", ] @@ -1507,7 +1466,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "subtle 2.4.1", ] @@ -1517,7 +1476,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "subtle 2.4.1", ] @@ -1587,55 +1546,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "cxx" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 1.0.109", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling" -version = "0.14.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ "darling_core", "darling_macro", @@ -1643,39 +1558,39 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] name = "darling_macro" -version = "0.14.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "der" -version = "0.6.1" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" dependencies = [ "const-oid", "zeroize", @@ -1736,16 +1651,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] name = "digest" -version = "0.10.7" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle 2.4.1", ] @@ -1778,7 +1694,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -1795,9 +1711,9 @@ checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" [[package]] name = "dunce" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "dyn-clone" @@ -1811,7 +1727,7 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "byteorder", "lazy_static", "proc-macro-error", @@ -1833,14 +1749,16 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.14.8" +version = "0.16.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" dependencies = [ "der", + "digest 0.10.6", "elliptic-curve", "rfc6979", - "signature", + "signature 2.1.0", + "spki", ] [[package]] @@ -1849,8 +1767,8 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "serde 1.0.147", - "signature", + "serde 1.0.163", + "signature 1.6.4", ] [[package]] @@ -1862,7 +1780,7 @@ dependencies = [ "curve25519-dalek-ng", "hex", "rand_core 0.6.4", - "serde 1.0.147", + "serde 1.0.163", "sha2 0.9.9", "thiserror", "zeroize", @@ -1878,7 +1796,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1886,23 +1804,22 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" -version = "0.12.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" dependencies = [ "base16ct", "crypto-bigint", - "der", - "digest 0.10.7", - "ff", - "generic-array 0.14.6", - "group", + "digest 0.10.6", + "ff 0.13.0", + "generic-array 0.14.7", + "group 0.13.0", "pkcs8", "rand_core 0.6.4", "sec1", @@ -1912,28 +1829,27 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "enr" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" +checksum = "cf56acd72bb22d2824e66ae8e9e5ada4d0de17a69c7fd35569dde2ada8ec9116" dependencies = [ "base64 0.13.1", - "bs58", "bytes", "hex", "k256", "log", "rand 0.8.5", "rlp", - "serde 1.0.147", + "serde 1.0.163", "sha3", "zeroize", ] @@ -1960,43 +1876,38 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.12" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" +checksum = "e875f1719c16de097dee81ed675e2d9bb63096823ed3f0ca827b7dea3028bbbb" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] -name = "erased-serde" -version = "0.3.25" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" -dependencies = [ - "serde 1.0.147", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno" -version = "0.2.8" +name = "erased-serde" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569" dependencies = [ - "errno-dragonfly", - "libc", - "winapi", + "serde 1.0.163", ] [[package]] @@ -2028,7 +1939,7 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log", "once_cell", - "serde 1.0.147", + "serde 1.0.163", "serde_json", ] @@ -2038,20 +1949,20 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" dependencies = [ - "aes 0.8.2", + "aes 0.8.3", "ctr", - "digest 0.10.7", + "digest 0.10.6", "hex", "hmac 0.12.1", "pbkdf2 0.11.0", "rand 0.8.5", "scrypt", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "sha2 0.10.6", "sha3", "thiserror", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -2064,7 +1975,7 @@ dependencies = [ "hex", "once_cell", "regex", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "sha3", "thiserror", @@ -2184,21 +2095,21 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1e010165c08a2a3fa43c0bb8bc9d596f079a021aaa2cc4e8d921df09709c95" +checksum = "7b856b7b8ff5c961093cb8efe151fbcce724b451941ce20781de11a531ccd578" dependencies = [ "ethers-core", "once_cell", - "serde 1.0.147", + "serde 1.0.163", "serde_json", ] [[package]] name = "ethers-contract" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be33fd47a06cc8f97caf614cf7cf91af9dd6dcd767511578895fa884b430c4b8" +checksum = "e066a0d9cfc70c454672bf16bb433b0243427420076dc5b2f49c448fb5a10628" dependencies = [ "ethers-contract-abigen", "ethers-contract-derive", @@ -2208,83 +2119,73 @@ dependencies = [ "hex", "once_cell", "pin-project", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "thiserror", ] [[package]] name = "ethers-contract-abigen" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d9f9ecb4a18c1693de954404b66e0c9df31dac055b411645e38c4efebf3dbc" +checksum = "c113e3e86b6bc16d98484b2c3bb2d01d6fed9f489fe2e592e5cc87c3024d616b" dependencies = [ "Inflector", - "cfg-if 1.0.0", "dunce", "ethers-core", - "ethers-etherscan", "eyre", - "getrandom 0.2.8", "hex", - "prettyplease 0.1.24", + "prettyplease 0.2.4", "proc-macro2", "quote", "regex", - "reqwest", - "serde 1.0.147", + "serde 1.0.163", "serde_json", - "syn 1.0.109", - "tokio", - "toml", - "url", + "syn 2.0.15", + "toml 0.7.6", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001b33443a67e273120923df18bab907a0744ad4b5fef681a8b0691f2ee0f3de" +checksum = "8c3fb5adee25701c79ec58fcf2c63594cd8829bc9ad6037ff862d5a111101ed2" dependencies = [ + "Inflector", "ethers-contract-abigen", "ethers-core", - "eyre", "hex", "proc-macro2", "quote", "serde_json", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] name = "ethers-core" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5925cba515ac18eb5c798ddf6069cc33ae00916cb08ae64194364a1b35c100b" +checksum = "6da5fa198af0d3be20c19192df2bd9590b92ce09a8421e793bec8851270f1b05" dependencies = [ "arrayvec 0.7.2", "bytes", "cargo_metadata", "chrono", - "convert_case", "elliptic-curve", "ethabi", - "generic-array 0.14.6", - "getrandom 0.2.8", + "generic-array 0.14.7", "hex", "k256", "num_enum", "once_cell", "open-fastrlp", - "proc-macro2", "rand 0.8.5", "rlp", - "rlp-derive", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "strum", - "syn 1.0.109", + "syn 2.0.15", "tempfile", "thiserror", "tiny-keccak", @@ -2293,16 +2194,14 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d769437fafd0b47ea8b95e774e343c5195c77423f0f54b48d11c0d9ed2148ad" +checksum = "84ebb401ba97c6f5af278c2c9936c4546cad75dec464b439ae6df249906f4caa" dependencies = [ "ethers-core", - "getrandom 0.2.8", "reqwest", "semver 1.0.17", - "serde 1.0.147", - "serde-aux", + "serde 1.0.163", "serde_json", "thiserror", "tracing 0.1.37", @@ -2310,9 +2209,9 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dd311b76eab9d15209e4fd16bb419e25543709cbdf33079e8923dfa597517c" +checksum = "740f4a773c19dd6d6a68c8c2e0996c096488d38997d524e21dc612c55da3bd24" dependencies = [ "async-trait", "auto_impl", @@ -2321,11 +2220,12 @@ dependencies = [ "ethers-etherscan", "ethers-providers", "ethers-signers", + "futures-channel", "futures-locks", "futures-util", "instant", "reqwest", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "thiserror", "tokio", @@ -2336,27 +2236,27 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7174af93619e81844d3d49887106a3721e5caecdf306e0b824bfb4316db3be" +checksum = "56b498fd2a6c019d023e43e83488cd1fb0721f299055975aa6bac8dbf1e95f2c" dependencies = [ "async-trait", "auto_impl", "base64 0.21.0", + "bytes", "enr", "ethers-core", "futures-core", "futures-timer", "futures-util", - "getrandom 0.2.8", "hashers", "hex", "http", + "instant", "once_cell", - "parking_lot 0.11.2", "pin-project", "reqwest", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "thiserror", "tokio", @@ -2365,16 +2265,15 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-timer", "web-sys", "ws_stream_wasm", ] [[package]] name = "ethers-signers" -version = "2.0.0" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d45ff294473124fd5bb96be56516ace179eef0eaec5b281f68c953ddea1a8bf" +checksum = "02c4b7e15f212fa7cc2e1251868320221d4ff77a3d48068e69f47ce1c491df2d" dependencies = [ "async-trait", "coins-bip32", @@ -2425,9 +2324,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -2439,7 +2338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if 1.0.0", - "rustix 0.38.3", + "rustix 0.38.4", "windows-sys 0.48.0", ] @@ -2460,7 +2359,7 @@ dependencies = [ "blake2", "blake2b_simd", "borsh 0.9.4", - "digest 0.10.7", + "digest 0.10.6", "ed25519-dalek", "either", "ferveo-common", @@ -2472,7 +2371,7 @@ dependencies = [ "num 0.4.0", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", "serde_json", "subproductdomain", @@ -2489,7 +2388,7 @@ dependencies = [ "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", ] @@ -2504,11 +2403,21 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.4.1", +] + [[package]] name = "file-serve" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43addbb09a5dcb5609cb44a01a79e67716fe40b50c109f50112ef201a8c7c59" +checksum = "547ebf393d987692a02b5d2be1c0b398b16a5b185c23a047c1d3fc3050d6d803" dependencies = [ "log", "mime_guess", @@ -2517,14 +2426,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall", - "windows-sys 0.42.0", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", ] [[package]] @@ -2547,12 +2456,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.7.1", ] [[package]] @@ -2611,9 +2520,9 @@ dependencies = [ [[package]] name = "fs_extra" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "funty" @@ -2687,7 +2596,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -2707,6 +2616,10 @@ name = "futures-timer" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +dependencies = [ + "gloo-timers", + "send_wrapper 0.4.0", +] [[package]] name = "futures-util" @@ -2746,12 +2659,13 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -2769,9 +2683,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -2787,17 +2701,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 1.9.3", "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + [[package]] name = "git2" version = "0.13.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "libc", "libgit2-sys", "log", @@ -2808,9 +2728,21 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-timers" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] [[package]] name = "group" @@ -2818,12 +2750,23 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", "memuse", "rand_core 0.6.4", "subtle 2.4.1", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", + "rand_core 0.6.4", + "subtle 2.4.1", +] + [[package]] name = "group-threshold-cryptography" version = "0.1.0" @@ -2870,9 +2813,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -2880,10 +2823,10 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.8", "tracing 0.1.37", ] @@ -2911,6 +2854,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "hashers" version = "1.0.1" @@ -2937,7 +2886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.1", - "bitflags 1.3.2", + "bitflags 1.2.1", "bytes", "headers-core", "http", @@ -2957,24 +2906,24 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hex" @@ -3018,7 +2967,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", ] [[package]] @@ -3039,7 +2988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.6", + "generic-array 0.14.7", "hmac 0.8.1", ] @@ -3051,9 +3000,9 @@ checksum = "77e806677ce663d0a199541030c816847b36e8dc095f70dae4a4f4ad63da5383" [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -3071,12 +3020,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "http-range-header" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" - [[package]] name = "httparse" version = "1.8.0" @@ -3091,9 +3034,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -3124,10 +3067,10 @@ dependencies = [ "headers", "http", "hyper", - "hyper-rustls 0.22.1", + "hyper-rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "tower-service", "webpki 0.21.4", ] @@ -3145,24 +3088,11 @@ dependencies = [ "rustls 0.19.1", "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls", "webpki 0.21.4", "webpki-roots 0.21.1", ] -[[package]] -name = "hyper-rustls" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" -dependencies = [ - "http", - "hyper", - "rustls 0.20.7", - "tokio", - "tokio-rustls 0.23.4", -] - [[package]] name = "hyper-timeout" version = "0.4.1" @@ -3190,26 +3120,25 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows 0.48.0", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -3226,11 +3155,11 @@ dependencies = [ "ibc-proto", "ics23", "num-traits 0.2.15", - "parking_lot 0.12.1", + "parking_lot", "primitive-types", "prost", "safe-regex", - "serde 1.0.147", + "serde 1.0.163", "serde_derive", "serde_json", "sha2 0.10.6", @@ -3256,7 +3185,7 @@ dependencies = [ "parity-scale-codec", "prost", "scale-info", - "serde 1.0.147", + "serde 1.0.163", "subtle-encoding", "tendermint-proto", ] @@ -3327,7 +3256,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -3347,7 +3276,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5ad43a3f5795945459d577f6589cf62a476e92c79b75e70cd954364e14ce17b" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -3362,18 +3291,28 @@ version = "0.7.1" source = "git+https://github.com/heliaxdev/index-set?tag=v0.7.1#dc24cdbbe3664514d59f1a4c4031863fc565f1c2" dependencies = [ "borsh 0.9.4", - "serde 1.0.147", + "serde 1.0.163", ] [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde 1.0.147", + "serde 1.0.163", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", ] [[package]] @@ -3382,7 +3321,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -3408,28 +3347,30 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.4" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.8" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi 0.3.2", - "rustix 0.38.3", + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix 0.37.13", "windows-sys 0.48.0", ] @@ -3444,24 +3385,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" dependencies = [ "wasm-bindgen", ] @@ -3474,30 +3415,34 @@ checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" dependencies = [ "bitvec 1.0.1", "bls12_381", - "ff", - "group", + "ff 0.12.1", + "group 0.12.1", "rand_core 0.6.4", "subtle 2.4.1", ] [[package]] name = "k256" -version = "0.11.6" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", + "once_cell", "sha2 0.10.6", - "sha3", + "signature 2.1.0", ] [[package]] name = "keccak" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] [[package]] name = "lazy_static" @@ -3524,7 +3469,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec 0.5.2", - "bitflags 1.3.2", + "bitflags 1.2.1", "cfg-if 1.0.0", "ryu", "static_assertions", @@ -3611,7 +3556,7 @@ dependencies = [ "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.147", + "serde 1.0.163", "sha2 0.9.9", "typenum", ] @@ -3658,9 +3603,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", "libc", @@ -3668,29 +3613,20 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "link-cplusplus" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" -dependencies = [ - "cc", -] - [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" [[package]] name = "linux-raw-sys" @@ -3710,12 +3646,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] name = "loupe" @@ -3723,7 +3656,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6a72dfa44fe15b5e76b94307eeb2ff995a8c5b283b55008940c02e0c5b634d" dependencies = [ - "indexmap", + "indexmap 1.9.3", "loupe-derive", "rustversion", ] @@ -3753,10 +3686,10 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c36112680b23ee7a5d2a10e56b59a74e4028963e9080b8abec4e69d0d773dc06" dependencies = [ - "indexmap", + "indexmap 1.9.3", "linked-hash-map", "regex", - "serde 1.0.147", + "serde 1.0.163", "serde_derive", "serde_yaml", ] @@ -3787,9 +3720,9 @@ dependencies = [ "bls12_381", "borsh 0.9.4", "byteorder", - "ff", + "ff 0.12.1", "fpe", - "group", + "group 0.12.1", "hex", "incrementalmerkletree", "jubjub", @@ -3813,8 +3746,8 @@ dependencies = [ "blake2b_simd", "bls12_381", "directories", - "getrandom 0.2.8", - "group", + "getrandom 0.2.9", + "group 0.12.1", "itertools", "jubjub", "lazy_static", @@ -3865,9 +3798,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] @@ -3890,6 +3823,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + [[package]] name = "memuse" version = "0.2.1" @@ -3916,9 +3858,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -3938,36 +3880,45 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "minreq" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c785bc6027fd359756e538541c8624012ba3776d3d3fe123885643092ed4132" +checksum = "cb6c6973f78ef55d0e5fc04fdb8f9ad67c87c9e86bca0ff77b6a3102b0eb36b7" dependencies = [ - "lazy_static", "log", - "rustls 0.20.7", + "once_cell", + "rustls 0.20.8", "webpki 0.22.0", - "webpki-roots 0.22.5", + "webpki-roots 0.22.6", ] [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -3983,32 +3934,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "multipart" -version = "0.18.0" +name = "multer" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" dependencies = [ - "buf_redux", + "bytes", + "encoding_rs", + "futures-util", + "http", "httparse", "log", + "memchr", "mime", - "mime_guess", - "quick-error", - "rand 0.8.5", - "safemem", - "tempfile", - "twoway", + "spin 0.9.8", + "version_check", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "namada" -version = "0.20.1" +version = "0.21.0" dependencies = [ "assert_matches", "async-trait", @@ -4048,7 +3999,7 @@ dependencies = [ "rand_core 0.6.4", "rayon", "ripemd", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "sha2 0.9.9", "slip10_ed25519", @@ -4059,9 +4010,9 @@ dependencies = [ "tiny-bip39", "tiny-hderive", "tokio", - "toml", + "toml 0.5.9", "tracing 0.1.37", - "tracing-subscriber 0.3.16", + "tracing-subscriber 0.3.17", "wasm-instrument", "wasmer", "wasmer-cache", @@ -4076,7 +4027,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.20.1" +version = "0.21.0" dependencies = [ "ark-serialize", "ark-std", @@ -4136,7 +4087,7 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", "serde_json", "sha2 0.9.9", @@ -4150,14 +4101,14 @@ dependencies = [ "thiserror", "tokio", "tokio-test", - "toml", + "toml 0.5.9", "tonic", "tower", "tower-abci", "tracing 0.1.37", "tracing-appender", "tracing-log", - "tracing-subscriber 0.3.16", + "tracing-subscriber 0.3.17", "warp", "winapi", "zeroize", @@ -4165,7 +4116,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.20.1" +version = "0.21.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4203,7 +4154,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -4212,17 +4163,17 @@ dependencies = [ "test-log", "thiserror", "tiny-keccak", - "toml", + "toml 0.5.9", "tonic-build", "tracing 0.1.37", - "tracing-subscriber 0.3.16", + "tracing-subscriber 0.3.17", "uint", "zeroize", ] [[package]] name = "namada_encoding_spec" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "itertools", @@ -4233,7 +4184,7 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.20.1" +version = "0.21.0" dependencies = [ "assert_matches", "borsh 0.9.4", @@ -4246,18 +4197,18 @@ dependencies = [ "namada_macros", "namada_proof_of_stake", "rand 0.8.5", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "tendermint", "tendermint-proto", "tendermint-rpc", - "toml", + "toml 0.5.9", "tracing 0.1.37", ] [[package]] name = "namada_macros" -version = "0.20.1" +version = "0.21.0" dependencies = [ "proc-macro2", "quote", @@ -4266,7 +4217,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "data-encoding", @@ -4279,12 +4230,12 @@ dependencies = [ "test-log", "thiserror", "tracing 0.1.37", - "tracing-subscriber 0.3.16", + "tracing-subscriber 0.3.17", ] [[package]] name = "namada_test_utils" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "namada_core", @@ -4293,7 +4244,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.20.1" +version = "0.21.0" dependencies = [ "assert_cmd", "async-trait", @@ -4326,20 +4277,21 @@ dependencies = [ "prost", "rand 0.8.5", "regex", + "serde 1.0.163", "serde_json", "sha2 0.9.9", "tempfile", "tendermint-light-client", "test-log", "tokio", - "toml", + "toml 0.5.9", "tracing 0.1.37", - "tracing-subscriber 0.3.16", + "tracing-subscriber 0.3.17", ] [[package]] name = "namada_tx_prelude" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -4353,7 +4305,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -4362,7 +4314,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "namada_core", @@ -4397,7 +4349,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "cfg-if 1.0.0", "libc", "memoffset 0.7.1", @@ -4407,9 +4359,9 @@ dependencies = [ [[package]] name = "nom" -version = "5.1.2" +version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" dependencies = [ "lexical-core", "memchr", @@ -4418,9 +4370,9 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -4472,7 +4424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" dependencies = [ "num-bigint 0.4.3", - "num-complex 0.4.2", + "num-complex 0.4.3", "num-integer", "num-iter", "num-rational 0.4.1", @@ -4513,9 +4465,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits 0.2.15", ] @@ -4605,39 +4557,39 @@ dependencies = [ "num 0.4.0", "num-derive", "num-traits 0.2.15", - "serde 1.0.147", + "serde 1.0.163", "serde_derive", ] [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.2.6", "libc", ] [[package]] name = "num_enum" -version = "0.5.11" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ - "proc-macro-crate 1.2.1", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -4648,15 +4600,15 @@ checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "crc32fast", "hashbrown 0.11.2", - "indexmap", + "indexmap 1.9.3", "memchr", ] [[package]] name = "object" -version = "0.29.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] @@ -4706,11 +4658,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.42" +version = "0.10.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "cfg-if 1.0.0", "foreign-types", "libc", @@ -4721,13 +4673,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -4738,11 +4690,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.77" +version = "0.9.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", @@ -4756,7 +4707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.8", + "getrandom 0.2.9", "subtle 2.4.1", "zeroize", ] @@ -4794,30 +4745,30 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" dependencies = [ - "group", + "group 0.12.1", ] [[package]] name = "parity-scale-codec" -version = "3.2.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" dependencies = [ "arrayvec 0.7.2", "bitvec 1.0.1", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "serde 1.0.147", + "serde 1.0.163", ] [[package]] name = "parity-scale-codec-derive" -version = "3.1.3" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" dependencies = [ - "proc-macro-crate 1.2.1", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -4829,17 +4780,6 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -4847,34 +4787,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", + "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if 1.0.0", - "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall", - "smallvec", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -4899,22 +4825,11 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "paste" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pbkdf2" @@ -4932,7 +4847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", - "password-hash 0.3.2", + "password-hash", ] [[package]] @@ -4941,10 +4856,17 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", +] + +[[package]] +name = "pbkdf2" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ca0b5a68607598bf3bad68f32227a8164f6254833f84eafaac409cd6746c31" +dependencies = [ + "digest 0.10.6", "hmac 0.12.1", - "password-hash 0.4.2", - "sha2 0.10.6", ] [[package]] @@ -4988,21 +4910,22 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.1.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.3", ] [[package]] @@ -5017,22 +4940,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -5049,9 +4972,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" -version = "0.9.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", @@ -5059,9 +4982,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "poly1305" @@ -5082,9 +5005,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "predicates" -version = "2.1.3" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6bd09a7f7e68f3f0bf710fb7ab9c4615a488b58b5f653382a687701e458c92" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "itertools", @@ -5093,15 +5016,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] name = "predicates-tree" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" dependencies = [ "predicates-core", "termtree", @@ -5121,9 +5044,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebcd279d20a4a0a2404a33056388e950504d891c855c7975b9a8fef75f3bf04" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ "proc-macro2", "syn 1.0.109", @@ -5131,12 +5054,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.6" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b69d39aab54d069e7f2fe8cb970493e7834601ca2d8c65fd7bbd183578080d1" +checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" dependencies = [ "proc-macro2", - "syn 2.0.16", + "syn 2.0.15", ] [[package]] @@ -5159,18 +5082,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ - "toml", + "toml 0.5.9", ] [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "thiserror", - "toml", + "toml_edit", ] [[package]] @@ -5199,9 +5121,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", ] @@ -5213,14 +5135,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" dependencies = [ "bit-set", - "bitflags 1.3.2", + "bitflags 1.2.1", "byteorder", "lazy_static", "num-traits 0.2.15", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.6.28", + "regex-syntax 0.6.29", "rusty-fork", "tempfile", "unarray", @@ -5258,7 +5180,7 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease 0.1.24", + "prettyplease 0.1.25", "prost", "prost-types", "regex", @@ -5404,7 +5326,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", ] [[package]] @@ -5439,13 +5361,13 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ - "crossbeam-channel 0.5.6", + "crossbeam-channel 0.5.8", "crossbeam-deque", - "crossbeam-utils 0.8.12", + "crossbeam-utils 0.8.15", "num_cpus", ] @@ -5460,7 +5382,7 @@ dependencies = [ "digest 0.9.0", "jubjub", "rand_core 0.6.4", - "serde 1.0.147", + "serde 1.0.163", "thiserror", "zeroize", ] @@ -5471,7 +5393,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.2.1", ] [[package]] @@ -5480,8 +5411,8 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", - "redox_syscall", + "getrandom 0.2.9", + "redox_syscall 0.2.16", "thiserror", ] @@ -5513,14 +5444,14 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax 0.6.28", + "regex-syntax 0.6.29", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" @@ -5534,7 +5465,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "libc", "mach", "winapi", @@ -5542,18 +5473,18 @@ dependencies = [ [[package]] name = "rend" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" dependencies = [ "bytecheck", ] [[package]] name = "reqwest" -version = "0.11.14" +version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ "base64 0.21.0", "bytes", @@ -5564,7 +5495,6 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-rustls 0.23.2", "hyper-tls", "ipnet", "js-sys", @@ -5574,32 +5504,27 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.20.7", - "rustls-pemfile 1.0.2", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "tokio-rustls 0.23.4", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.22.5", "winreg", ] [[package]] name = "rfc6979" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "crypto-bigint", "hmac 0.12.1", - "zeroize", + "subtle 2.4.1", ] [[package]] @@ -5611,7 +5536,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -5623,28 +5548,31 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", ] [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" dependencies = [ + "bitvec 1.0.1", "bytecheck", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", + "tinyvec", + "uuid 1.3.2", ] [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" dependencies = [ "proc-macro2", "quote", @@ -5667,6 +5595,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ "bytes", + "rlp-derive", "rustc-hex", ] @@ -5709,9 +5638,9 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -5745,26 +5674,26 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.5" +version = "0.37.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" +checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" dependencies = [ - "bitflags 1.3.2", - "errno 0.2.8", + "bitflags 1.2.1", + "errno", "io-lifetimes", "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.42.0", + "linux-raw-sys 0.3.7", + "windows-sys 0.48.0", ] [[package]] name = "rustix" -version = "0.38.3" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ "bitflags 2.3.3", - "errno 0.3.1", + "errno", "libc", "linux-raw-sys 0.4.3", "windows-sys 0.48.0", @@ -5785,9 +5714,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -5809,27 +5738,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" -dependencies = [ - "base64 0.13.1", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ "base64 0.21.0", ] [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "rusty-fork" @@ -5845,9 +5765,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safe-proc-macro2" @@ -5896,12 +5816,6 @@ dependencies = [ "safe-regex-compiler", ] -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "salsa20" version = "0.10.2" @@ -5922,9 +5836,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.3.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" +checksum = "35c0a159d0c45c12b20c5a844feb1fe4bea86e28f17b92a5f0c42193634d3782" dependencies = [ "cfg-if 1.0.0", "derive_more", @@ -5934,11 +5848,11 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.3.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" +checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29" dependencies = [ - "proc-macro-crate 1.2.1", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -5946,12 +5860,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.42.0", ] [[package]] @@ -5966,12 +5879,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "scratch" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" - [[package]] name = "scrypt" version = "0.10.0" @@ -6012,13 +5919,13 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "sec1" -version = "0.3.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" dependencies = [ "base16ct", "der", - "generic-array 0.14.6", + "generic-array 0.14.7", "pkcs8", "subtle 2.4.1", "zeroize", @@ -6030,7 +5937,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" dependencies = [ - "bitflags 1.3.2", + "bitflags 1.2.1", "core-foundation", "core-foundation-sys", "libc", @@ -6039,9 +5946,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -6062,7 +5969,7 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -6074,6 +5981,12 @@ dependencies = [ "pest", ] +[[package]] +name = "send_wrapper" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" + [[package]] name = "send_wrapper" version = "0.6.0" @@ -6088,23 +6001,13 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-aux" -version = "4.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c599b3fd89a75e0c18d6d2be693ddb12cccaf771db4ff9e39097104808a014c0" -dependencies = [ - "serde 1.0.147", - "serde_json", -] - [[package]] name = "serde-hjson" version = "0.9.1" @@ -6119,11 +6022,11 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.7" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -6133,40 +6036,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", - "serde 1.0.147", + "serde 1.0.163", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", - "serde 1.0.147", + "serde 1.0.163", ] [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", +] + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde 1.0.163", ] [[package]] @@ -6178,7 +6090,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.147", + "serde 1.0.163", ] [[package]] @@ -6189,21 +6101,10 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.147", + "serde 1.0.163", "yaml-rust", ] -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha1" version = "0.10.5" @@ -6212,7 +6113,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.7", + "digest 0.10.6", ] [[package]] @@ -6248,16 +6149,16 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.7", + "digest 0.10.6", ] [[package]] name = "sha3" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", "keccak", ] @@ -6278,9 +6179,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" dependencies = [ "libc", "signal-hook-registry", @@ -6288,9 +6189,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -6300,11 +6201,23 @@ name = "signature" version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ - "digest 0.10.7", + "digest 0.10.6", "rand_core 0.6.4", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "simple-error" version = "0.2.3" @@ -6313,9 +6226,9 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -6337,9 +6250,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -6363,11 +6276,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" -version = "0.6.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", "der", @@ -6466,9 +6385,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", @@ -6481,23 +6400,11 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - [[package]] name = "sysinfo" -version = "0.29.6" +version = "0.27.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cb97a5a85a136d84e75d5c3cf89655090602efb1be0d8d5337b7e386af2908" +checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys", @@ -6526,21 +6433,21 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "tempfile" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if 1.0.0", "fastrand", - "redox_syscall", - "rustix 0.36.5", - "windows-sys 0.42.0", + "redox_syscall 0.3.5", + "rustix 0.37.13", + "windows-sys 0.45.0", ] [[package]] @@ -6558,12 +6465,12 @@ dependencies = [ "once_cell", "prost", "prost-types", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", "serde_json", "serde_repr", "sha2 0.9.9", - "signature", + "signature 1.6.4", "subtle 2.4.1", "subtle-encoding", "tendermint-proto", @@ -6577,10 +6484,10 @@ version = "0.23.6" source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=b7d1e5afc6f2ccb3fd1545c2174bab1cc48d7fa7#b7d1e5afc6f2ccb3fd1545c2174bab1cc48d7fa7" dependencies = [ "flex-error", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "tendermint", - "toml", + "toml 0.5.9", "url", ] @@ -6594,7 +6501,7 @@ dependencies = [ "derive_more", "flex-error", "futures", - "serde 1.0.147", + "serde 1.0.163", "serde_cbor", "serde_derive", "static_assertions", @@ -6612,7 +6519,7 @@ source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=b7d1e5afc6f2ccb dependencies = [ "derive_more", "flex-error", - "serde 1.0.147", + "serde 1.0.163", "tendermint", "time", ] @@ -6628,7 +6535,7 @@ dependencies = [ "num-traits 0.2.15", "prost", "prost-types", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", "subtle-encoding", "time", @@ -6643,14 +6550,14 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom 0.2.8", + "getrandom 0.2.9", "http", "hyper", "hyper-proxy", - "hyper-rustls 0.22.1", + "hyper-rustls", "peg", "pin-project", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", "serde_json", "subtle-encoding", @@ -6662,7 +6569,7 @@ dependencies = [ "tokio", "tracing 0.1.37", "url", - "uuid", + "uuid 0.8.2", "walkdir", ] @@ -6673,7 +6580,7 @@ source = "git+https://github.com/heliaxdev/tendermint-rs.git?rev=b7d1e5afc6f2ccb dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "simple-error", "tempfile", @@ -6681,20 +6588,11 @@ dependencies = [ "time", ] -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - [[package]] name = "termtree" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "test-log" @@ -6709,41 +6607,41 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if 1.0.0", "once_cell", ] [[package]] name = "tikv-jemalloc-sys" -version = "0.5.2+5.3.0-patched" +version = "0.5.3+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" +checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" dependencies = [ "cc", - "fs_extra", "libc", ] @@ -6754,7 +6652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", - "serde 1.0.147", + "serde 1.0.163", "time-core", "time-macros", ] @@ -6816,15 +6714,14 @@ dependencies = [ [[package]] name = "tiny_http" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0d6ef4e10d23c1efb862eecad25c5054429a71958b4eeef85eb5e7170b477ca" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" dependencies = [ "ascii", "chunked_transfer", + "httpdate", "log", - "time", - "url", ] [[package]] @@ -6838,28 +6735,27 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.21.2" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -6874,20 +6770,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -6904,22 +6800,11 @@ dependencies = [ "webpki 0.21.4", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.7", - "tokio", - "webpki 0.22.0", -] - [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -6941,9 +6826,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", @@ -6967,9 +6852,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -6985,7 +6870,41 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.147", + "serde 1.0.163", +] + +[[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde 1.0.163", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde 1.0.163", +] + +[[package]] +name = "toml_edit" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +dependencies = [ + "indexmap 2.0.0", + "serde 1.0.163", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] @@ -7012,7 +6931,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.7.4", + "tokio-util 0.7.8", "tower", "tower-layer", "tower-service", @@ -7026,7 +6945,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ - "prettyplease 0.1.24", + "prettyplease 0.1.25", "proc-macro2", "prost-build", "quote", @@ -7042,13 +6961,13 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap", + "indexmap 1.9.3", "pin-project", "pin-project-lite", "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.4", + "tokio-util 0.7.8", "tower-layer", "tower-service", "tracing 0.1.37", @@ -7072,25 +6991,6 @@ dependencies = [ "tracing-tower", ] -[[package]] -name = "tower-http" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" -dependencies = [ - "bitflags 1.3.2", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-layer" version = "0.3.2" @@ -7132,8 +7032,8 @@ dependencies = [ "cfg-if 1.0.0", "log", "pin-project-lite", - "tracing-attributes 0.1.23", - "tracing-core 0.1.30", + "tracing-attributes 0.1.24", + "tracing-core 0.1.31", ] [[package]] @@ -7142,9 +7042,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ - "crossbeam-channel 0.5.6", + "crossbeam-channel 0.5.8", "time", - "tracing-subscriber 0.3.16", + "tracing-subscriber 0.3.17", ] [[package]] @@ -7159,13 +7059,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -7178,9 +7078,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -7223,7 +7123,7 @@ checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "lazy_static", "log", - "tracing-core 0.1.30", + "tracing-core 0.1.31", ] [[package]] @@ -7232,8 +7132,8 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ - "serde 1.0.147", - "tracing-core 0.1.30", + "serde 1.0.163", + "tracing-core 0.1.31", ] [[package]] @@ -7244,25 +7144,25 @@ checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", "thread_local", - "tracing-core 0.1.30", + "tracing-core 0.1.31", ] [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "sharded-slab", "thread_local", "tracing 0.1.37", - "tracing-core 0.1.30", + "tracing-core 0.1.31", "tracing-log", "tracing-serde", ] @@ -7283,15 +7183,15 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ "base64 0.13.1", "byteorder", @@ -7300,26 +7200,17 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "sha-1", + "sha1", "thiserror", "url", "utf-8", ] -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] - [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" @@ -7356,15 +7247,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -7375,12 +7266,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" - [[package]] name = "unicode-width" version = "0.1.10" @@ -7444,10 +7329,16 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.8", - "serde 1.0.147", + "getrandom 0.2.9", + "serde 1.0.163", ] +[[package]] +name = "uuid" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" + [[package]] name = "valuable" version = "0.1.0" @@ -7527,12 +7418,11 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -7548,9 +7438,9 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" +checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" dependencies = [ "bytes", "futures-channel", @@ -7561,18 +7451,18 @@ dependencies = [ "log", "mime", "mime_guess", - "multipart", + "multer", "percent-encoding", "pin-project", - "rustls-pemfile 0.2.1", + "rustls-pemfile", "scoped-tls", - "serde 1.0.147", + "serde 1.0.163", "serde_json", "serde_urlencoded", "tokio", "tokio-stream", "tokio-tungstenite", - "tokio-util 0.7.4", + "tokio-util 0.7.8", "tower-service", "tracing 0.1.37", ] @@ -7591,9 +7481,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7601,24 +7491,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "083abe15c5d88556b77bdf7aef403625be9e327ad37c62c4e4129af740168163" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7628,9 +7518,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7638,28 +7528,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" [[package]] name = "wasm-encoder" -version = "0.19.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9424cdab516a16d4ea03c8f4a01b14e7b2d04a129dcc2bcdde5bcc5f68f06c41" +checksum = "d05d0b6fcd0aeb98adf16e7975331b3c17222aa815148f5b976370ce589d80ef" dependencies = [ "leb128", ] @@ -7673,21 +7563,6 @@ dependencies = [ "parity-wasm", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmer" version = "2.3.0" @@ -7695,7 +7570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" dependencies = [ "cfg-if 1.0.0", - "indexmap", + "indexmap 1.9.3", "js-sys", "loupe", "more-asserts", @@ -7749,7 +7624,7 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", "smallvec", "target-lexicon", @@ -7767,7 +7642,7 @@ dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "gimli", + "gimli 0.26.2", "loupe", "more-asserts", "rayon", @@ -7787,7 +7662,7 @@ dependencies = [ "byteorder", "dynasm", "dynasmrt", - "gimli", + "gimli 0.26.2", "lazy_static", "loupe", "more-asserts", @@ -7822,7 +7697,7 @@ dependencies = [ "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.147", + "serde 1.0.163", "serde_bytes", "target-lexicon", "thiserror", @@ -7846,7 +7721,7 @@ dependencies = [ "loupe", "object 0.28.4", "rkyv", - "serde 1.0.147", + "serde 1.0.163", "tempfile", "tracing 0.1.37", "wasmer-artifact", @@ -7914,11 +7789,11 @@ checksum = "39df01ea05dc0a9bab67e054c7cb01521e53b35a7bb90bd02eca564ed0b2667f" dependencies = [ "backtrace", "enum-iterator", - "indexmap", + "indexmap 1.9.3", "loupe", "more-asserts", "rkyv", - "serde 1.0.147", + "serde 1.0.163", "thiserror", ] @@ -7933,7 +7808,7 @@ dependencies = [ "cfg-if 1.0.0", "corosensei", "enum-iterator", - "indexmap", + "indexmap 1.9.3", "lazy_static", "libc", "loupe", @@ -7943,7 +7818,7 @@ dependencies = [ "region", "rkyv", "scopeguard", - "serde 1.0.147", + "serde 1.0.163", "thiserror", "wasmer-artifact", "wasmer-types", @@ -7962,7 +7837,7 @@ version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29e3ac9b780c7dda0cac7a52a5d6d2d6707cc6e3451c9db209b6c758f40d7acb" dependencies = [ - "indexmap", + "indexmap 1.9.3", "semver 1.0.17", ] @@ -7974,7 +7849,7 @@ checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" dependencies = [ "futures", "js-sys", - "parking_lot 0.12.1", + "parking_lot", "pin-utils", "slab", "wasm-bindgen", @@ -7982,9 +7857,9 @@ dependencies = [ [[package]] name = "wast" -version = "49.0.0" +version = "57.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ef81fcd60d244cafffeafac3d17615fdb2fddda6aca18f34a8ae233353587c" +checksum = "6eb0f5ed17ac4421193c7477da05892c2edafd67f9639e3c11a82086416662dc" dependencies = [ "leb128", "memchr", @@ -7994,18 +7869,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.51" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c347c4460ffb311e95aafccd8c29e4888f241b9e4b3bb0e0ccbd998de2c8c0d" +checksum = "ab9ab0d87337c3be2bb6fc5cd331c4ba9fd6bcb4ee85048a0dd59ed9ecf92e53" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" dependencies = [ "js-sys", "wasm-bindgen", @@ -8042,18 +7917,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki 0.22.0", ] [[package]] name = "which" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ "either", "libc", @@ -8100,6 +7975,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-sys" version = "0.33.0" @@ -8113,19 +7997,6 @@ dependencies = [ "windows_x86_64_msvc 0.33.0", ] -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -8141,13 +8012,22 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets 0.48.0", ] [[package]] @@ -8167,9 +8047,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", @@ -8198,12 +8078,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -8222,12 +8096,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -8246,12 +8114,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -8270,12 +8132,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -8306,12 +8162,6 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -8324,6 +8174,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" @@ -8345,7 +8204,7 @@ dependencies = [ "log", "pharos", "rustc_version 0.4.0", - "send_wrapper", + "send_wrapper 0.6.0", "thiserror", "wasm-bindgen", "wasm-bindgen-futures", @@ -8390,31 +8249,31 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", - "synstructure", + "syn 2.0.15", ] [[package]] name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", "libc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 15c0f09dbf..489a66b186 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ exclude = [ "wasm", "wasm_for_tests", + "test_fixtures", ] [workspace.package] @@ -31,7 +32,7 @@ keywords = ["blockchain", "privacy", "crypto", "protocol", "network"] license = "GPL-3.0" readme = "README.md" repository = "https://github.com/anoma/namada" -version = "0.20.1" +version = "0.21.0" [workspace.dependencies] ark-bls12-381 = {version = "0.3"} @@ -115,7 +116,7 @@ sha2 = "0.9.3" signal-hook = "0.3.9" slip10_ed25519 = "0.1.3" # sysinfo with disabled multithread feature -sysinfo = {version = "0.29.4", default-features = false} +sysinfo = {version = "0.27.8", default-features = false} tar = "0.4.37" tempfile = {version = "3.2.0"} tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs.git", rev = "b7d1e5afc6f2ccb3fd1545c2174bab1cc48d7fa7"} diff --git a/Makefile b/Makefile index 638b28ceaf..0b6345a50d 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ package = namada NAMADA_E2E_USE_PREBUILT_BINARIES ?= true NAMADA_E2E_DEBUG ?= true RUST_BACKTRACE ?= 1 +NAMADA_MASP_TEST_SEED ?= 0 cargo := $(env) cargo rustup := $(env) rustup @@ -116,12 +117,14 @@ audit: test: test-unit test-e2e test-wasm -# Unit tests with coverage report test-coverage: + # Run integration tests with pre-built MASP proofs + NAMADA_MASP_TEST_SEED=$(NAMADA_MASP_TEST_SEED) \ + NAMADA_MASP_TEST_PROOFS=load \ $(cargo) +$(nightly) llvm-cov --output-dir target \ --features namada/testing \ --html \ - -- --skip e2e --skip integration -Z unstable-options --report-time + -- --skip e2e -Z unstable-options --report-time # NOTE: `TEST_FILTER` is prepended with `e2e::`. Since filters in `cargo test` # work with a substring search, TEST_FILTER only works if it contains a string @@ -137,7 +140,23 @@ test-e2e: --test-threads=1 \ -Z unstable-options --report-time +# Run integration tests with pre-built MASP proofs test-integration: + NAMADA_MASP_TEST_SEED=$(NAMADA_MASP_TEST_SEED) \ + NAMADA_MASP_TEST_PROOFS=load \ + make test-integration-slow + +# Clear pre-built proofs, run integration tests and save the new proofs +test-integration-save-proofs: + # Clear old proofs first + rm --force test_fixtures/masp_proofs/*.bin || true + NAMADA_MASP_TEST_SEED=$(NAMADA_MASP_TEST_SEED) \ + NAMADA_MASP_TEST_PROOFS=save \ + TEST_FILTER=masp \ + make test-integration-slow + +# Run integration tests without specifiying any pre-built MASP proofs option +test-integration-slow: RUST_BACKTRACE=$(RUST_BACKTRACE) \ $(cargo) +$(nightly) test integration::$(TEST_FILTER) \ -Z unstable-options \ diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index d6a3cbdaf9..ae62212975 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -3,6 +3,7 @@ use color_eyre::eyre::{eyre, Report, Result}; use namada::ledger::eth_bridge::bridge_pool; use namada::ledger::rpc::wait_until_node_is_synched; +use namada::ledger::tx::dump_tx; use namada::ledger::{signing, tx as sdk_tx}; use namada::types::control_flow::ProceedOrElse; use namada_apps::cli; @@ -67,7 +68,7 @@ pub async fn main() -> Result<()> { tx::submit_ibc_transfer::(&client, ctx, args) .await?; } - Sub::TxUpdateVp(TxUpdateVp(mut args)) => { + Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) @@ -76,8 +77,10 @@ pub async fn main() -> Result<()> { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::(&client, &mut ctx, args) - .await?; + tx::submit_update_account::( + &client, &mut ctx, args, + ) + .await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { let client = HttpClient::new(utils::take_config_address( @@ -213,26 +216,52 @@ pub async fn main() -> Result<()> { .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let tx_args = args.tx.clone(); - let (mut tx, addr, pk) = bridge_pool::build_bridge_pool_tx( + + let default_signer = Some(args.sender.clone()); + let signing_data = signing::aux_signing_data( &client, &mut ctx.wallet, - args, + &args.tx, + &args.sender, + default_signer, ) - .await - .unwrap(); + .await?; + tx::submit_reveal_aux( &client, &mut ctx, - &tx_args, - addr, - pk.clone(), - &mut tx, + tx_args.clone(), + &args.sender, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &tx_args, &pk) + + let tx = bridge_pool::build_bridge_pool_tx( + &client, + args.clone(), + signing_data.gas_payer.clone(), + ) .await?; - sdk_tx::process_tx(&client, &mut ctx.wallet, &tx_args, tx) + + signing::generate_test_vector(&client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + dump_tx(&args.tx, tx); + } else { + signing::sign_tx( + &mut ctx.wallet, + &tx_args, + &mut tx, + signing_data, + )?; + + sdk_tx::process_tx( + &client, + &mut ctx.wallet, + &tx_args, + tx, + ) .await?; + } } Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { let client = HttpClient::new(utils::take_config_address( @@ -463,6 +492,16 @@ pub async fn main() -> Result<()> { let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } + Sub::QueryAccount(QueryAccount(args)) => { + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_account(&client, args).await; + } } } cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { diff --git a/apps/src/bin/namada/cli.rs b/apps/src/bin/namada/cli.rs index 8c7a1e0b49..0259ba525a 100644 --- a/apps/src/bin/namada/cli.rs +++ b/apps/src/bin/namada/cli.rs @@ -47,7 +47,7 @@ fn handle_command(cmd: cli::cmds::Namada, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Namada::TxCustom(_) | cli::cmds::Namada::TxTransfer(_) | cli::cmds::Namada::TxIbcTransfer(_) - | cli::cmds::Namada::TxUpdateVp(_) + | cli::cmds::Namada::TxUpdateAccount(_) | cli::cmds::Namada::TxRevealPk(_) | cli::cmds::Namada::TxInitProposal(_) | cli::cmds::Namada::TxVoteProposal(_) => { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index c8023c0ec5..1ed2275f2c 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -57,7 +57,7 @@ pub mod cmds { TxCustom(TxCustom), TxTransfer(TxTransfer), TxIbcTransfer(TxIbcTransfer), - TxUpdateVp(TxUpdateVp), + TxUpdateAccount(TxUpdateAccount), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), TxRevealPk(TxRevealPk), @@ -74,7 +74,7 @@ pub mod cmds { .subcommand(TxCustom::def()) .subcommand(TxTransfer::def()) .subcommand(TxIbcTransfer::def()) - .subcommand(TxUpdateVp::def()) + .subcommand(TxUpdateAccount::def()) .subcommand(TxInitProposal::def()) .subcommand(TxVoteProposal::def()) .subcommand(TxRevealPk::def()) @@ -92,7 +92,8 @@ pub mod cmds { let tx_transfer = SubCmd::parse(matches).map(Self::TxTransfer); let tx_ibc_transfer = SubCmd::parse(matches).map(Self::TxIbcTransfer); - let tx_update_vp = SubCmd::parse(matches).map(Self::TxUpdateVp); + let tx_update_account = + SubCmd::parse(matches).map(Self::TxUpdateAccount); let tx_init_proposal = SubCmd::parse(matches).map(Self::TxInitProposal); let tx_vote_proposal = @@ -106,7 +107,7 @@ pub mod cmds { .or(tx_custom) .or(tx_transfer) .or(tx_ibc_transfer) - .or(tx_update_vp) + .or(tx_update_account) .or(tx_init_proposal) .or(tx_vote_proposal) .or(tx_reveal_pk) @@ -213,7 +214,7 @@ pub mod cmds { .subcommand(TxCustom::def().display_order(1)) .subcommand(TxTransfer::def().display_order(1)) .subcommand(TxIbcTransfer::def().display_order(1)) - .subcommand(TxUpdateVp::def().display_order(1)) + .subcommand(TxUpdateAccount::def().display_order(1)) .subcommand(TxInitAccount::def().display_order(1)) .subcommand(TxRevealPk::def().display_order(1)) // Proposal transactions @@ -230,6 +231,7 @@ pub mod cmds { .subcommand(AddToEthBridgePool::def().display_order(3)) // Queries .subcommand(QueryEpoch::def().display_order(4)) + .subcommand(QueryAccount::def().display_order(4)) .subcommand(QueryTransfers::def().display_order(4)) .subcommand(QueryConversions::def().display_order(4)) .subcommand(QueryBlock::def().display_order(4)) @@ -244,9 +246,12 @@ pub mod cmds { .subcommand(QueryProposal::def().display_order(4)) .subcommand(QueryProposalResult::def().display_order(4)) .subcommand(QueryProtocolParameters::def().display_order(4)) + .subcommand(QueryPgf::def().display_order(4)) .subcommand(QueryValidatorState::def().display_order(4)) + // Actions + .subcommand(SignTx::def().display_order(5)) // Utils - .subcommand(Utils::def().display_order(5)) + .subcommand(Utils::def().display_order(6)) } fn parse(matches: &ArgMatches) -> Option { @@ -254,7 +259,8 @@ pub mod cmds { let tx_custom = Self::parse_with_ctx(matches, TxCustom); let tx_transfer = Self::parse_with_ctx(matches, TxTransfer); let tx_ibc_transfer = Self::parse_with_ctx(matches, TxIbcTransfer); - let tx_update_vp = Self::parse_with_ctx(matches, TxUpdateVp); + let tx_update_account = + Self::parse_with_ctx(matches, TxUpdateAccount); let tx_init_account = Self::parse_with_ctx(matches, TxInitAccount); let tx_init_validator = Self::parse_with_ctx(matches, TxInitValidator); @@ -271,6 +277,7 @@ pub mod cmds { let unbond = Self::parse_with_ctx(matches, Unbond); let withdraw = Self::parse_with_ctx(matches, Withdraw); let query_epoch = Self::parse_with_ctx(matches, QueryEpoch); + let query_account = Self::parse_with_ctx(matches, QueryAccount); let query_transfers = Self::parse_with_ctx(matches, QueryTransfers); let query_conversions = Self::parse_with_ctx(matches, QueryConversions); @@ -291,15 +298,17 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProposalResult); let query_protocol_parameters = Self::parse_with_ctx(matches, QueryProtocolParameters); + let query_pgf = Self::parse_with_ctx(matches, QueryPgf); let query_validator_state = Self::parse_with_ctx(matches, QueryValidatorState); let add_to_eth_bridge_pool = Self::parse_with_ctx(matches, AddToEthBridgePool); + let sign_tx = Self::parse_with_ctx(matches, SignTx); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) .or(tx_ibc_transfer) - .or(tx_update_vp) + .or(tx_update_account) .or(tx_init_account) .or(tx_reveal_pk) .or(tx_init_proposal) @@ -326,7 +335,10 @@ pub mod cmds { .or(query_proposal) .or(query_proposal_result) .or(query_protocol_parameters) + .or(query_pgf) .or(query_validator_state) + .or(query_account) + .or(sign_tx) .or(utils) } } @@ -368,7 +380,7 @@ pub mod cmds { TxTransfer(TxTransfer), TxIbcTransfer(TxIbcTransfer), QueryResult(QueryResult), - TxUpdateVp(TxUpdateVp), + TxUpdateAccount(TxUpdateAccount), TxInitAccount(TxInitAccount), TxInitValidator(TxInitValidator), TxCommissionRateChange(TxCommissionRateChange), @@ -381,6 +393,7 @@ pub mod cmds { Withdraw(Withdraw), AddToEthBridgePool(AddToEthBridgePool), QueryEpoch(QueryEpoch), + QueryAccount(QueryAccount), QueryTransfers(QueryTransfers), QueryConversions(QueryConversions), QueryBlock(QueryBlock), @@ -395,7 +408,9 @@ pub mod cmds { QueryProposal(QueryProposal), QueryProposalResult(QueryProposalResult), QueryProtocolParameters(QueryProtocolParameters), + QueryPgf(QueryPgf), QueryValidatorState(QueryValidatorState), + SignTx(SignTx), } #[allow(clippy::large_enum_variant)] @@ -1174,6 +1189,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryPgf(pub args::QueryPgf); + + impl SubCmd for QueryPgf { + const CMD: &'static str = "query-pgf"; + + fn parse(matches: &ArgMatches) -> Option + where + Self: Sized, + { + matches + .subcommand_matches(Self::CMD) + .map(|matches| QueryPgf(args::QueryPgf::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Query pgf stewards and continous funding.") + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct TxCustom(pub args::TxCustom); @@ -1232,15 +1269,15 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct TxUpdateVp(pub args::TxUpdateVp); + pub struct TxUpdateAccount(pub args::TxUpdateAccount); - impl SubCmd for TxUpdateVp { - const CMD: &'static str = "update"; + impl SubCmd for TxUpdateAccount { + const CMD: &'static str = "update-account"; fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| TxUpdateVp(args::TxUpdateVp::parse(matches))) + matches.subcommand_matches(Self::CMD).map(|matches| { + TxUpdateAccount(args::TxUpdateAccount::parse(matches)) + }) } fn def() -> App { @@ -1249,7 +1286,7 @@ pub mod cmds { "Send a signed transaction to update account's validity \ predicate.", ) - .add_args::>() + .add_args::>() } } @@ -1394,6 +1431,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryAccount(pub args::QueryAccount); + + impl SubCmd for QueryAccount { + const CMD: &'static str = "query-account"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| QueryAccount(args::QueryAccount::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Query the substorage space of a specific enstablished \ + address.", + ) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct QueryConversions(pub args::QueryConversions); @@ -1489,6 +1548,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct SignTx(pub args::SignTx); + + impl SubCmd for SignTx { + const CMD: &'static str = "sign-tx"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| SignTx(args::SignTx::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Query PoS bonded stake.") + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct QueryValidatorState( pub args::QueryValidatorState, @@ -2316,6 +2394,7 @@ pub mod args { pub const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; + pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; pub const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; @@ -2378,15 +2457,8 @@ pub mod args { ); pub const ETH_SYNC: ArgFlag = flag("sync"); pub const EXPIRATION_OPT: ArgOpt = arg_opt("expiration"); - pub const FEE_AMOUNT: ArgDefault = arg_default( - "fee-amount", - DefaultFn(|| token::DenominatedAmount { - amount: token::Amount::default(), - denom: NATIVE_MAX_DECIMAL_PLACES.into(), - }), - ); - pub const FEE_PAYER: Arg = arg("fee-payer"); pub const FORCE: ArgFlag = flag("force"); + pub const GAS_PAYER: ArgOpt = arg("gas-payer").opt(); pub const GAS_AMOUNT: ArgDefault = arg_default( "gas-amount", DefaultFn(|| token::DenominatedAmount { @@ -2403,6 +2475,14 @@ pub mod args { ); pub const GAS_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".parse().unwrap())); + pub const FEE_PAYER: Arg = arg("fee-payer"); + pub const FEE_AMOUNT: ArgDefault = arg_default( + "fee-amount", + DefaultFn(|| token::DenominatedAmount { + amount: token::Amount::default(), + denom: NATIVE_MAX_DECIMAL_PLACES.into(), + }), + ); pub const GENESIS_PATH: Arg = arg("genesis-path"); pub const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); @@ -2434,6 +2514,8 @@ pub mod args { pub const NAMADA_START_TIME: ArgOpt = arg_opt("time"); pub const NO_CONVERSIONS: ArgFlag = flag("no-conversions"); pub const OUT_FILE_PATH_OPT: ArgOpt = arg_opt("out-file-path"); + pub const OUTPUT_FOLDER_PATH: ArgOpt = + arg_opt("output-folder-path"); pub const OWNER: Arg = arg("owner"); pub const OWNER_OPT: ArgOpt = OWNER.opt(); pub const PIN: ArgFlag = flag("pin"); @@ -2441,10 +2523,14 @@ pub mod args { "port-id", DefaultFn(|| PortId::from_str("transfer").unwrap()), ); + pub const PROPOSAL_ETH: ArgFlag = flag("eth"); + pub const PROPOSAL_PGF_STEWARD: ArgFlag = flag("pgf-stewards"); + pub const PROPOSAL_PGF_FUNDING: ArgFlag = flag("pgf-funding"); pub const PROPOSAL_OFFLINE: ArgFlag = flag("offline"); pub const PROTOCOL_KEY: ArgOpt = arg_opt("protocol-key"); pub const PRE_GENESIS_PATH: ArgOpt = arg_opt("pre-genesis-path"); pub const PUBLIC_KEY: Arg = arg("public-key"); + pub const PUBLIC_KEYS: ArgMulti = arg_multi("public-keys"); pub const PROPOSAL_ID: Arg = arg("proposal-id"); pub const PROPOSAL_ID_OPT: ArgOpt = arg_opt("proposal-id"); pub const PROPOSAL_VOTE_PGF_OPT: ArgOpt = arg_opt("pgf"); @@ -2460,9 +2546,8 @@ pub mod args { pub const SAFE_MODE: ArgFlag = flag("safe-mode"); pub const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); - pub const SIGNER: ArgOpt = arg_opt("signer"); - pub const SIGNING_KEY_OPT: ArgOpt = SIGNING_KEY.opt(); - pub const SIGNING_KEY: Arg = arg("signing-key"); + pub const SIGNING_KEYS: ArgMulti = arg_multi("signing-keys"); + pub const SIGNATURES: ArgMulti = arg_multi("signatures"); pub const SOURCE: Arg = arg("source"); pub const SOURCE_OPT: ArgOpt = SOURCE.opt(); pub const STORAGE_KEY: Arg = arg("storage-key"); @@ -2475,16 +2560,19 @@ pub mod args { pub const TRANSFER_SOURCE: Arg = arg("source"); pub const TRANSFER_TARGET: Arg = arg("target"); pub const TX_HASH: Arg = arg("tx-hash"); + pub const THRESOLD: ArgOpt = arg_opt("threshold"); pub const UNSAFE_DONT_ENCRYPT: ArgFlag = flag("unsafe-dont-encrypt"); pub const UNSAFE_SHOW_SECRET: ArgFlag = flag("unsafe-show-secret"); pub const VALIDATOR: Arg = arg("validator"); pub const VALIDATOR_OPT: ArgOpt = VALIDATOR.opt(); pub const VALIDATOR_ACCOUNT_KEY: ArgOpt = arg_opt("account-key"); - pub const VALIDATOR_CODE_PATH: ArgOpt = - arg_opt("validator-code-path"); + pub const VALIDATOR_ACCOUNT_KEYS: ArgMulti = + arg_multi("account-keys"); pub const VALIDATOR_CONSENSUS_KEY: ArgOpt = arg_opt("consensus-key"); + pub const VALIDATOR_CODE_PATH: ArgOpt = + arg_opt("validator-code-path"); pub const VALIDATOR_ETH_COLD_KEY: ArgOpt = arg_opt("eth-cold-key"); pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = @@ -2496,6 +2584,8 @@ pub mod args { pub const WALLET_ALIAS_FORCE: ArgFlag = flag("wallet-alias-force"); pub const WASM_CHECKSUMS_PATH: Arg = arg("wasm-checksums-path"); pub const WASM_DIR: ArgOpt = arg_opt("wasm-dir"); + pub const TX_PATH: Arg = arg("tx-path"); + pub const TX_PATH_OPT: ArgOpt = TX_PATH.opt(); /// Global command arguments #[derive(Clone, Debug)] @@ -2702,9 +2792,9 @@ pub mod args { recipient: self.recipient, sender: ctx.get(&self.sender), amount: self.amount, - gas_amount: self.gas_amount, - gas_payer: ctx.get(&self.gas_payer), - code_path: ctx.read_wasm(self.code_path), + fee_amount: self.fee_amount, + fee_payer: ctx.get(&self.fee_payer), + code_path: self.code_path, } } } @@ -2716,8 +2806,8 @@ pub mod args { let recipient = ETH_ADDRESS.parse(matches); let sender = ADDRESS.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); - let gas_amount = FEE_AMOUNT.parse(matches).amount; - let gas_payer = FEE_PAYER.parse(matches); + let fee_amount = FEE_AMOUNT.parse(matches).amount; + let fee_payer = FEE_PAYER.parse(matches); let code_path = PathBuf::from(TX_BRIDGE_POOL_WASM); Self { tx, @@ -2725,8 +2815,8 @@ pub mod args { recipient, sender, amount, - gas_amount, - gas_payer, + fee_amount, + fee_payer, code_path, } } @@ -3110,11 +3200,15 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxCustom { TxCustom:: { tx: self.tx.to_sdk(ctx), - code_path: ctx.read_wasm(self.code_path), + code_path: self.code_path, data_path: self.data_path.map(|data_path| { std::fs::read(data_path) - .expect("Expected a file at given data path") + .expect("Expected a file at given path") }), + serialized_tx: self.serialized_tx.map(|path| { + std::fs::read(path).expect("Expected a file at given path") + }), + owner: ctx.get(&self.owner), } } } @@ -3122,26 +3216,50 @@ pub mod args { impl Args for TxCustom { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let code_path = CODE_PATH.parse(matches); + let code_path = CODE_PATH_OPT.parse(matches); let data_path = DATA_PATH_OPT.parse(matches); + let serialized_tx = TX_PATH_OPT.parse(matches); + let owner = OWNER.parse(matches); Self { tx, code_path, data_path, + serialized_tx, + owner, } } fn def(app: App) -> App { app.add_args::>() .arg( - CODE_PATH + CODE_PATH_OPT .def() - .help("The path to the transaction's WASM code."), + .help("The path to the transaction's WASM code.") + .conflicts_with(TX_PATH_OPT.name), ) - .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.", + .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.", + ) + .requires(CODE_PATH_OPT.name) + .conflicts_with(TX_PATH_OPT.name), + ) + .arg( + TX_PATH_OPT + .def() + .help("The path to a serialized transaction.") + .conflicts_with_all([ + CODE_PATH_OPT.name, + DATA_PATH_OPT.name, + ]), + ) + .arg(OWNER.def().help( + "The address corresponding to the signatures or signing \ + keys.", )) } } @@ -3271,10 +3389,14 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { TxInitAccount:: { tx: self.tx.to_sdk(ctx), - source: ctx.get(&self.source), - vp_code_path: self.vp_code_path.to_path_buf(), - tx_code_path: self.tx_code_path.to_path_buf(), - public_key: ctx.get_cached(&self.public_key), + vp_code_path: self.vp_code_path, + tx_code_path: self.tx_code_path, + public_keys: self + .public_keys + .iter() + .map(|pk| ctx.get_cached(pk)) + .collect(), + threshold: self.threshold, } } } @@ -3282,34 +3404,36 @@ pub mod args { impl Args for TxInitAccount { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = SOURCE.parse(matches); let vp_code_path = CODE_PATH_OPT .parse(matches) .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); let tx_code_path = PathBuf::from(TX_INIT_ACCOUNT_WASM); - let public_key = PUBLIC_KEY.parse(matches); + let public_keys = PUBLIC_KEYS.parse(matches); + let threshold = THRESOLD.parse(matches); Self { tx, - source, vp_code_path, - public_key, + public_keys, + threshold, tx_code_path, } } fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().help( - "The source account's address that signs the transaction.", - )) .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().help( - "A public key to be used for the new account in \ - hexadecimal encoding.", + .arg(PUBLIC_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding.", + )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", )) } } @@ -3318,9 +3442,13 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { TxInitValidator:: { tx: self.tx.to_sdk(ctx), - source: ctx.get(&self.source), scheme: self.scheme, - account_key: self.account_key.map(|x| ctx.get_cached(&x)), + account_keys: self + .account_keys + .iter() + .map(|x| ctx.get_cached(x)) + .collect(), + threshold: self.threshold, consensus_key: self.consensus_key.map(|x| ctx.get_cached(&x)), eth_cold_key: self.eth_cold_key.map(|x| ctx.get_cached(&x)), eth_hot_key: self.eth_hot_key.map(|x| ctx.get_cached(&x)), @@ -3339,9 +3467,8 @@ pub mod args { impl Args for TxInitValidator { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = SOURCE.parse(matches); let scheme = SCHEME.parse(matches); - let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); + let account_keys = VALIDATOR_ACCOUNT_KEYS.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); let eth_cold_key = VALIDATOR_ETH_COLD_KEY.parse(matches); let eth_hot_key = VALIDATOR_ETH_HOT_KEY.parse(matches); @@ -3354,11 +3481,12 @@ pub mod args { .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let tx_code_path = PathBuf::from(TX_INIT_VALIDATOR_WASM); + let threshold = THRESOLD.parse(matches); Self { tx, - source, scheme, - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -3373,16 +3501,14 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().help( - "The source account's address that signs the transaction.", - )) .arg(SCHEME.def().help( "The key scheme/type used for the validator keys. \ Currently supports ed25519 and secp256k1.", )) - .arg(VALIDATOR_ACCOUNT_KEY.def().help( - "A public key for the validator account. A new one will \ - be generated if none given.", + .arg(VALIDATOR_ACCOUNT_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding. A new one will be generated if \ + none given.", )) .arg(VALIDATOR_CONSENSUS_KEY.def().help( "A consensus key for the validator account. A new one \ @@ -3423,38 +3549,53 @@ pub mod args { "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", + )) } } - impl CliToSdk> for TxUpdateVp { - fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { - TxUpdateVp:: { + impl CliToSdk> for TxUpdateAccount { + fn to_sdk(self, ctx: &mut Context) -> TxUpdateAccount { + TxUpdateAccount:: { tx: self.tx.to_sdk(ctx), vp_code_path: self.vp_code_path, tx_code_path: self.tx_code_path, addr: ctx.get(&self.addr), + public_keys: self + .public_keys + .iter() + .map(|pk| ctx.get_cached(pk)) + .collect(), + threshold: self.threshold, } } } - impl Args for TxUpdateVp { + impl Args for TxUpdateAccount { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let vp_code_path = CODE_PATH.parse(matches); + let vp_code_path = CODE_PATH_OPT.parse(matches); let addr = ADDRESS.parse(matches); - let tx_code_path = PathBuf::from(TX_UPDATE_VP_WASM); + let tx_code_path = PathBuf::from(TX_UPDATE_ACCOUNT_WASM); + let public_keys = PUBLIC_KEYS.parse(matches); + let threshold = THRESOLD.parse(matches); Self { tx, vp_code_path, addr, tx_code_path, + public_keys, + threshold, } } fn def(app: App) -> App { app.add_args::>() .arg( - CODE_PATH.def().help( + CODE_PATH_OPT.def().help( "The path to the new validity predicate WASM code.", ), ) @@ -3462,6 +3603,15 @@ pub mod args { "The account's address. It's key is used to produce the \ signature.", )) + .arg(PUBLIC_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding.", + )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", + )) } } @@ -3565,26 +3715,15 @@ pub mod args { )) } } - #[derive(Clone, Debug)] - pub struct InitProposal { - /// Common tx arguments - pub tx: Tx, - /// The proposal file path - pub proposal_data: PathBuf, - /// Flag if proposal should be run offline - pub offline: bool, - /// Native token address - pub native_token: C::NativeAddress, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, - } impl CliToSdk> for InitProposal { fn to_sdk(self, ctx: &mut Context) -> InitProposal { InitProposal:: { tx: self.tx.to_sdk(ctx), - proposal_data: self.proposal_data, - offline: self.offline, + proposal_data: std::fs::read(self.proposal_data).expect(""), + is_offline: self.is_offline, + is_pgf_stewards: self.is_pgf_stewards, + is_pgf_funding: self.is_pgf_funding, native_token: ctx.native_token.clone(), tx_code_path: self.tx_code_path, } @@ -3595,15 +3734,19 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let proposal_data = DATA_PATH.parse(matches); - let offline = PROPOSAL_OFFLINE.parse(matches); + let is_offline = PROPOSAL_OFFLINE.parse(matches); + let is_pgf_stewards = PROPOSAL_PGF_STEWARD.parse(matches); + let is_pgf_funding = PROPOSAL_PGF_FUNDING.parse(matches); let tx_code_path = PathBuf::from(TX_INIT_PROPOSAL); Self { tx, proposal_data, - offline, native_token: (), tx_code_path, + is_offline, + is_pgf_stewards, + is_pgf_funding, } } @@ -3615,42 +3758,66 @@ pub mod args { .arg( PROPOSAL_OFFLINE .def() - .help("Flag if the proposal vote should run offline."), + .help( + "Flag if the proposal should be serialized \ + offline (only for default types).", + ) + .conflicts_with_all([ + PROPOSAL_PGF_FUNDING.name, + PROPOSAL_PGF_STEWARD.name, + PROPOSAL_ETH.name, + ]), + ) + .arg( + PROPOSAL_ETH + .def() + .help("Flag if the proposal is of type eth.") + .conflicts_with_all([ + PROPOSAL_PGF_FUNDING.name, + PROPOSAL_PGF_STEWARD.name, + ]), + ) + .arg( + PROPOSAL_PGF_STEWARD + .def() + .help( + "Flag if the proposal is of type pgf-stewards. \ + Used to elect/remove stewards.", + ) + .conflicts_with_all([ + PROPOSAL_ETH.name, + PROPOSAL_PGF_FUNDING.name, + ]), + ) + .arg( + PROPOSAL_PGF_FUNDING + .def() + .help( + "Flag if the proposal is of type pgf-funding. \ + Used to control continous/retro pgf fundings.", + ) + .conflicts_with_all([ + PROPOSAL_ETH.name, + PROPOSAL_PGF_STEWARD.name, + ]), ) } } - #[derive(Clone, Debug)] - pub struct VoteProposal { - /// Common tx arguments - pub tx: Tx, - /// Proposal id - pub proposal_id: Option, - /// The vote - pub vote: String, - /// PGF proposal - pub proposal_pgf: Option, - /// ETH proposal - pub proposal_eth: Option, - /// Flag if proposal vote should be run offline - pub offline: bool, - /// The proposal file path - pub proposal_data: Option, - /// Path to the TX WASM code file - pub tx_code_path: PathBuf, - } - impl CliToSdk> for VoteProposal { fn to_sdk(self, ctx: &mut Context) -> VoteProposal { VoteProposal:: { tx: self.tx.to_sdk(ctx), proposal_id: self.proposal_id, vote: self.vote, - offline: self.offline, - proposal_data: self.proposal_data, + voter: ctx.get(&self.voter), + is_offline: self.is_offline, + proposal_data: self.proposal_data.map(|path| { + println!("Not able to read {}.", path.to_string_lossy()); + std::fs::read(path) + .expect("Should be able to read the file.") + }), tx_code_path: self.tx_code_path.to_path_buf(), - proposal_pgf: self.proposal_pgf, - proposal_eth: self.proposal_eth, } } } @@ -3659,10 +3826,9 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let proposal_id = PROPOSAL_ID_OPT.parse(matches); - let proposal_pgf = PROPOSAL_VOTE_PGF_OPT.parse(matches); - let proposal_eth = PROPOSAL_VOTE_ETH_OPT.parse(matches); let vote = PROPOSAL_VOTE.parse(matches); - let offline = PROPOSAL_OFFLINE.parse(matches); + let voter = ADDRESS.parse(matches); + let is_offline = PROPOSAL_OFFLINE.parse(matches); let proposal_data = DATA_PATH_OPT.parse(matches); let tx_code_path = PathBuf::from(TX_VOTE_PROPOSAL); @@ -3670,9 +3836,8 @@ pub mod args { tx, proposal_id, vote, - proposal_pgf, - proposal_eth, - offline, + is_offline, + voter, proposal_data, tx_code_path, } @@ -3692,29 +3857,7 @@ pub mod args { .arg( PROPOSAL_VOTE .def() - .help("The vote for the proposal. Either yay or nay"), - ) - .arg( - PROPOSAL_VOTE_PGF_OPT - .def() - .help( - "The list of proposed councils and spending \ - caps:\n$council1 $cap1 $council2 $cap2 ... \ - (council is bech32m encoded address, cap is \ - expressed in microNAM", - ) - .requires(PROPOSAL_ID.name) - .conflicts_with(PROPOSAL_VOTE_ETH_OPT.name), - ) - .arg( - PROPOSAL_VOTE_ETH_OPT - .def() - .help( - "The signing key and message bytes (hex encoded) \ - to be signed: $signing_key $message", - ) - .requires(PROPOSAL_ID.name) - .conflicts_with(PROPOSAL_VOTE_PGF_OPT.name), + .help("The vote for the proposal. Either yay or nay."), ) .arg( PROPOSAL_OFFLINE @@ -3729,8 +3872,10 @@ pub mod args { "The data path file (json) that describes the \ proposal.", ) + .requires(PROPOSAL_OFFLINE.name) .conflicts_with(PROPOSAL_ID.name), ) + .arg(ADDRESS.def().help("The address of the voter.")) } } @@ -3775,7 +3920,7 @@ pub mod args { } fn def(app: App) -> App { - app.add_args::>() + app.add_args::>() .arg(PROPOSAL_ID_OPT.def().help("The proposal identifier.")) } } @@ -3820,7 +3965,15 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(PROPOSAL_ID_OPT.def().help("The proposal identifier.")) + .arg( + PROPOSAL_ID_OPT + .def() + .help("The proposal identifier.") + .conflicts_with_all([ + PROPOSAL_OFFLINE.name, + DATA_PATH_OPT.name, + ]), + ) .arg( PROPOSAL_OFFLINE .def() @@ -3828,16 +3981,18 @@ pub mod args { "Flag if the proposal result should run on \ offline data.", ) - .conflicts_with(PROPOSAL_ID.name), + .conflicts_with(PROPOSAL_ID.name) + .requires(DATA_PATH_OPT.name), ) .arg( DATA_PATH_OPT .def() .help( "The path to the folder containing the proposal \ - json and votes", + and votes files in json format.", ) - .conflicts_with(PROPOSAL_ID.name), + .conflicts_with(PROPOSAL_ID.name) + .requires(PROPOSAL_OFFLINE.name), ) } } @@ -3867,6 +4022,26 @@ pub mod args { } } + impl CliToSdk> for QueryPgf { + fn to_sdk(self, ctx: &mut Context) -> QueryPgf { + QueryPgf:: { + query: self.query.to_sdk(ctx), + } + } + } + + impl Args for QueryPgf { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + + Self { query } + } + + fn def(app: App) -> App { + app.add_args::>() + } + } + impl CliToSdk> for Withdraw { fn to_sdk(self, ctx: &mut Context) -> Withdraw { Withdraw:: { @@ -3940,6 +4115,32 @@ pub mod args { } } + impl CliToSdk> for QueryAccount { + fn to_sdk(self, ctx: &mut Context) -> QueryAccount { + QueryAccount:: { + query: self.query.to_sdk(ctx), + owner: ctx.get(&self.owner), + } + } + } + + impl Args for QueryAccount { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let owner = OWNER.parse(matches); + Self { query, owner } + } + + fn def(app: App) -> App { + app.add_args::>().arg( + OWNER + .def() + .help("The substorage space address to query.") + .required(true), + ) + } + } + impl CliToSdk> for QueryBalance { fn to_sdk(self, ctx: &mut Context) -> QueryBalance { QueryBalance:: { @@ -4196,6 +4397,39 @@ pub mod args { } } + impl CliToSdk> for SignTx { + fn to_sdk(self, ctx: &mut Context) -> SignTx { + SignTx:: { + tx: self.tx.to_sdk(ctx), + tx_data: std::fs::read(self.tx_data).expect(""), + owner: ctx.get(&self.owner), + } + } + } + + impl Args for SignTx { + fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); + let tx_path = TX_PATH.parse(matches); + let owner = OWNER.parse(matches); + Self { + tx, + tx_data: tx_path, + owner, + } + } + + fn def(app: App) -> App { + app.add_args::>() + .arg( + TX_PATH.def().help( + "The path to the tx file with the serialized tx.", + ), + ) + .arg(OWNER.def().help("The address of the account owner")) + } + } + impl CliToSdk> for QueryCommissionRate { fn to_sdk(self, ctx: &mut Context) -> QueryCommissionRate { QueryCommissionRate:: { @@ -4350,19 +4584,29 @@ pub mod args { Tx:: { dry_run: self.dry_run, dump_tx: self.dump_tx, + output_folder: self.output_folder, force: self.force, broadcast_only: self.broadcast_only, ledger_address: (), initialized_account_alias: self.initialized_account_alias, wallet_alias_force: self.wallet_alias_force, - fee_amount: self.fee_amount, - fee_token: ctx.get(&self.fee_token), + gas_payer: ctx.get_opt_cached(&self.gas_payer), + gas_amount: self.gas_amount, + gas_token: ctx.get(&self.gas_token), gas_limit: self.gas_limit, - signing_key: self.signing_key.map(|x| ctx.get_cached(&x)), + signing_keys: self + .signing_keys + .iter() + .map(|key| ctx.get_cached(key)) + .collect(), + signatures: self + .signatures + .iter() + .map(|path| std::fs::read(path).unwrap()) + .collect(), verification_key: self .verification_key - .map(|x| ctx.get_cached(&x)), - signer: self.signer.map(|x| ctx.get(&x)), + .map(|public_key| ctx.get_cached(&public_key)), tx_reveal_code_path: self.tx_reveal_code_path, password: self.password, expiration: self.expiration, @@ -4404,6 +4648,10 @@ pub mod args { .arg(WALLET_ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) + .arg(GAS_PAYER.def().help( + "The implicit address of the gas payer. It defaults to the \ + address associated to the first key passed to --signing-keys.", + )) .arg(GAS_AMOUNT.def().help( "The amount being paid for the inclusion of this transaction", )) @@ -4420,26 +4668,35 @@ pub mod args { 12:12:12Z\n2012- 12-12T12: 12:12Z", )) .arg( - SIGNING_KEY_OPT + SIGNING_KEYS .def() .help( "Sign the transaction with the key for the given \ public key, public key hash or alias from your \ wallet.", ) - .conflicts_with(SIGNER.name) - .conflicts_with(VERIFICATION_KEY.name), + .conflicts_with_all([ + SIGNATURES.name, + VERIFICATION_KEY.name, + ]), ) .arg( - SIGNER + SIGNATURES .def() .help( - "Sign the transaction with the keypair of the public \ - key of the given address.", + "List of file paths containing a serialized signature \ + to be attached to a transaction. Requires to provide \ + a gas payer.", ) - .conflicts_with(SIGNING_KEY_OPT.name) - .conflicts_with(VERIFICATION_KEY.name), + .conflicts_with_all([ + SIGNING_KEYS.name, + VERIFICATION_KEY.name, + ]) + .requires(GAS_PAYER.name), ) + .arg(OUTPUT_FOLDER_PATH.def().help( + "The output folder path where the artifact will be stored.", + )) .arg( VERIFICATION_KEY .def() @@ -4448,8 +4705,7 @@ pub mod args { public key, public key hash or alias from your \ wallet.", ) - .conflicts_with(SIGNER.name) - .conflicts_with(SIGNING_KEY_OPT.name), + .conflicts_with_all([SIGNING_KEYS.name, SIGNATURES.name]), ) .arg(CHAIN_ID_OPT.def().help("The chain ID.")) } @@ -4462,17 +4718,19 @@ 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 = + let gas_payer = GAS_PAYER.parse(matches); + let gas_amount = InputAmount::Unvalidated(GAS_AMOUNT.parse(matches)); - let fee_token = GAS_TOKEN.parse(matches); + let gas_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).amount.into(); let expiration = EXPIRATION_OPT.parse(matches); - let signing_key = SIGNING_KEY_OPT.parse(matches); + let signing_keys = SIGNING_KEYS.parse(matches); + let signatures = SIGNATURES.parse(matches); let verification_key = VERIFICATION_KEY.parse(matches); - let signer = SIGNER.parse(matches); let tx_reveal_code_path = PathBuf::from(TX_REVEAL_PK); let chain_id = CHAIN_ID_OPT.parse(matches); let password = None; + let output_folder = OUTPUT_FOLDER_PATH.parse(matches); Self { dry_run, dump_tx, @@ -4481,16 +4739,18 @@ pub mod args { ledger_address, initialized_account_alias, wallet_alias_force, - fee_amount, - fee_token, + gas_payer, + gas_amount, + gas_token, gas_limit, expiration, - signing_key, + signing_keys, + signatures, verification_key, - signer, tx_reveal_code_path, password, chain_id, + output_folder, } } } diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 6b68316850..8649049269 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -1,5 +1,6 @@ use color_eyre::eyre::{eyre, Report, Result}; use namada::ledger::eth_bridge::bridge_pool; +use namada::ledger::tx::dump_tx; use namada::ledger::{signing, tx as sdk_tx}; use namada::types::control_flow::ProceedOrElse; @@ -76,7 +77,7 @@ impl CliApi { let args = args.to_sdk(&mut ctx); tx::submit_ibc_transfer(&client, ctx, args).await?; } - Sub::TxUpdateVp(TxUpdateVp(mut args)) => { + Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { let client = client.unwrap_or_else(|| { C::from_tendermint_address( &mut args.tx.ledger_address, @@ -87,7 +88,8 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_vp(&client, &mut ctx, args).await?; + tx::submit_update_account(&client, &mut ctx, args) + .await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -236,37 +238,57 @@ impl CliApi { .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let tx_args = args.tx.clone(); - let (mut tx, addr, pk) = - bridge_pool::build_bridge_pool_tx( - &client, - &mut ctx.wallet, - args, - ) - .await - .unwrap(); - tx::submit_reveal_aux( + + let default_signer = Some(args.sender.clone()); + let signing_data = signing::aux_signing_data( &client, - &mut ctx, - &tx_args, - addr, - pk.clone(), - &mut tx, + &mut ctx.wallet, + &args.tx, + &Some(args.sender.clone()), + default_signer, ) .await?; - signing::sign_tx( - &mut ctx.wallet, - &mut tx, - &tx_args, - &pk, + + let mut tx = bridge_pool::build_bridge_pool_tx( + &client, + args.clone(), + signing_data.gas_payer.clone(), ) .await?; - sdk_tx::process_tx( + + signing::generate_test_vector( &client, &mut ctx.wallet, - &tx_args, - tx, + &tx, ) - .await?; + .await; + + if args.tx.dump_tx { + dump_tx(&args.tx, tx); + } else { + tx::submit_reveal_aux( + &client, + &mut ctx, + tx_args.clone(), + &args.sender, + ) + .await?; + + signing::sign_tx( + &mut ctx.wallet, + &tx_args, + &mut tx, + signing_data, + ); + + sdk_tx::process_tx( + &client, + &mut ctx.wallet, + &tx_args, + tx, + ) + .await?; + } } Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { let client = client.unwrap_or_else(|| { @@ -527,6 +549,45 @@ impl CliApi { let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } + Sub::QueryPgf(QueryPgf(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_pgf(&client, args).await; + } + Sub::QueryAccount(QueryAccount(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_account(&client, args).await; + } + Sub::SignTx(SignTx(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.tx.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + tx::sign_tx(&client, &mut ctx, args).await?; + } } } cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index 8236c16eab..aed45507d9 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -224,6 +224,27 @@ where } } +impl ArgMulti> +where + T: FromStr, + ::Err: Debug, +{ + pub fn def(&self) -> ClapArg { + ClapArg::new(self.name) + .long(self.name) + .num_args(1..) + .value_delimiter(',') + } + + pub fn parse(&self, matches: &ArgMatches) -> Vec> { + matches + .get_many(self.name) + .unwrap_or_default() + .map(|raw: &String| FromContext::new(raw.to_string())) + .collect() + } +} + impl ArgDefaultFromCtx> where T: FromStr, diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index 57f3c5a043..8862c5a564 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,4 +1,3 @@ pub mod rpc; -pub mod signing; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 2f8883842d..cf802f5706 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -2,10 +2,9 @@ use std::cmp::Ordering; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::fs::File; +use std::fs::{self, read_dir}; use std::io::{self, Write}; use std::iter::Iterator; -use std::path::PathBuf; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; @@ -15,22 +14,28 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::{Node, ViewingKey}; use masp_primitives::zip32::ExtendedFullViewingKey; -use namada::core::types::transaction::governance::ProposalType; +use namada::core::ledger::governance::cli::offline::{ + find_offline_proposal, find_offline_votes, read_offline_files, + OfflineSignedProposal, OfflineVote, +}; +use namada::core::ledger::governance::parameters::GovernanceParameters; +use namada::core::ledger::governance::storage::keys as governance_storage; +use namada::core::ledger::governance::storage::proposal::{ + PGFTarget, StorageProposal, +}; +use namada::core::ledger::governance::utils::{ + compute_proposal_result, ProposalVotes, TallyType, TallyVote, VotePower, +}; use namada::ledger::events::Event; -use namada::ledger::governance::parameters::GovParams; -use namada::ledger::governance::storage as gov_storage; use namada::ledger::masp::{ Conversions, MaspAmount, MaspChange, PinnedBalanceError, ShieldedContext, ShieldedUtils, }; -use namada::ledger::native_vp::governance::utils::{self, Votes}; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; -use namada::ledger::pos::{ - self, BondId, BondsAndUnbondsDetail, CommissionPair, PosParams, Slash, -}; +use namada::ledger::pos::{CommissionPair, PosParams, Slash}; use namada::ledger::queries::RPC; use namada::ledger::rpc::{ - enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, + self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, TxResponse, }; use namada::ledger::storage::ConversionState; @@ -38,9 +43,6 @@ use namada::ledger::wallet::{AddressVpType, Wallet}; use namada::proof_of_stake::types::{ValidatorState, WeightedValidator}; use namada::types::address::{masp, Address}; use namada::types::control_flow::ProceedOrElse; -use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalVote, VotePower, VoteType, -}; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; @@ -571,133 +573,48 @@ pub async fn query_proposal( client: &C, args: args::QueryProposal, ) { - async fn print_proposal( - client: &C, - id: u64, - current_epoch: Epoch, - details: bool, - ) -> Option<()> { - let author_key = gov_storage::get_author_key(id); - let start_epoch_key = gov_storage::get_voting_start_epoch_key(id); - let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); - let proposal_type_key = gov_storage::get_proposal_type_key(id); - - let author = - query_storage_value::(client, &author_key).await?; - let start_epoch = - query_storage_value::(client, &start_epoch_key).await?; - let end_epoch = - query_storage_value::(client, &end_epoch_key).await?; - let proposal_type = - query_storage_value::(client, &proposal_type_key) - .await?; - - if details { - let content_key = gov_storage::get_content_key(id); - let grace_epoch_key = gov_storage::get_grace_epoch_key(id); - let content = query_storage_value::>( - client, - &content_key, - ) - .await?; - let grace_epoch = - query_storage_value::(client, &grace_epoch_key) - .await?; - - println!("Proposal: {}", id); - println!("{:4}Type: {}", "", proposal_type); - println!("{:4}Author: {}", "", author); - println!("{:4}Content:", ""); - for (key, value) in &content { - println!("{:8}{}: {}", "", key, value); - } - println!("{:4}Start Epoch: {}", "", start_epoch); - 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) + let current_epoch = query_and_print_epoch(client).await; + + if let Some(id) = args.proposal_id { + let proposal = query_proposal_by_id(client, id).await; + if let Some(proposal) = proposal { + println!("{}", proposal.to_string_with_status(current_epoch)); + } else { + eprintln!("No proposal found with id: {}", id); + } + } else { + println!("asd2"); + let last_proposal_id_key = governance_storage::get_counter_key(); + let last_proposal_id = + query_storage_value::(client, &last_proposal_id_key) .await - .try_into() .unwrap(); - if start_epoch > current_epoch { - println!("{:4}Status: pending", ""); - } else if start_epoch <= current_epoch && current_epoch <= end_epoch - { - match utils::compute_tally(votes, total_stake, &proposal_type) { - Ok(partial_proposal_result) => { - println!( - "{:4}Yay votes: {}", - "", partial_proposal_result.total_yay_power - ); - println!( - "{:4}Nay votes: {}", - "", partial_proposal_result.total_nay_power - ); - println!("{:4}Status: on-going", ""); - } - Err(msg) => { - eprintln!("Error in tally computation: {}", msg) - } - } - } else { - match utils::compute_tally(votes, total_stake, &proposal_type) { - Ok(proposal_result) => { - println!("{:4}Status: done", ""); - println!("{:4}Result: {}", "", proposal_result); - } - Err(msg) => { - eprintln!("Error in tally computation: {}", msg) - } - } - } + + let from_id = if last_proposal_id > 10 { + last_proposal_id - 10 } else { - println!("Proposal: {}", id); - println!("{:4}Type: {}", "", proposal_type); - println!("{:4}Author: {}", "", author); - println!("{:4}Start Epoch: {}", "", start_epoch); - println!("{:4}End Epoch: {}", "", end_epoch); - if start_epoch > current_epoch { - println!("{:4}Status: pending", ""); - } else if start_epoch <= current_epoch && current_epoch <= end_epoch - { - println!("{:4}Status: on-going", ""); - } else { - println!("{:4}Status: done", ""); - } - } + 0 + }; - Some(()) - } + println!("id: {}", last_proposal_id); - let current_epoch = query_and_print_epoch(client).await; - match args.proposal_id { - Some(id) => { - if print_proposal::(client, id, current_epoch, true) + for id in from_id..last_proposal_id { + let proposal = query_proposal_by_id(client, id) .await - .is_none() - { - eprintln!("No valid proposal was found with id {}", id) - } - } - None => { - let last_proposal_id_key = gov_storage::get_counter_key(); - let last_proposal_id = - query_storage_value::(client, &last_proposal_id_key) - .await - .unwrap(); - - for id in 0..last_proposal_id { - if print_proposal::(client, id, current_epoch, false) - .await - .is_none() - { - eprintln!("No valid proposal was found with id {}", id) - }; - } + .expect("Proposal should be written to storage."); + println!("{}", proposal); } } } +/// Query proposal by Id +pub async fn query_proposal_by_id( + client: &C, + proposal_id: u64, +) -> Option { + namada::ledger::rpc::query_proposal_by_id(client, proposal_id).await +} + /// Query token shielded balance(s) pub async fn query_shielded_balance< C: namada::ledger::queries::Client + Sync, @@ -976,169 +893,145 @@ pub async fn query_proposal_result< client: &C, args: args::QueryProposalResult, ) { - let current_epoch = query_epoch(client).await; + if args.proposal_id.is_some() { + let proposal_id = + args.proposal_id.expect("Proposal id should be defined."); + let proposal = if let Some(proposal) = + query_proposal_by_id(client, proposal_id).await + { + proposal + } else { + eprintln!("Proposal {} not found.", proposal_id); + return; + }; - match args.proposal_id { - Some(id) => { - let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); - let end_epoch = - query_storage_value::(client, &end_epoch_key).await; - - match end_epoch { - Some(end_epoch) => { - if current_epoch > end_epoch { - let votes = - get_proposal_votes(client, end_epoch, id).await; - let proposal_type_key = - gov_storage::get_proposal_type_key(id); - let proposal_type = query_storage_value::< - C, - ProposalType, - >( - client, &proposal_type_key - ) - .await - .expect("Could not read proposal type from storage"); - let total_stake = - get_total_staked_tokens(client, end_epoch) - .await - .try_into() - .unwrap(); - println!("Proposal: {}", id); - match utils::compute_tally( - votes, - total_stake, - &proposal_type, - ) { - Ok(proposal_result) => { - println!("{:4}Result: {}", "", proposal_result) - } - Err(msg) => { - eprintln!("Error in tally computation: {}", msg) - } - } - } else { - eprintln!("Proposal is still in progress."); - cli::safe_exit(1) - } - } - None => { - eprintln!("Error while retriving proposal."); - cli::safe_exit(1) - } - } - } - None => { - if args.offline { - match args.proposal_folder { - Some(path) => { - let mut dir = tokio::fs::read_dir(&path) - .await - .expect("Should be able to read the directory."); - let mut files = HashSet::new(); - let mut is_proposal_present = false; - - while let Some(entry) = - dir.next_entry().await.transpose() - { - match entry { - Ok(entry) => match entry.file_type().await { - Ok(entry_stat) => { - if entry_stat.is_file() { - if entry.file_name().eq(&"proposal") - { - is_proposal_present = true - } else if entry - .file_name() - .to_string_lossy() - .starts_with("proposal-vote-") - { - // Folder may contain other - // files than just the proposal - // and the votes - files.insert(entry.path()); - } - } - } - Err(e) => { - eprintln!( - "Can't read entry type: {}.", - e - ); - cli::safe_exit(1) - } - }, - Err(e) => { - eprintln!("Can't read entry: {}.", e); - cli::safe_exit(1) - } - } - } + let tally_type = proposal.get_tally_type(); + let total_voting_power = + get_total_staked_tokens(client, proposal.voting_end_epoch).await; - if !is_proposal_present { - eprintln!( - "The folder must contain the offline proposal \ - in a file named \"proposal\"" - ); - cli::safe_exit(1) - } + let votes = compute_proposal_votes( + client, + proposal_id, + proposal.voting_end_epoch, + ) + .await; - let file = File::open(path.join("proposal")) - .expect("Proposal file must exist."); - let proposal: OfflineProposal = - serde_json::from_reader(file).expect( - "JSON was not well-formatted for proposal.", - ); - - let public_key = - get_public_key(client, &proposal.address) - .await - .expect("Public key should exist."); - - if !proposal.check_signature(&public_key) { - eprintln!("Bad proposal signature."); - cli::safe_exit(1) - } + let proposal_result = + compute_proposal_result(votes, total_voting_power, tally_type); - let votes = get_proposal_offline_votes( - client, - proposal.clone(), - files, - ) - .await; - let total_stake = get_total_staked_tokens( - client, - proposal.tally_epoch, - ) - .await - .try_into() - .unwrap(); - match utils::compute_tally( - votes, - total_stake, - &ProposalType::Default(None), - ) { - Ok(proposal_result) => { - println!("{:4}Result: {}", "", proposal_result) - } - Err(msg) => { - eprintln!("Error in tally computation: {}", msg) - } - } - } - None => { - eprintln!( - "Offline flag must be followed by data-path." - ); - cli::safe_exit(1) - } - }; + println!("Proposal Id: {} ", proposal_id); + println!("{:4}{}", "", proposal_result); + } else { + let proposal_folder = args.proposal_folder.expect( + "The argument --proposal-folder is required with --offline.", + ); + let data_directory = read_dir(&proposal_folder).unwrap_or_else(|_| { + panic!( + "Should be able to read {} directory.", + proposal_folder.to_string_lossy() + ) + }); + let files = read_offline_files(data_directory); + let proposal_path = find_offline_proposal(&files); + + let proposal = if let Some(path) = proposal_path { + let proposal_file = + fs::File::open(path).expect("file should open read only"); + let proposal: OfflineSignedProposal = + serde_json::from_reader(proposal_file) + .expect("file should be proper JSON"); + + let author_account = + rpc::get_account_info(client, &proposal.proposal.author) + .await + .expect("Account should exist."); + + let proposal = proposal.validate( + &author_account.public_keys_map, + author_account.threshold, + ); + + if proposal.is_ok() { + proposal.unwrap() } else { - eprintln!( - "Either --proposal-id or --data-path should be provided \ - as arguments." - ); - cli::safe_exit(1) + eprintln!("The offline proposal is not valid."); + return; + } + } else { + eprintln!("Couldn't find a file name offline_proposal_*.json."); + return; + }; + + let votes = find_offline_votes(&files) + .iter() + .map(|path| { + let vote_file = fs::File::open(path).expect(""); + let vote: OfflineVote = + serde_json::from_reader(vote_file).expect(""); + vote + }) + .collect::>(); + + let proposal_votes = + compute_offline_proposal_votes(client, &proposal, votes.clone()) + .await; + let total_voting_power = + get_total_staked_tokens(client, proposal.proposal.tally_epoch) + .await; + + let proposal_result = compute_proposal_result( + proposal_votes, + total_voting_power, + TallyType::TwoThird, + ); + + println!("Proposal offline: {}", proposal.proposal.hash()); + println!("Parsed {} votes.", votes.len()); + println!("{:4}{}", "", proposal_result); + } +} + +pub async fn query_account( + client: &C, + args: args::QueryAccount, +) { + let account = rpc::get_account_info(client, &args.owner).await; + if let Some(account) = account { + println!("Address: {}", account.address); + println!("Threshold: {}", account.threshold); + println!("Public keys:"); + for (public_key, _) in account.public_keys_map.pk_to_idx { + println!("- {}", public_key); + } + } else { + println!("No account exists for {}", args.owner); + } +} + +pub async fn query_pgf( + client: &C, + _args: args::QueryPgf, +) { + let stewards = query_pgf_stewards(client).await; + let fundings = query_pgf_fundings(client).await; + + match stewards.len() { + 0 => println!("Pgf stewards: no stewards are currectly set."), + _ => { + println!("Pgf stewards:"); + for steward in stewards { + println!("{:4}- {}", "", steward); + } + } + } + + match fundings.len() { + 0 => println!("Pgf fundings: no fundings are currently set."), + _ => { + println!("Pgf fundings:"); + for payment in fundings { + println!("{:4}- {}", "", payment.target); + println!("{:6}{}", "", payment.amount.to_string_native()); } } } @@ -1150,8 +1043,8 @@ pub async fn query_protocol_parameters< client: &C, _args: args::QueryProtocolParameters, ) { - let gov_parameters = get_governance_parameters(client).await; - println!("Governance Parameters\n {:4}", gov_parameters); + let governance_parameters = query_governance_parameters(client).await; + println!("Governance Parameters\n {:4}", governance_parameters); println!("Protocol parameters"); let key = param_storage::get_epoch_duration_storage_key(); @@ -1186,10 +1079,7 @@ pub async fn query_protocol_parameters< println!("{:4}Transactions whitelist: {:?}", "", tx_whitelist); println!("PoS parameters"); - let key = pos::params_key(); - let pos_params = query_storage_value::(client, &key) - .await - .expect("Parameter should be defined."); + let pos_params = query_pos_parameters(client).await; println!( "{:4}Block proposer reward: {}", "", pos_params.block_proposer_reward @@ -1241,6 +1131,30 @@ pub async fn query_unbond_with_slashing< ) } +pub async fn query_pos_parameters( + client: &C, +) -> PosParams { + unwrap_client_response::( + RPC.vp().pos().pos_params(client).await, + ) +} + +pub async fn query_pgf_stewards( + client: &C, +) -> BTreeSet
{ + unwrap_client_response::>( + RPC.vp().pgf().stewards(client).await, + ) +} + +pub async fn query_pgf_fundings( + client: &C, +) -> BTreeSet { + unwrap_client_response::>( + RPC.vp().pgf().funding(client).await, + ) +} + pub async fn query_and_print_unbonds< C: namada::ledger::queries::Client + Sync, >( @@ -1779,8 +1693,9 @@ where pub async fn get_public_key( client: &C, address: &Address, + index: u8, ) -> Option { - namada::ledger::rpc::get_public_key(client, address).await + namada::ledger::rpc::get_public_key_at(client, address, index).await } /// Check if the given address is a known validator. @@ -2030,199 +1945,6 @@ pub async fn epoch_sleep( } } -pub async fn get_proposal_votes( - client: &C, - epoch: Epoch, - proposal_id: u64, -) -> Votes { - namada::ledger::rpc::get_proposal_votes(client, epoch, proposal_id).await -} - -pub async fn get_proposal_offline_votes< - C: namada::ledger::queries::Client + Sync, ->( - client: &C, - proposal: OfflineProposal, - files: HashSet, -) -> Votes { - // let validators = get_all_validators(client, proposal.tally_epoch).await; - - let proposal_hash = proposal.compute_hash(); - - let mut yay_validators: HashMap = - HashMap::new(); - let mut delegators: HashMap< - Address, - HashMap, - > = HashMap::new(); - - for path in files { - let file = File::open(&path).expect("Proposal file must exist."); - let proposal_vote: OfflineVote = serde_json::from_reader(file) - .expect("JSON was not well-formatted for offline vote."); - - let key = pk_key(&proposal_vote.address); - let public_key = query_storage_value(client, &key) - .await - .expect("Public key should exist."); - - if !proposal_vote.proposal_hash.eq(&proposal_hash) - || !proposal_vote.check_signature(&public_key) - { - continue; - } - - if proposal_vote.vote.is_yay() - // && validators.contains(&proposal_vote.address) - && unwrap_client_response::( - RPC.vp().pos().is_validator(client, &proposal_vote.address).await, - ) - { - let amount: VotePower = get_validator_stake( - client, - proposal.tally_epoch, - &proposal_vote.address, - ) - .await - .unwrap_or_default() - .try_into() - .expect("Amount out of bounds"); - yay_validators.insert( - proposal_vote.address, - (amount, ProposalVote::Yay(VoteType::Default)), - ); - } else if is_delegator_at( - client, - &proposal_vote.address, - proposal.tally_epoch, - ) - .await - { - // TODO: decide whether to do this with `bond_with_slashing` RPC - // endpoint or with `bonds_and_unbonds` - let bonds_and_unbonds: pos::types::BondsAndUnbondsDetails = - unwrap_client_response::( - RPC.vp() - .pos() - .bonds_and_unbonds( - client, - &Some(proposal_vote.address.clone()), - &None, - ) - .await, - ); - for ( - BondId { - source: _, - validator, - }, - BondsAndUnbondsDetail { - bonds, - unbonds: _, - slashes: _, - }, - ) in bonds_and_unbonds - { - let mut delegated_amount = token::Amount::zero(); - for delta in bonds { - if delta.start <= proposal.tally_epoch { - delegated_amount += delta.amount - - delta.slashed_amount.unwrap_or_default(); - } - } - - let entry = delegators - .entry(proposal_vote.address.clone()) - .or_default(); - entry.insert( - validator, - ( - VotePower::try_from(delegated_amount).unwrap(), - proposal_vote.vote.clone(), - ), - ); - } - - // let key = pos::bonds_for_source_prefix(&proposal_vote.address); - // let bonds_iter = - // query_storage_prefix::(client, &key).await; - // if let Some(bonds) = bonds_iter { - // for (key, epoched_bonds) in bonds { - // // Look-up slashes for the validator in this key and - // // apply them if any - // let validator = - // pos::get_validator_address_from_bond(&key) - // .expect( - // "Delegation key should contain validator - // address.", ); - // let slashes_key = pos::validator_slashes_key(&validator); - // let slashes = query_storage_value::( - // client, - // &slashes_key, - // ) - // .await - // .unwrap_or_default(); - // let mut delegated_amount: token::Amount = 0.into(); - // let bond = epoched_bonds - // .get(proposal.tally_epoch) - // .expect("Delegation bond should be defined."); - // let mut to_deduct = bond.neg_deltas; - // for (start_epoch, &(mut delta)) in - // bond.pos_deltas.iter().sorted() - // { - // // deduct bond's neg_deltas - // if to_deduct > delta { - // to_deduct -= delta; - // // If the whole bond was deducted, continue to - // // the next one - // continue; - // } else { - // delta -= to_deduct; - // to_deduct = token::Amount::zero(); - // } - - // delta = apply_slashes( - // &slashes, - // delta, - // *start_epoch, - // None, - // None, - // ); - // delegated_amount += delta; - // } - - // let validator_address = - // pos::get_validator_address_from_bond(&key).expect( - // "Delegation key should contain validator - // address.", ); - // if proposal_vote.vote.is_yay() { - // let entry = yay_delegators - // .entry(proposal_vote.address.clone()) - // .or_default(); - // entry.insert( - // validator_address, - // VotePower::from(delegated_amount), - // ); - // } else { - // let entry = nay_delegators - // .entry(proposal_vote.address.clone()) - // .or_default(); - // entry.insert( - // validator_address, - // VotePower::from(delegated_amount), - // ); - // } - // } - // } - } - } - - Votes { - yay_validators, - delegators, - } -} - pub async fn get_bond_amount_at( client: &C, delegator: &Address, @@ -2281,12 +2003,23 @@ pub async fn get_delegators_delegation< namada::ledger::rpc::get_delegators_delegation(client, address).await } -pub async fn get_governance_parameters< +pub async fn get_delegators_delegation_at< C: namada::ledger::queries::Client + Sync, >( client: &C, -) -> GovParams { - namada::ledger::rpc::get_governance_parameters(client).await + address: &Address, + epoch: Epoch, +) -> HashMap { + namada::ledger::rpc::get_delegators_delegation_at(client, address, epoch) + .await +} + +pub async fn query_governance_parameters< + C: namada::ledger::queries::Client + Sync, +>( + client: &C, +) -> GovernanceParameters { + namada::ledger::rpc::query_governance_parameters(client).await } /// A helper to unwrap client's response. Will shut down process on error. @@ -2298,3 +2031,121 @@ fn unwrap_client_response( cli::safe_exit(1) }) } + +pub async fn compute_offline_proposal_votes< + C: namada::ledger::queries::Client + Sync, +>( + client: &C, + proposal: &OfflineSignedProposal, + votes: Vec, +) -> ProposalVotes { + let mut validators_vote: HashMap = HashMap::default(); + let mut validator_voting_power: HashMap = + HashMap::default(); + let mut delegators_vote: HashMap = HashMap::default(); + let mut delegator_voting_power: HashMap< + Address, + HashMap, + > = HashMap::default(); + for vote in votes { + let is_validator = is_validator(client, &vote.address).await; + let is_delegator = is_delegator(client, &vote.address).await; + if is_validator { + let validator_stake = get_validator_stake( + client, + proposal.proposal.tally_epoch, + &vote.address, + ) + .await + .unwrap_or_default(); + validators_vote.insert(vote.address.clone(), vote.clone().into()); + validator_voting_power + .insert(vote.address.clone(), validator_stake); + } else if is_delegator { + let validators = get_delegators_delegation_at( + client, + &vote.address.clone(), + proposal.proposal.tally_epoch, + ) + .await; + + for validator in vote.delegations.clone() { + let delegator_stake = + validators.get(&validator).cloned().unwrap_or_default(); + + delegators_vote + .insert(vote.address.clone(), vote.clone().into()); + delegator_voting_power + .entry(vote.address.clone()) + .or_default() + .insert(validator, delegator_stake); + } + } else { + println!( + "Skipping vote, not a validator/delegator at epoch {}.", + proposal.proposal.tally_epoch + ); + } + } + + ProposalVotes { + validators_vote, + validator_voting_power, + delegators_vote, + delegator_voting_power, + } +} + +pub async fn compute_proposal_votes< + C: namada::ledger::queries::Client + Sync, +>( + client: &C, + proposal_id: u64, + epoch: Epoch, +) -> ProposalVotes { + let votes = + namada::ledger::rpc::query_proposal_votes(client, proposal_id).await; + + let mut validators_vote: HashMap = HashMap::default(); + let mut validator_voting_power: HashMap = + HashMap::default(); + let mut delegators_vote: HashMap = HashMap::default(); + let mut delegator_voting_power: HashMap< + Address, + HashMap, + > = HashMap::default(); + + for vote in votes { + if vote.is_validator() { + let validator_stake = + get_validator_stake(client, epoch, &vote.validator.clone()) + .await + .unwrap_or_default(); + + validators_vote.insert(vote.validator.clone(), vote.data.into()); + validator_voting_power.insert(vote.validator, validator_stake); + } else { + let delegator_stake = get_bond_amount_at( + client, + &vote.delegator, + &vote.validator, + epoch, + ) + .await + .unwrap_or_default(); + + delegators_vote.insert(vote.delegator.clone(), vote.data.into()); + delegator_voting_power + .entry(vote.delegator.clone()) + .or_default() + .insert(vote.validator, delegator_stake); + } + } + + ProposalVotes { + validators_vote, + validator_voting_power, + delegators_vote, + delegator_voting_power, + } +} diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 3c2a5d67fe..93ebe5f114 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -31,7 +31,7 @@ where /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer( +pub async fn tx_signers( client: &C, wallet: &mut Wallet, args: &args::Tx, @@ -42,7 +42,7 @@ where C::Error: std::fmt::Display, U: WalletUtils, { - namada::ledger::signing::tx_signer::(client, wallet, args, default) + namada::ledger::signing::tx_signers::(client, wallet, args, default) .await } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 974e940db4..125fe30588 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,4 +1,3 @@ -use std::collections::HashSet; use std::env; use std::fmt::Debug; use std::fs::{File, OpenOptions}; @@ -7,32 +6,27 @@ use std::path::PathBuf; use async_trait::async_trait; use borsh::{BorshDeserialize, BorshSerialize}; -use data_encoding::HEXLOWER_PERMISSIVE; use masp_proofs::prover::LocalTxProver; -use namada::ledger::governance::storage as gov_storage; +use namada::core::ledger::governance::cli::offline::{ + OfflineSignedProposal, OfflineVote, +}; +use namada::core::ledger::governance::cli::onchain::{ + DefaultProposal, PgfFundingProposal, PgfStewardProposal, ProposalVote, +}; use namada::ledger::queries::Client; use namada::ledger::rpc::{TxBroadcastData, TxResponse}; -use namada::ledger::signing::TxSigningKey; use namada::ledger::wallet::{Wallet, WalletUtils}; use namada::ledger::{masp, pos, signing, tx}; use namada::proof_of_stake::parameters::PosParams; -use namada::proto::{Code, Data, Section, Tx}; -use namada::types::address::Address; +use namada::proto::Tx; +use namada::types::address::{Address, ImplicitAddress}; use namada::types::dec::Dec; -use namada::types::governance::{ - OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, -}; use namada::types::key::{self, *}; -use namada::types::storage::{Epoch, Key}; -use namada::types::token; -use namada::types::transaction::governance::{ProposalType, VoteProposalData}; -use namada::types::transaction::{InitValidator, TxType}; +use namada::types::transaction::pos::InitValidator; use super::rpc; -use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::query_wasm_code_hash; -use crate::client::signing::find_pk; use crate::client::tx::tx::ProcessTxResponse; use crate::config::TendermintMode; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -43,31 +37,47 @@ use crate::wallet::{gen_validator_keys, read_and_confirm_encryption_password}; pub async fn submit_reveal_aux( client: &C, ctx: &mut Context, - args: &args::Tx, - addr: Option
, - pk: common::PublicKey, - tx: &mut Tx, + args: args::Tx, + address: &Address, ) -> Result<(), tx::Error> { - if let Some(Address::Implicit(_)) = addr { - let reveal_pk = tx::build_reveal_pk( - client, - &mut ctx.wallet, - args::RevealPk { - tx: args.clone(), - public_key: pk.clone(), - }, - ) - .await?; - if let Some((mut rtx, _, pk)) = reveal_pk { - // Sign the reveal public key transaction with the fee payer - signing::sign_tx(&mut ctx.wallet, &mut rtx, args, &pk).await?; - // Submit the reveal public key transaction first - tx::process_tx(client, &mut ctx.wallet, args, rtx).await?; - // Update the stateful PoW challenge of the outer transaction - #[cfg(not(feature = "mainnet"))] - signing::update_pow_challenge(client, args, tx, &pk, false).await; + if args.dump_tx { + return Ok(()); + } + + if let Address::Implicit(ImplicitAddress(pkh)) = address { + let key = ctx + .wallet + .find_key_by_pkh(pkh, args.clone().password) + .map_err(|e| tx::Error::Other(e.to_string()))?; + let public_key = key.ref_to(); + + if tx::is_reveal_pk_needed::(client, address, args.force).await? { + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args, + &None, + None, + ) + .await?; + + let mut tx = tx::build_reveal_pk::( + client, + &args, + address, + &public_key, + &signing_data.gas_payer, + ) + .await?; + + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + signing::sign_tx(&mut ctx.wallet, &args, &mut tx, signing_data); + + tx::process_tx(client, &mut ctx.wallet, &args, tx).await?; } } + Ok(()) } @@ -80,28 +90,65 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = - tx::build_custom(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let default_signer = Some(args.owner.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(args.owner.clone()), + default_signer, + ) + .await?; + + submit_reveal_aux(client, ctx, args.tx.clone(), &args.owner).await?; + + let mut tx = + tx::build_custom(client, args.clone(), &signing_data.gas_payer).await?; + + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } -pub async fn submit_update_vp( +pub async fn submit_update_account( client: &C, ctx: &mut Context, - args: args::TxUpdateVp, + args: args::TxUpdateAccount, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = - tx::build_update_vp(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let default_signer = Some(args.addr.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(args.addr.clone()), + default_signer, + ) + .await?; + + let mut tx = + tx::build_update_account(client, args.clone(), &signing_data.gas_payer) + .await?; + + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } @@ -114,11 +161,28 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = - tx::build_init_account(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &None, + None, + ) + .await?; + + let mut tx = + tx::build_init_account(client, args.clone(), &signing_data.gas_payer) + .await?; + + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } @@ -127,9 +191,9 @@ pub async fn submit_init_validator( mut ctx: Context, args::TxInitValidator { tx: tx_args, - source, scheme, - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -160,25 +224,19 @@ where let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = format!("{}-consensus-key", alias); + + let threshold = match threshold { + Some(threshold) => threshold, + None => { + if account_keys.len() == 1 { + 1u8 + } else { + safe_exit(1) + } + } + }; let eth_hot_key_alias = format!("{}-eth-hot-key", alias); let eth_cold_key_alias = format!("{}-eth-cold-key", alias); - let account_key = account_key.unwrap_or_else(|| { - println!("Generating validator account key..."); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet - .gen_key( - scheme, - Some(validator_key_alias.clone()), - tx_args.wallet_alias_force, - password, - None, - ) - .expect("Key generation should not fail.") - .expect("No existing alias expected.") - .1 - .ref_to() - }); let consensus_key = consensus_key .map(|key| match key { @@ -311,12 +369,14 @@ where .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - let extra = tx.add_section(Section::ExtraData(Code::from_hash( - validator_vp_code_hash, - ))); + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + let extra_section_hash = + tx.add_extra_section_from_hash(validator_vp_code_hash); + let data = InitValidator { - account_key, + account_keys, + threshold, consensus_key: consensus_key.ref_to(), eth_cold_key: key::secp256k1::PublicKey::try_from_pk(ð_cold_pk) .unwrap(), @@ -326,91 +386,109 @@ where dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code_hash: extra.get_hash(), + validator_vp_code_hash: extra_section_hash, }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - tx.header.chain_id = tx_args.chain_id.clone().unwrap(); - tx.header.expiration = tx_args.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); - let (mut tx, addr, pk) = tx::prepare_tx( + tx.add_code_from_hash(tx_code_hash).add_data(data); + + let signing_data = signing::aux_signing_data( client, &mut ctx.wallet, &tx_args, - tx, - TxSigningKey::WalletAddress(source), + &None, + None, + ) + .await?; + + tx::prepare_tx( + client, + &tx_args, + &mut tx, + signing_data.gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await?; - submit_reveal_aux(client, &mut ctx, &tx_args, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &tx_args, &pk).await?; - let result = tx::process_tx(client, &mut ctx.wallet, &tx_args, tx) - .await? - .initialized_accounts(); - - if !tx_args.dry_run { - let (validator_address_alias, validator_address) = match &result[..] { - // There should be 1 account for the validator itself - [validator_address] => { - if let Some(alias) = ctx.wallet.find_alias(validator_address) { - (alias.clone(), validator_address.clone()) - } else { + .await; + + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if tx_args.dump_tx { + tx::dump_tx(&tx_args, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &tx_args, &mut tx, signing_data); + + let result = tx::process_tx(client, &mut ctx.wallet, &tx_args, tx) + .await? + .initialized_accounts(); + + if !tx_args.dry_run { + let (validator_address_alias, validator_address) = match &result[..] + { + // There should be 1 account for the validator itself + [validator_address] => { + if let Some(alias) = + ctx.wallet.find_alias(validator_address) + { + (alias.clone(), validator_address.clone()) + } else { + eprintln!("Expected one account to be created"); + safe_exit(1) + } + } + _ => { eprintln!("Expected one account to be created"); safe_exit(1) } - } - _ => { - eprintln!("Expected one account to be created"); - safe_exit(1) - } - }; - // add validator address and keys to the wallet - ctx.wallet - .add_validator_data(validator_address, validator_keys); - crate::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); - - let tendermint_home = ctx.config.ledger.cometbft_dir(); - tendermint_node::write_validator_key(&tendermint_home, &consensus_key); - tendermint_node::write_validator_state(tendermint_home); - - // Write Namada config stuff or figure out how to do the above - // tendermint_node things two epochs in the future!!! - ctx.config.ledger.shell.tendermint_mode = TendermintMode::Validator; - ctx.config - .write( - &ctx.config.ledger.shell.base_dir, - &ctx.config.ledger.chain_id, - true, - ) - .unwrap(); + }; + // add validator address and keys to the wallet + ctx.wallet + .add_validator_data(validator_address, validator_keys); + crate::wallet::save(&ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); + + let tendermint_home = ctx.config.ledger.cometbft_dir(); + tendermint_node::write_validator_key( + &tendermint_home, + &consensus_key, + ); + tendermint_node::write_validator_state(tendermint_home); + + // Write Namada config stuff or figure out how to do the above + // tendermint_node things two epochs in the future!!! + ctx.config.ledger.shell.tendermint_mode = TendermintMode::Validator; + ctx.config + .write( + &ctx.config.ledger.shell.base_dir, + &ctx.config.ledger.chain_id, + true, + ) + .unwrap(); - let key = pos::params_key(); - let pos_params = rpc::query_storage_value::(client, &key) - .await - .expect("Pos parameter should be defined."); + let key = pos::params_key(); + let pos_params = + rpc::query_storage_value::(client, &key) + .await + .expect("Pos parameter should be defined."); - println!(); - println!( - "The validator's addresses and keys were stored in the wallet:" - ); - println!(" Validator address \"{}\"", validator_address_alias); - println!(" Validator account key \"{}\"", validator_key_alias); - println!(" Consensus key \"{}\"", consensus_key_alias); - println!( - "The ledger node has been setup to use this validator's address \ - and consensus key." - ); - println!( - "Your validator will be active in {} epochs. Be sure to restart \ - your node for the changes to take effect!", - pos_params.pipeline_len - ); - } else { - println!("Transaction dry run. No addresses have been saved."); + println!(); + println!( + "The validator's addresses and keys were stored in the wallet:" + ); + println!(" Validator address \"{}\"", validator_address_alias); + println!(" Validator account key \"{}\"", validator_key_alias); + println!(" Consensus key \"{}\"", consensus_key_alias); + println!( + "The ledger node has been setup to use this validator's \ + address and consensus key." + ); + println!( + "Your validator will be active in {} epochs. Be sure to \ + restart your node for the changes to take effect!", + pos_params.pipeline_len + ); + } else { + println!("Transaction dry run. No addresses have been saved."); + } } Ok(()) } @@ -529,46 +607,66 @@ pub async fn submit_transfer( args: args::TxTransfer, ) -> Result<(), tx::Error> { for _ in 0..2 { - let arg = args.clone(); - let (mut tx, addr, pk, tx_epoch, _isf) = - tx::build_transfer(client, &mut ctx.wallet, &mut ctx.shielded, arg) - .await?; + let default_signer = Some(args.source.effective_address()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(args.source.effective_address()), + default_signer, + ) + .await?; + submit_reveal_aux( client, &mut ctx, - &args.tx, - addr, - pk.clone(), - &mut tx, + args.tx.clone(), + &args.source.effective_address(), ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - let result = - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; - // Query the epoch in which the transaction was probably submitted - let submission_epoch = rpc::query_and_print_epoch(client).await; - - match result { - ProcessTxResponse::Applied(resp) if - // If a transaction is shielded - tx_epoch.is_some() && - // And it is rejected by a VP - resp.code == 1.to_string() && - // And the its submission epoch doesn't match construction epoch - tx_epoch.unwrap() != submission_epoch => - { - // Then we probably straddled an epoch boundary. Let's retry... - eprintln!( - "MASP transaction rejected and this may be due to the \ - epoch changing. Attempting to resubmit transaction.", - ); - continue; - }, - // Otherwise either the transaction was successful or it will not - // benefit from resubmission - _ => break, + + let arg = args.clone(); + let (mut tx, tx_epoch) = tx::build_transfer( + client, + &mut ctx.shielded, + arg, + &signing_data.gas_payer, + ) + .await?; + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + let result = + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + + let submission_epoch = rpc::query_and_print_epoch(client).await; + + match result { + ProcessTxResponse::Applied(resp) if + // If a transaction is shielded + tx_epoch.is_some() && + // And it is rejected by a VP + resp.code == 1.to_string() && + // And its submission epoch doesn't match construction epoch + tx_epoch.unwrap() != submission_epoch => + { + // Then we probably straddled an epoch boundary. Let's retry... + eprintln!( + "MASP transaction rejected and this may be due to the \ + epoch changing. Attempting to resubmit transaction.", + ); + continue; + }, + // Otherwise either the transaction was successful or it will not + // benefit from resubmission + _ => break, + } } } + Ok(()) } @@ -581,12 +679,30 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = - tx::build_ibc_transfer(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let default_signer = Some(args.source.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(args.source.clone()), + default_signer, + ) + .await?; + + submit_reveal_aux(client, &mut ctx, args.tx.clone(), &args.source).await?; + + let mut tx = + tx::build_ibc_transfer(client, args.clone(), &signing_data.gas_payer) + .await?; + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } @@ -599,180 +715,175 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let file = File::open(&args.proposal_data).expect("File must exist."); - let proposal: Proposal = - serde_json::from_reader(file).expect("JSON was not well-formatted"); - - let signer = WalletAddress::new(proposal.clone().author.to_string()); let current_epoch = rpc::query_and_print_epoch(client).await; + let governance_parameters = rpc::query_governance_parameters(client).await; - let governance_parameters = rpc::get_governance_parameters(client).await; - if proposal.voting_start_epoch <= current_epoch - || proposal.voting_start_epoch.0 - % governance_parameters.min_proposal_period - != 0 - { - println!("{}", proposal.voting_start_epoch <= current_epoch); - println!( - "{}", - proposal.voting_start_epoch.0 - % governance_parameters.min_proposal_period - == 0 - ); - eprintln!( - "Invalid proposal start epoch: {} must be greater than current \ - epoch {} and a multiple of {}", - proposal.voting_start_epoch, - current_epoch, - governance_parameters.min_proposal_period - ); - if !args.tx.force { - safe_exit(1) - } - } else if proposal.voting_end_epoch <= proposal.voting_start_epoch - || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - < governance_parameters.min_proposal_period - || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - > governance_parameters.max_proposal_period - || proposal.voting_end_epoch.0 % 3 != 0 - { - eprintln!( - "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and at max {} and end epoch \ - must be a multiple of {}", - governance_parameters.min_proposal_period, - governance_parameters.max_proposal_period, - governance_parameters.min_proposal_period - ); - if !args.tx.force { - safe_exit(1) - } - } else if proposal.grace_epoch <= proposal.voting_end_epoch - || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 - < governance_parameters.min_proposal_grace_epochs - { - eprintln!( - "Invalid proposal grace epoch: difference between proposal grace \ - and end epoch must be at least {}", - governance_parameters.min_proposal_grace_epochs + let (mut tx_builder, signing_data) = if args.is_offline { + let proposal = namada::core::ledger::governance::cli::offline::OfflineProposal::try_from(args.proposal_data.as_ref()).map_err(|e| tx::Error::FailedGovernaneProposalDeserialize(e.to_string()))?.validate(current_epoch) + .map_err(|e| tx::Error::InvalidProposal(e.to_string()))?; + + let default_signer = Some(proposal.author.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(proposal.author.clone()), + default_signer, + ) + .await?; + + let signed_offline_proposal = proposal.sign( + args.tx.signing_keys, + &signing_data.account_public_keys_map.unwrap(), ); - if !args.tx.force { - safe_exit(1) - } - } + let output_file_path = signed_offline_proposal + .serialize(args.tx.output_folder) + .map_err(|e| { + tx::Error::FailedGovernaneProposalDeserialize(e.to_string()) + })?; + + println!("Proposal serialized to: {}", output_file_path); + return Ok(()); + } else if args.is_pgf_funding { + let proposal = + PgfFundingProposal::try_from(args.proposal_data.as_ref()) + .map_err(|e| { + tx::Error::FailedGovernaneProposalDeserialize(e.to_string()) + })? + .validate(&governance_parameters, current_epoch) + .map_err(|e| tx::Error::InvalidProposal(e.to_string()))?; + + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(proposal.proposal.author.clone()), + default_signer, + ) + .await?; - if args.offline { - let signer = ctx.get(&signer); - let key = find_pk(client, &mut ctx.wallet, &signer).await?; - let signing_key = - signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; - let offline_proposal = - OfflineProposal::new(proposal, signer, &signing_key); - let proposal_filename = args - .proposal_data - .parent() - .expect("No parent found") - .join("proposal"); - let out = File::create(&proposal_filename).unwrap(); - match serde_json::to_writer_pretty(out, &offline_proposal) { - Ok(_) => { - println!( - "Proposal created: {}.", - proposal_filename.to_string_lossy() - ); - } - Err(e) => { - eprintln!("Error while creating proposal file: {}.", e); - safe_exit(1) - } - } - Ok(()) - } else { - let signer = ctx.get(&signer); - 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) - }; + submit_reveal_aux( + client, + &mut ctx, + args.tx.clone(), + &proposal.proposal.author, + ) + .await?; - let balance = - rpc::get_token_balance(client, &ctx.native_token, &proposal.author) - .await; - if balance - < token::Amount::from_uint( - governance_parameters.min_proposal_fund, - 0, + ( + tx::build_pgf_funding_proposal( + client, + args.clone(), + proposal, + &signing_data.gas_payer, ) - .unwrap() - { - eprintln!( - "Address {} doesn't have enough funds.", - &proposal.author - ); - safe_exit(1); - } + .await?, + signing_data, + ) + } else if args.is_pgf_stewards { + let proposal = PgfStewardProposal::try_from( + args.proposal_data.as_ref(), + ) + .map_err(|e| { + tx::Error::FailedGovernaneProposalDeserialize(e.to_string()) + })?; + let author_balane = rpc::get_token_balance( + client, + &ctx.native_token, + &proposal.proposal.author, + ) + .await; + let proposal = proposal + .validate(&governance_parameters, current_epoch, author_balane) + .map_err(|e| tx::Error::InvalidProposal(e.to_string()))?; - if init_proposal_content.len() - > governance_parameters.max_proposal_content_size as usize - { - eprintln!("Proposal content size too big.",); - safe_exit(1); - } + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(proposal.proposal.author.clone()), + default_signer, + ) + .await?; - let mut tx = Tx::new(TxType::Raw); - 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, - ))); - let content_sec_hash = content_sec.get_hash(); - init_proposal_data.content = content_sec_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))); - let code_sec_hash = code_sec.get_hash(); - init_proposal_data.r#type = - ProposalType::Default(Some(code_sec_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)); + submit_reveal_aux( + client, + &mut ctx, + args.tx.clone(), + &proposal.proposal.author, + ) + .await?; + + ( + tx::build_pgf_stewards_proposal( + client, + args.clone(), + proposal, + &signing_data.gas_payer, + ) + .await?, + signing_data, + ) + } else { + let proposal = DefaultProposal::try_from(args.proposal_data.as_ref()) + .map_err(|e| { + tx::Error::FailedGovernaneProposalDeserialize(e.to_string()) + })?; + let author_balane = rpc::get_token_balance( + client, + &ctx.native_token, + &proposal.proposal.author, + ) + .await; + let proposal = proposal + .validate(&governance_parameters, current_epoch, author_balane) + .map_err(|e| tx::Error::InvalidProposal(e.to_string()))?; - let (mut tx, addr, pk) = tx::prepare_tx( + let default_signer = Some(proposal.proposal.author.clone()); + let signing_data = signing::aux_signing_data( client, &mut ctx.wallet, &args.tx, - tx, - TxSigningKey::WalletAddress(signer), - #[cfg(not(feature = "mainnet"))] - false, + &Some(proposal.proposal.author.clone()), + default_signer, ) .await?; + submit_reveal_aux( client, &mut ctx, - &args.tx, - addr, - pk.clone(), - &mut tx, + args.tx.clone(), + &proposal.proposal.author, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; - Ok(()) + + ( + tx::build_default_proposal( + client, + args.clone(), + proposal, + &signing_data.gas_payer, + ) + .await?, + signing_data, + ) + }; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + signing::sign_tx( + &mut ctx.wallet, + &args.tx, + &mut tx_builder, + signing_data, + ); + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; } + + Ok(()) } pub async fn submit_vote_proposal( @@ -784,274 +895,166 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let signer = if let Some(addr) = &args.tx.signer { - addr - } else { - eprintln!("Missing mandatory argument --signer."); - safe_exit(1) - }; - - // Construct vote - let proposal_vote = match args.vote.to_ascii_lowercase().as_str() { - "yay" => { - if let Some(pgf) = args.proposal_pgf { - let splits = pgf.trim().split_ascii_whitespace(); - let address_iter = splits.clone().step_by(2); - let cap_iter = splits.into_iter().skip(1).step_by(2); - let mut set = HashSet::new(); - for (address, cap) in - address_iter.zip(cap_iter).map(|(addr, cap)| { - ( - addr.parse() - .expect("Failed to parse pgf council address"), - cap.parse::() - .expect("Failed to parse pgf spending cap"), - ) - }) - { - set.insert(( - address, - token::Amount::from_uint(cap, 0).unwrap(), - )); - } - - ProposalVote::Yay(VoteType::PGFCouncil(set)) - } else if let Some(eth) = args.proposal_eth { - let mut splits = eth.trim().split_ascii_whitespace(); - // Sign the message - let sigkey = splits - .next() - .expect("Expected signing key") - .parse::() - .expect("Signing key parsing failed."); - - let msg = splits.next().expect("Missing message to sign"); - if splits.next().is_some() { - eprintln!("Unexpected argument after message"); - safe_exit(1); - } + let current_epoch = rpc::query_and_print_epoch(client).await; - ProposalVote::Yay(VoteType::ETHBridge(common::SigScheme::sign( - &sigkey, - HEXLOWER_PERMISSIVE - .decode(msg.as_bytes()) - .expect("Error while decoding message"), - ))) - } else { - ProposalVote::Yay(VoteType::Default) - } - } - "nay" => ProposalVote::Nay, - _ => { - eprintln!("Vote must be either yay or nay"); - safe_exit(1); - } - }; + let default_signer = Some(args.voter.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(args.voter.clone()), + default_signer.clone(), + ) + .await?; - if args.offline { - if !proposal_vote.is_default_vote() { - eprintln!( - "Wrong vote type for offline proposal. Just vote yay or nay!" - ); - safe_exit(1); - } - let proposal_file_path = - args.proposal_data.expect("Proposal file should exist."); - let file = File::open(&proposal_file_path).expect("File must exist."); + let mut tx_builder = if args.is_offline { + let proposal_vote = ProposalVote::try_from(args.vote) + .map_err(|_| tx::Error::InvalidProposalVote)?; - let proposal: OfflineProposal = - serde_json::from_reader(file).expect("JSON was not well-formatted"); - let public_key = rpc::get_public_key(client, &proposal.address) - .await - .expect("Public key should exist."); - if !proposal.check_signature(&public_key) { - eprintln!("Proposal signature mismatch!"); - safe_exit(1) - } + let proposal = OfflineSignedProposal::try_from( + args.proposal_data.clone().unwrap().as_ref(), + ) + .map_err(|e| tx::Error::InvalidProposal(e.to_string()))? + .validate( + &signing_data.account_public_keys_map.clone().unwrap(), + signing_data.threshold, + ) + .map_err(|e| tx::Error::InvalidProposal(e.to_string()))?; + let delegations = rpc::get_delegators_delegation_at( + client, + &args.voter, + proposal.proposal.tally_epoch, + ) + .await + .keys() + .cloned() + .collect::>(); - let key = find_pk(client, &mut ctx.wallet, signer).await?; - let signing_key = - signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; let offline_vote = OfflineVote::new( &proposal, proposal_vote, - signer.clone(), - &signing_key, + args.voter.clone(), + delegations, ); - let proposal_vote_filename = proposal_file_path - .parent() - .expect("No parent found") - .join(format!("proposal-vote-{}", &signer.to_string())); - let out = File::create(&proposal_vote_filename).unwrap(); - match serde_json::to_writer_pretty(out, &offline_vote) { - Ok(_) => { - println!( - "Proposal vote created: {}.", - proposal_vote_filename.to_string_lossy() - ); - Ok(()) - } - Err(e) => { - eprintln!("Error while creating proposal vote file: {}.", e); - safe_exit(1) - } - } - } else { - let current_epoch = rpc::query_and_print_epoch(client).await; + let offline_signed_vote = offline_vote.sign( + args.tx.signing_keys, + &signing_data.account_public_keys_map.unwrap(), + ); + let output_file_path = offline_signed_vote + .serialize(args.tx.output_folder) + .expect("Should be able to serialize the offline proposal"); - let voter_address = signer.clone(); - let proposal_id = args.proposal_id.unwrap(); - let proposal_start_epoch_key = - gov_storage::get_voting_start_epoch_key(proposal_id); - let proposal_start_epoch = rpc::query_storage_value::( + println!("Proposal vote serialized to: {}", output_file_path); + return Ok(()); + } else { + tx::build_vote_proposal( client, - &proposal_start_epoch_key, + args.clone(), + current_epoch, + &signing_data.gas_payer, ) - .await; + .await? + }; - // Check vote type and memo - let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); - let proposal_type: ProposalType = rpc::query_storage_value::< - C, - ProposalType, - >(client, &proposal_type_key) - .await - .unwrap_or_else(|| { - panic!("Didn't find type of proposal id {} in storage", proposal_id) - }); + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + signing::sign_tx( + &mut ctx.wallet, + &args.tx, + &mut tx_builder, + signing_data, + ); + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } - if let ProposalVote::Yay(ref vote_type) = proposal_vote { - if &proposal_type != vote_type { - eprintln!( - "Expected vote of type {}, found {}", - proposal_type, args.vote - ); - safe_exit(1); - } else if let VoteType::PGFCouncil(set) = vote_type { - // Check that addresses proposed as council are established and - // are present in storage - for (address, _) in set { - match address { - Address::Established(_) => { - let vp_key = Key::validity_predicate(address); - if !rpc::query_has_storage_key::(client, &vp_key) - .await - { - eprintln!( - "Proposed PGF council {} cannot be found \ - in storage", - address - ); - safe_exit(1); - } - } - _ => { - eprintln!( - "PGF council vote contains a non-established \ - address: {}", - address - ); - safe_exit(1); - } - } - } - } - } + Ok(()) +} - match proposal_start_epoch { - Some(epoch) => { - if current_epoch < epoch { - eprintln!( - "Current epoch {} is not greater than proposal start \ - epoch {}", - current_epoch, epoch - ); +pub async fn sign_tx( + client: &C, + ctx: &mut Context, + args::SignTx { + tx: tx_args, + tx_data, + owner, + }: args::SignTx, +) -> Result<(), tx::Error> +where + C: namada::ledger::queries::Client + Sync, + C::Error: std::fmt::Display, +{ + let tx = if let Ok(transaction) = Tx::deserialize(tx_data.as_ref()) { + transaction + } else { + eprintln!("Couldn't decode the transaction."); + safe_exit(1) + }; - if !args.tx.force { - safe_exit(1) - } - } - let mut delegations = - rpc::get_delegators_delegation(client, &voter_address) - .await; - - // Optimize by quering if a vote from a validator - // is equal to ours. If so, we can avoid voting, but ONLY if we - // are voting in the last third of the voting - // window, otherwise there's the risk of the - // validator changing his vote and, effectively, invalidating - // the delgator's vote - if !args.tx.force - && is_safe_voting_window(client, proposal_id, epoch).await? - { - delegations = filter_delegations( - client, - delegations, - proposal_id, - &proposal_vote, - ) - .await; - } + let default_signer = Some(owner.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &tx_args, + &Some(owner), + default_signer, + ) + .await?; - let tx_data = VoteProposalData { - id: proposal_id, - vote: proposal_vote, - voter: voter_address, - delegations: delegations.into_iter().collect(), - }; - - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; - let data = tx_data - .try_to_vec() - .expect("Encoding proposal data shouldn't fail"); - - let tx_code_hash = query_wasm_code_hash( - client, - args.tx_code_path.to_str().unwrap(), - ) - .await - .unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = chain_id; - tx.header.expiration = expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); - - let (mut tx, addr, pk) = tx::prepare_tx( - client, - &mut ctx.wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(signer.clone()), - #[cfg(not(feature = "mainnet"))] - false, - ) - .await?; - submit_reveal_aux( - client, - &mut ctx, - &args.tx, - addr, - pk.clone(), - &mut tx, - ) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk) - .await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; - Ok(()) - } - None => { + let secret_keys = &signing_data + .public_keys + .iter() + .filter_map(|public_key| { + if let Ok(secret_key) = + signing::find_key_by_pk(&mut ctx.wallet, &tx_args, public_key) + { + Some(secret_key) + } else { eprintln!( - "Proposal start epoch for proposal id {} is not definied.", - proposal_id + "Couldn't find the secret key for {}. Skipping signature \ + generation.", + public_key ); - if !args.tx.force { safe_exit(1) } else { Ok(()) } + None } + }) + .collect::>(); + + if let Some(account_public_keys_map) = signing_data.account_public_keys_map + { + let signatures = + tx.compute_section_signature(secret_keys, &account_public_keys_map); + + for signature in &signatures { + let filename = format!( + "offline_signature_{}_{}.tx", + tx.header_hash(), + signature.index + ); + let output_path = match &tx_args.output_folder { + Some(path) => path.join(filename), + None => filename.into(), + }; + + let signature_path = File::create(&output_path) + .expect("Should be able to create signature file."); + + serde_json::to_writer_pretty( + signature_path, + &signature.serialize(), + ) + .expect("Signature should be deserializable."); + println!( + "Signature for {} serialized at {}", + &account_public_keys_map + .get_public_key_from_index(signature.index) + .unwrap(), + output_path.display() + ); } } + Ok(()) } pub async fn submit_reveal_pk( @@ -1063,71 +1066,9 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let reveal_tx = - tx::build_reveal_pk(client, &mut ctx.wallet, args.clone()).await?; - if let Some((mut tx, _, pk)) = reveal_tx { - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; - } - Ok(()) -} + submit_reveal_aux(client, ctx, args.tx, &(&args.public_key).into()).await?; -/// Check if current epoch is in the last third of the voting period of the -/// proposal. This ensures that it is safe to optimize the vote writing to -/// storage. -async fn is_safe_voting_window( - client: &C, - proposal_id: u64, - proposal_start_epoch: Epoch, -) -> Result -where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, -{ - tx::is_safe_voting_window(client, proposal_id, proposal_start_epoch).await -} - -/// Removes validators whose vote corresponds to that of the delegator (needless -/// vote) -async fn filter_delegations( - client: &C, - delegations: HashSet
, - proposal_id: u64, - delegator_vote: &ProposalVote, -) -> HashSet
-where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, -{ - // Filter delegations by their validator's vote concurrently - let delegations = futures::future::join_all( - delegations - .into_iter() - // we cannot use `filter/filter_map` directly because we want to - // return a future - .map(|validator_address| async { - let vote_key = gov_storage::get_vote_proposal_key( - proposal_id, - validator_address.to_owned(), - validator_address.to_owned(), - ); - - if let Some(validator_vote) = - rpc::query_storage_value::( - client, &vote_key, - ) - .await - { - if &validator_vote == delegator_vote { - return None; - } - } - Some(validator_address) - }), - ) - .await; - // Take out the `None`s - delegations.into_iter().flatten().collect() + Ok(()) } pub async fn submit_bond( @@ -1139,11 +1080,32 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = - tx::build_bond::(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let default_address = args.source.clone().unwrap_or(args.validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(default_address.clone()), + default_signer, + ) + .await?; + + submit_reveal_aux(client, ctx, args.tx.clone(), &default_address).await?; + + let mut tx = + tx::build_bond::(client, args.clone(), &signing_data.gas_payer) + .await?; + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } @@ -1156,12 +1118,36 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk, latest_withdrawal_pre) = - tx::build_unbond(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; - tx::query_unbonds(client, args.clone(), latest_withdrawal_pre).await?; + let default_address = args.source.clone().unwrap_or(args.validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(default_address), + default_signer, + ) + .await?; + + let (mut tx, latest_withdrawal_pre) = tx::build_unbond( + client, + &mut ctx.wallet, + args.clone(), + &signing_data.gas_payer, + ) + .await?; + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + + tx::query_unbonds(client, args.clone(), latest_withdrawal_pre).await?; + } + Ok(()) } @@ -1174,12 +1160,30 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = - tx::build_withdraw(client, &mut ctx.wallet, args.clone()).await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let default_address = args.source.clone().unwrap_or(args.validator.clone()); + let default_signer = Some(default_address.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(default_address), + default_signer, + ) + .await?; + + let mut tx = + tx::build_withdraw(client, args.clone(), &signing_data.gas_payer) + .await?; + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } @@ -1192,14 +1196,32 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let arg = args.clone(); - let (mut tx, addr, pk) = - tx::build_validator_commission_change(client, &mut ctx.wallet, arg) - .await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let default_signer = Some(args.validator.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(args.validator.clone()), + default_signer, + ) + .await?; + + let mut tx = tx::build_validator_commission_change( + client, + args.clone(), + &signing_data.gas_payer, + ) + .await?; + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } @@ -1214,13 +1236,32 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let (mut tx, addr, pk) = - tx::build_unjail_validator(client, &mut ctx.wallet, args.clone()) - .await?; - submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) - .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + let default_signer = Some(args.validator.clone()); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &Some(args.validator.clone()), + default_signer, + ) + .await?; + + let mut tx = tx::build_unjail_validator( + client, + args.clone(), + &signing_data.gas_payer, + ) + .await?; + signing::generate_test_vector(client, &mut ctx.wallet, &tx).await; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx); + } else { + signing::sign_tx(&mut ctx.wallet, &args.tx, &mut tx, signing_data); + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } @@ -1270,12 +1311,11 @@ where #[cfg(test)] mod test_tx { use masp_primitives::transaction::components::Amount; + use namada::core::types::storage::Epoch; use namada::ledger::masp::{make_asset_type, MaspAmount}; use namada::types::address::testing::gen_established_address; use namada::types::token::MaspDenom; - use super::*; - #[test] fn test_masp_add_amount() { let address_1 = gen_established_address(); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5c86debcb6..320a153d64 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -4,10 +4,10 @@ use std::collections::HashMap; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; +use namada::core::ledger::governance::parameters::GovernanceParameters; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; use namada::ledger::eth_bridge::EthereumBridgeConfig; -use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{Dec, GenesisValidator, PosParams}; use namada::types::address::Address; @@ -29,9 +29,9 @@ pub mod genesis_config { use data_encoding::HEXLOWER; use eyre::Context; + use namada::core::ledger::governance::parameters::GovernanceParameters; #[cfg(not(feature = "mainnet"))] use namada::core::ledger::testnet_pow; - use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{Dec, GenesisValidator, PosParams}; use namada::types::address::Address; @@ -148,7 +148,7 @@ pub mod genesis_config { // Maximum size of proposal in kibibytes (KiB) pub max_proposal_code_size: u64, // Minimum proposal period length in epochs - pub min_proposal_period: u64, + pub min_proposal_voting_period: u64, // Maximum proposal period length in epochs pub max_proposal_period: u64, // Maximum number of characters in the proposal content @@ -261,6 +261,8 @@ pub mod genesis_config { pub implicit_vp: String, /// Expected number of epochs per year pub epochs_per_year: u64, + /// Max signature per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p pub pos_gain_p: Dec, /// PoS gain d @@ -601,25 +603,28 @@ pub mod genesis_config { implicit_vp_code_path, implicit_vp_sha256, epochs_per_year: parameters.epochs_per_year, + max_signatures_per_transaction: parameters + .max_signatures_per_transaction, pos_gain_p: parameters.pos_gain_p, pos_gain_d: parameters.pos_gain_d, staked_ratio: Dec::zero(), pos_inflation_amount: token::Amount::zero(), + #[cfg(not(feature = "mainnet"))] wrapper_tx_fees: parameters.wrapper_tx_fees, }; let GovernanceParamsConfig { min_proposal_fund, max_proposal_code_size, - min_proposal_period, + min_proposal_voting_period, max_proposal_content_size, min_proposal_grace_epochs, max_proposal_period, } = gov_params; - let gov_params = GovParams { - min_proposal_fund, + let gov_params = GovernanceParameters { + min_proposal_fund: token::Amount::native_whole(min_proposal_fund), max_proposal_code_size, - min_proposal_period, + min_proposal_voting_period, max_proposal_content_size, min_proposal_grace_epochs, max_proposal_period, @@ -720,7 +725,7 @@ pub struct Genesis { pub implicit_accounts: Vec, pub parameters: Parameters, pub pos_params: PosParams, - pub gov_params: GovParams, + pub gov_params: GovernanceParameters, // Ethereum bridge config pub ethereum_bridge_params: Option, } @@ -848,6 +853,8 @@ pub struct Parameters { pub implicit_vp_sha256: [u8; 32], /// Expected number of epochs per year (read only) pub epochs_per_year: u64, + /// Maximum amount of signatures per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p (read only) pub pos_gain_p: Dec, /// PoS gain d (read only) @@ -969,12 +976,14 @@ pub fn genesis(num_validators: u64) -> Genesis { tx_whitelist: vec![], implicit_vp_code_path: vp_implicit_path.into(), implicit_vp_sha256: Default::default(), + max_signatures_per_transaction: 15, epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds * per epoch (60 = min_duration) */ 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(), + #[cfg(not(feature = "mainnet"))] wrapper_tx_fees: Some(token::Amount::native_whole(0)), }; let albert = EstablishedAccount { @@ -1074,7 +1083,7 @@ pub fn genesis(num_validators: u64) -> Genesis { token_accounts, parameters, pos_params: PosParams::default(), - gov_params: GovParams::default(), + gov_params: GovernanceParameters::default(), ethereum_bridge_params: Some(EthereumBridgeConfig { eth_start_height: Default::default(), min_confirmations: Default::default(), diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 0bcb1b5f61..f6307c6bd1 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -283,7 +283,7 @@ impl Config { .and_then(|c| c.merge(config::File::with_name(file_name))) .and_then(|c| { c.merge( - config::Environment::with_prefix("namada").separator("__"), + config::Environment::with_prefix("NAMADA").separator("__"), ) }) .map_err(Error::ReadError)?; diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 37b447b4da..e421fc6e6b 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -14,8 +14,8 @@ use std::thread; use byte_unit::Byte; use futures::future::TryFutureExt; +use namada::core::ledger::governance::storage::keys as governance_storage; use namada::eth_bridge::ethers::providers::{Http, Provider}; -use namada::ledger::governance::storage as gov_storage; use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; @@ -64,7 +64,7 @@ const ENV_VAR_RAYON_THREADS: &str = "NAMADA_RAYON_THREADS"; //``` impl Shell { fn load_proposals(&mut self) { - let proposals_key = gov_storage::get_commiting_proposals_prefix( + let proposals_key = governance_storage::get_commiting_proposals_prefix( self.wl_storage.storage.last_epoch.0, ); @@ -73,7 +73,7 @@ impl Shell { for (key, _, _) in proposal_iter { let key = Key::from_str(key.as_str()).expect("Key should be parsable"); - if gov_storage::get_commit_proposal_epoch(&key).unwrap() + if governance_storage::get_commit_proposal_epoch(&key).unwrap() != self.wl_storage.storage.last_epoch.0 { // NOTE: `iter_prefix` iterate over the matching prefix. In this @@ -85,7 +85,7 @@ impl Shell { continue; } - let proposal_id = gov_storage::get_commit_proposal_id(&key); + let proposal_id = governance_storage::get_commit_proposal_id(&key); if let Some(id) = proposal_id { self.proposal_data.insert(id); } @@ -329,7 +329,7 @@ async fn run_aux_setup( // Find the system available memory let available_memory_bytes = Lazy::new(|| { let sys = System::new_with_specifics(RefreshKind::new().with_memory()); - let available_memory_bytes = sys.available_memory() * 1024; + let available_memory_bytes = sys.available_memory(); tracing::info!( "Available memory: {}", Byte::from_bytes(available_memory_bytes as u128) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 2f178eb47e..8af2d1a746 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -3,6 +3,8 @@ use std::collections::HashMap; use data_encoding::HEXUPPER; +use namada::core::ledger::pgf::storage::keys as pgf_storage; +use namada::core::ledger::pgf::ADDRESS as pgf_address; use namada::ledger::parameters::storage as params_storage; use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; @@ -92,8 +94,7 @@ where &mut self.wl_storage, )?; - let _proposals_result = - execute_governance_proposals(self, &mut response)?; + execute_governance_proposals(self, &mut response)?; // Copy the new_epoch + pipeline_len - 1 validator set into // new_epoch + pipeline_len @@ -247,15 +248,15 @@ where self.invalidate_pow_solution_if_valid(wrapper); // Charge fee - let fee_payer = + let gas_payer = if wrapper.pk != address::masp_tx_key().ref_to() { - wrapper.fee_payer() + wrapper.gas_payer() } else { address::masp() }; let balance_key = - token::balance_key(&wrapper.fee.token, &fee_payer); + token::balance_key(&wrapper.fee.token, &gas_payer); let balance: token::Amount = self .wl_storage .read(&balance_key) @@ -817,6 +818,68 @@ where .remove(&mut self.wl_storage, &address)?; } + // Pgf inflation + let pgf_inflation_rate_key = pgf_storage::get_pgf_inflation_rate_key(); + let pgf_inflation_rate = self + .read_storage_key::(&pgf_inflation_rate_key) + .unwrap_or_default(); + + let pgf_pd_rate = pgf_inflation_rate / Dec::from(epochs_per_year); + let pgf_inflation = Dec::from(total_tokens) * pgf_pd_rate; + let pgf_inflation_amount = token::Amount::from(pgf_inflation); + + credit_tokens( + &mut self.wl_storage, + &staking_token, + &pgf_address, + pgf_inflation_amount, + )?; + + tracing::info!( + "Minting {} tokens for PGF rewards distribution into the PGF \ + account.", + pgf_inflation_amount.to_string_native() + ); + + // Pgf steward inflation + let pgf_stewards_inflation_rate_key = + pgf_storage::get_steward_inflation_rate_key(); + let pgf_stewards_inflation_rate = self + .read_storage_key::(&pgf_stewards_inflation_rate_key) + .unwrap_or_default(); + + let pgf_stewards_pd_rate = + pgf_stewards_inflation_rate / Dec::from(epochs_per_year); + let pgf_steward_inflation = + Dec::from(total_tokens) * pgf_stewards_pd_rate; + + let pgf_stewards_key = pgf_storage::get_stewards_key(); + let pgf_stewards: BTreeSet
= + self.read_storage_key(&pgf_stewards_key).unwrap_or_default(); + + let pgf_steward_reward = match pgf_stewards.len() { + 0 => Dec::zero(), + _ => pgf_steward_inflation + .trunc_div(&Dec::from(pgf_stewards.len())) + .unwrap_or_default(), + }; + let pgf_steward_reward = token::Amount::from(pgf_steward_reward); + + for steward in pgf_stewards { + credit_tokens( + &mut self.wl_storage, + &staking_token, + &steward, + pgf_steward_reward, + )?; + tracing::info!( + "Minting {} tokens for PGF Steward rewards distribution into \ + the steward address {}.", + pgf_steward_reward.to_string_native(), + steward, + ); + } + Ok(()) } @@ -941,6 +1004,11 @@ mod test_finalize_block { use data_encoding::HEXUPPER; use namada::core::ledger::eth_bridge::storage::wrapped_erc20s; + use namada::core::ledger::governance::storage::keys::get_proposal_execution_key; + use namada::core::ledger::governance::storage::proposal::ProposalType; + use namada::core::ledger::governance::storage::vote::{ + StorageProposalVote, VoteType, + }; use namada::eth_bridge::storage::bridge_pool::{ self, get_key_from_hash, get_nonce_key, get_signed_root_key, }; @@ -973,7 +1041,6 @@ mod test_finalize_block { use namada::types::ethereum_events::{ EthAddress, TransferToEthereum, Uint as ethUint, }; - use namada::types::governance::ProposalVote; use namada::types::hash::Hash; use namada::types::keccak::KeccakHash; use namada::types::key::tm_consensus_key_raw_hash; @@ -981,7 +1048,7 @@ mod test_finalize_block { use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::token::{Amount, NATIVE_MAX_DECIMAL_PLACES}; use namada::types::transaction::governance::{ - InitProposalData, ProposalType, VoteProposalData, + InitProposalData, VoteProposalData, }; use namada::types::transaction::protocol::EthereumTxData; use namada::types::transaction::{Fee, WrapperTx, MIN_FEE_AMOUNT}; @@ -1007,7 +1074,7 @@ mod test_finalize_block { keypair: &common::SecretKey, ) -> (Tx, ProcessedTx) { let mut wrapper_tx = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: MIN_FEE_AMOUNT, token: shell.wl_storage.storage.native_token.clone(), @@ -1047,17 +1114,18 @@ mod test_finalize_block { keypair: &common::SecretKey, ) -> ProcessedTx { let tx_code = TestWasms::TxNoOp.read_bytes(); - let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: MIN_FEE_AMOUNT, - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut outer_tx = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE_AMOUNT, + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new( @@ -1155,17 +1223,18 @@ mod test_finalize_block { fn test_process_proposal_rejected_decrypted_tx() { let (mut shell, _, _, _) = setup(); let keypair = gen_keypair(); - let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Default::default(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut outer_tx = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Default::default(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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( @@ -1221,7 +1290,7 @@ mod test_finalize_block { pow_solution: None, }; let processed_tx = ProcessedTx { - tx: Tx::new(TxType::Decrypted(DecryptedTx::Undecryptable)) + tx: Tx::from_type(TxType::Decrypted(DecryptedTx::Undecryptable)) .to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), @@ -1229,7 +1298,7 @@ mod test_finalize_block { }, }; - let tx = Tx::new(TxType::Wrapper(Box::new(wrapper))); + let tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); shell.enqueue_tx(tx); // check that correct error message is returned @@ -1754,11 +1823,8 @@ mod test_finalize_block { }; // Add a proposal to be accepted and one to be rejected. - add_proposal( - 0, - ProposalVote::Yay(namada::types::governance::VoteType::Default), - ); - add_proposal(1, ProposalVote::Nay); + add_proposal(0, StorageProposalVote::Yay(VoteType::Default)); + add_proposal(1, StorageProposalVote::Nay); // Commit the genesis state shell.wl_storage.commit_block().unwrap(); @@ -2189,7 +2255,7 @@ mod test_finalize_block { let tx_code = std::fs::read(wasm_path) .expect("Expected a file at given code path"); let mut wrapper_tx = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), @@ -3505,16 +3571,15 @@ mod test_finalize_block { /// Test that updating the ethereum bridge params via governance works. #[tokio::test] async fn test_eth_bridge_param_updates() { - use namada::ledger::governance::storage as gov_storage; let (mut shell, _broadcaster, _, mut control_receiver) = setup_at_height(3u64); - let proposal_execution_key = gov_storage::get_proposal_execution_key(0); + let proposal_execution_key = get_proposal_execution_key(0); shell .wl_storage .write(&proposal_execution_key, 0u64) .expect("Test failed."); - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(0u64.try_to_vec().expect("Test failed"))); + let mut tx = Tx::new(shell.chain_id.clone(), None); + tx.add_code_from_hash(Hash::default()).add_data(0u64); let new_min_confirmations = MinimumConfirmations::from(unsafe { NonZeroU64::new_unchecked(42) }); diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index e488cbf2b8..09f0c9dac3 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,22 +1,30 @@ -use namada::core::ledger::slash_fund::ADDRESS as slash_fund_address; -use namada::core::types::transaction::governance::ProposalType; -use namada::ledger::events::EventType; -use namada::ledger::governance::{ - storage as gov_storage, ADDRESS as gov_address, +use std::collections::{BTreeSet, HashMap}; + +use namada::core::ledger::governance::storage::keys as gov_storage; +use namada::core::ledger::governance::storage::proposal::{ + AddRemove, PGFAction, PGFTarget, ProposalType, }; -use namada::ledger::native_vp::governance::utils::{ - compute_tally, get_proposal_votes, ProposalEvent, +use namada::core::ledger::governance::utils::{ + compute_proposal_result, ProposalVotes, TallyResult, TallyType, TallyVote, + VotePower, }; +use namada::core::ledger::governance::ADDRESS as gov_address; +use namada::core::ledger::pgf::storage::keys as pgf_storage; +use namada::core::ledger::pgf::ADDRESS; +use namada::core::ledger::storage_api::governance as gov_api; +use namada::ledger::governance::utils::ProposalEvent; +use namada::ledger::pos::BondId; use namada::ledger::protocol; use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::ledger::storage_api::{token, StorageWrite}; -use namada::proof_of_stake::read_total_stake; +use namada::proof_of_stake::parameters::PosParams; +use namada::proof_of_stake::{bond_amount, read_total_stake}; use namada::proto::{Code, Data}; use namada::types::address::Address; -use namada::types::governance::{Council, Tally, TallyResult, VotePower}; use namada::types::storage::Epoch; +use super::utils::force_read; use super::*; #[derive(Default)] @@ -40,218 +48,333 @@ where let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(id); let proposal_type_key = gov_storage::get_proposal_type_key(id); - let funds = shell - .read_storage_key::(&proposal_funds_key) - .ok_or_else(|| { - Error::BadProposal(id, "Invalid proposal funds.".to_string()) - })?; - let proposal_end_epoch = shell - .read_storage_key::(&proposal_end_epoch_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal end_epoch.".to_string(), - ) - })?; - - let proposal_type = shell - .read_storage_key::(&proposal_type_key) - .ok_or_else(|| { - Error::BadProposal(id, "Invalid proposal type".to_string()) - })?; - - let votes = - get_proposal_votes(&shell.wl_storage, proposal_end_epoch, id) - .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; - let params = read_pos_params(&shell.wl_storage) - .map_err(|msg| Error::BadProposal(id, msg.to_string()))?; - 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::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; - - // Execute proposal if succesful - let transfer_address = match tally_result { - TallyResult::Passed(tally) => { - let (successful_execution, proposal_event) = match tally { - Tally::Default => execute_default_proposal(shell, id), - Tally::PGFCouncil(council) => { - execute_pgf_proposal(id, council) + let funds: token::Amount = + force_read(&shell.wl_storage, &proposal_funds_key)?; + let proposal_end_epoch: Epoch = + force_read(&shell.wl_storage, &proposal_end_epoch_key)?; + let proposal_type: ProposalType = + force_read(&shell.wl_storage, &proposal_type_key)?; + + let params = read_pos_params(&shell.wl_storage)?; + let total_voting_power = + read_total_stake(&shell.wl_storage, ¶ms, proposal_end_epoch)?; + + let tally_type = TallyType::from(proposal_type.clone()); + let votes = compute_proposal_votes( + &shell.wl_storage, + ¶ms, + id, + proposal_end_epoch, + )?; + let proposal_result = + compute_proposal_result(votes, total_voting_power, tally_type); + + let transfer_address = match proposal_result.result { + TallyResult::Passed => { + let proposal_event = match proposal_type { + ProposalType::Default(_) => { + let proposal_code_key = + gov_storage::get_proposal_code_key(id); + let proposal_code = + shell.wl_storage.read_bytes(&proposal_code_key)?; + let result = execute_default_proposal( + shell, + id, + proposal_code.clone(), + )?; + tracing::info!( + "Governance proposal (default) {} has been \ + executed ({}) and passed.", + id, + result + ); + + ProposalEvent::default_proposal_event( + id, + proposal_code.is_some(), + result, + ) + .into() } - Tally::ETHBridge => execute_eth_proposal(id), - }; + ProposalType::PGFSteward(stewards) => { + let result = execute_pgf_steward_proposal( + &mut shell.wl_storage, + stewards, + )?; + tracing::info!( + "Governance proposal (pgf stewards){} has been \ + executed and passed.", + id + ); + + ProposalEvent::pgf_steward_proposal_event(id, result) + .into() + } + ProposalType::PGFPayment(payments) => { + let native_token = + &shell.wl_storage.get_native_token()?; + let result = execute_pgf_payment_proposal( + &mut shell.wl_storage, + native_token, + payments, + )?; + tracing::info!( + "Governance proposal (pgs payments) {} has been \ + executed and passed.", + id + ); + ProposalEvent::pgf_payments_proposal_event(id, result) + .into() + } + }; response.events.push(proposal_event); - if successful_execution { - proposals_result.passed.push(id); - shell - .read_storage_key::
( - &gov_storage::get_author_key(id), - ) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })? - } else { - proposals_result.rejected.push(id); - slash_fund_address - } + proposals_result.passed.push(id); + + let proposal_author_key = gov_storage::get_author_key(id); + shell.wl_storage.read::
(&proposal_author_key)? } TallyResult::Rejected => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Rejected, - id, - false, - false, - ) - .into(); + if let ProposalType::PGFPayment(_) = proposal_type { + let two_third_nay = proposal_result.two_third_nay(); + if two_third_nay { + let pgf_stewards_key = pgf_storage::get_stewards_key(); + let proposal_author = gov_storage::get_author_key(id); + + let mut pgf_stewards = shell + .wl_storage + .read::>(&pgf_stewards_key)? + .unwrap_or_default(); + let proposal_author: Address = + force_read(&shell.wl_storage, &proposal_author)?; + + pgf_stewards.remove(&proposal_author); + shell + .wl_storage + .write(&pgf_stewards_key, pgf_stewards)?; + + tracing::info!( + "Governance proposal {} was rejected with 2/3 of \ + nay votes. Removing {} from stewards set.", + id, + proposal_author + ); + } + } + let proposal_event = + ProposalEvent::rejected_proposal_event(id).into(); response.events.push(proposal_event); proposals_result.rejected.push(id); - slash_fund_address + tracing::info!( + "Governance proposal {} has been executed and rejected.", + id + ); + + None } }; let native_token = shell.wl_storage.storage.native_token.clone(); - // transfer proposal locked funds - token::transfer( - &mut shell.wl_storage, - &native_token, - &gov_address, - &transfer_address, - funds, - ) - .expect( - "Must be able to transfer governance locked funds after proposal \ - has been tallied", - ); + if let Some(address) = transfer_address { + token::transfer( + &mut shell.wl_storage, + &native_token, + &gov_address, + &address, + funds, + )?; + } else { + token::burn( + &mut shell.wl_storage, + &native_token, + &gov_address, + funds, + )?; + } } Ok(proposals_result) } +fn compute_proposal_votes( + storage: &S, + params: &PosParams, + proposal_id: u64, + epoch: Epoch, +) -> storage_api::Result +where + S: StorageRead, +{ + let votes = gov_api::get_proposal_votes(storage, proposal_id)?; + + let mut validators_vote: HashMap = HashMap::default(); + let mut validator_voting_power: HashMap = + HashMap::default(); + let mut delegators_vote: HashMap = HashMap::default(); + let mut delegator_voting_power: HashMap< + Address, + HashMap, + > = HashMap::default(); + + for vote in votes { + if vote.is_validator() { + let validator = vote.validator.clone(); + let vote_data = vote.data.clone(); + + let validator_stake = + read_total_stake(storage, params, epoch).unwrap_or_default(); + + validators_vote.insert(validator.clone(), vote_data.into()); + validator_voting_power.insert(validator, validator_stake); + } else { + let validator = vote.validator.clone(); + let delegator = vote.delegator.clone(); + let vote_data = vote.data.clone(); + + let bond_id = BondId { + source: delegator.clone(), + validator: validator.clone(), + }; + let (_, delegator_stake) = + bond_amount(storage, &bond_id, epoch).unwrap_or_default(); + + delegators_vote.insert(delegator.clone(), vote_data.into()); + delegator_voting_power + .entry(delegator) + .or_default() + .insert(validator, delegator_stake); + } + } + + Ok(ProposalVotes { + validators_vote, + validator_voting_power, + delegators_vote, + delegator_voting_power, + }) +} + fn execute_default_proposal( shell: &mut Shell, id: u64, -) -> (bool, Event) + proposal_code: Option>, +) -> storage_api::Result where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { - let proposal_code_key = gov_storage::get_proposal_code_key(id); - let proposal_code = shell.read_storage_key_bytes(&proposal_code_key); - match proposal_code { - Some(proposal_code) => { - let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })); - tx.header.chain_id = shell.chain_id.clone(); - tx.set_data(Data::new(encode(&id))); - tx.set_code(Code::new(proposal_code)); - let pending_execution_key = - gov_storage::get_proposal_execution_key(id); - shell - .wl_storage - .write(&pending_execution_key, ()) - .expect("Should be able to write to storage."); - let tx_result = protocol::dispatch_tx( - tx, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ - TxIndex::default(), - &mut BlockGasMeter::default(), - &mut shell.wl_storage, - &mut shell.vp_wasm_cache, - &mut shell.tx_wasm_cache, - ); - shell - .wl_storage - .storage - .delete(&pending_execution_key) - .expect("Should be able to delete the storage."); - match tx_result { - Ok(tx_result) if tx_result.is_accepted() => { + if let Some(code) = proposal_code { + let pending_execution_key = gov_storage::get_proposal_execution_key(id); + shell.wl_storage.write(&pending_execution_key, ())?; + + let mut tx = Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_data(Data::new(encode(&id))); + tx.set_code(Code::new(code)); + + // 0 parameter is used to compute the fee + // based on the code size. We dont + // need it here. + let tx_result = protocol::dispatch_tx( + tx, + 0, /* this is used to compute the fee + * based on the code size. We dont + * need it here. */ + TxIndex::default(), + &mut BlockGasMeter::default(), + &mut shell.wl_storage, + &mut shell.vp_wasm_cache, + &mut shell.tx_wasm_cache, + ); + shell + .wl_storage + .storage + .delete(&pending_execution_key) + .expect("Should be able to delete the storage."); + match tx_result { + Ok(tx_result) => { + if tx_result.is_accepted() { shell.wl_storage.commit_tx(); - ( - tx_result.is_accepted(), - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed(Tally::Default), - id, - true, - tx_result.is_accepted(), - ) - .into(), - ) - } - _ => { - shell.wl_storage.drop_tx(); - ( - false, - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed(Tally::Default), - id, - true, - false, - ) - .into(), - ) + Ok(true) + } else { + Ok(false) } } + Err(_) => { + shell.wl_storage.drop_tx(); + Ok(false) + } } - None => ( - true, - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed(Tally::Default), - id, - false, - false, - ) - .into(), - ), + } else { + tracing::info!( + "Governance proposal {} doesn't have any associated proposal code.", + id + ); + Ok(true) } } -fn execute_pgf_proposal(id: u64, council: Council) -> (bool, Event) { - // TODO: implement when PGF is in place, update the PGF - // council in storage - ( - true, - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed(Tally::PGFCouncil(council)), - id, - false, - false, - ) - .into(), - ) +fn execute_pgf_steward_proposal( + storage: &mut S, + stewards: HashSet>, +) -> Result +where + S: StorageRead + StorageWrite, +{ + let stewards_key = pgf_storage::get_stewards_key(); + let mut storage_stewards: BTreeSet
= + storage.read(&stewards_key)?.unwrap_or_default(); + + for action in stewards { + match action { + AddRemove::Add(steward) => storage_stewards.insert(steward), + AddRemove::Remove(steward) => storage_stewards.remove(&steward), + }; + } + + let write_result = storage.write(&stewards_key, storage_stewards); + Ok(write_result.is_ok()) } -fn execute_eth_proposal(id: u64) -> (bool, Event) { - // TODO: implement when ETH Bridge. Apply the - // modification requested by the proposal - // - ( - true, - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed(Tally::ETHBridge), - id, - false, - false, - ) - .into(), - ) +fn execute_pgf_payment_proposal( + storage: &mut S, + token: &Address, + payments: Vec, +) -> Result +where + S: StorageRead + StorageWrite, +{ + let continous_payments_key = pgf_storage::get_payments_key(); + let mut continous_payments: BTreeSet = + storage.read(&continous_payments_key)?.unwrap_or_default(); + + for payment in payments { + match payment { + PGFAction::Continuous(action) => match action { + AddRemove::Add(target) => { + continous_payments.insert(target); + } + AddRemove::Remove(target) => { + continous_payments.remove(&target); + } + }, + PGFAction::Retro(target) => { + token::transfer( + storage, + token, + &ADDRESS, + &target.target, + target.amount, + )?; + } + } + } + + let write_result = + storage.write(&continous_payments_key, continous_payments); + Ok(write_result.is_ok()) } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index ef08373789..41bc88c03b 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -90,6 +90,7 @@ where implicit_vp_code_path, implicit_vp_sha256, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -182,6 +183,7 @@ where tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -289,10 +291,12 @@ where .unwrap(); if let Some(pk) = public_key { - let pk_storage_key = pk_key(&address); - self.wl_storage - .write_bytes(&pk_storage_key, pk.try_to_vec().unwrap()) - .unwrap(); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + &address, + &pk, + 0, + )?; } for (key, value) in storage { @@ -328,9 +332,14 @@ where ) { // Initialize genesis implicit for genesis::ImplicitAccount { public_key } in accounts { - let address: Address = (&public_key).into(); - let pk_storage_key = pk_key(&address); - self.wl_storage.write(&pk_storage_key, public_key).unwrap(); + let address: address::Address = (&public_key).into(); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + &address, + &public_key, + 0, + ) + .unwrap(); } } @@ -390,10 +399,13 @@ where .write_bytes(&Key::validity_predicate(addr), vp_code_hash) .expect("Unable to write user VP"); // Validator account key - let pk_key = pk_key(addr); - self.wl_storage - .write(&pk_key, &validator.account_key) - .expect("Unable to set genesis user public key"); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + addr, + &validator.account_key, + 0, + ) + .unwrap(); // Balances // Account balance (tokens not staked in PoS) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2b7a0b7ac7..c65fbb3f99 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -16,6 +16,7 @@ mod stats; #[cfg(any(test, feature = "testing"))] #[allow(dead_code)] pub mod testing; +pub mod utils; mod vote_extensions; use std::collections::{BTreeSet, HashSet}; @@ -1242,13 +1243,13 @@ where } // Check balance for fee - let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { - wrapper.fee_payer() + let gas_payer = if wrapper.pk != masp_tx_key().ref_to() { + wrapper.gas_payer() } else { masp() }; // check that the fee payer has sufficient balance - let balance = self.get_balance(&wrapper.fee.token, &fee_payer); + let balance = self.get_balance(&wrapper.fee.token, &gas_payer); // In testnets with a faucet, tx is allowed to skip fees if // it includes a valid PoW @@ -1287,38 +1288,6 @@ where response } - /// Lookup a validator's keypair for their established account from their - /// wallet. If the node is not validator, this function returns None - #[allow(dead_code)] - fn get_account_keypair(&self) -> Option { - let wallet_path = &self.base_dir.join(self.chain_id.as_str()); - let genesis_path = &self - .base_dir - .join(format!("{}.toml", self.chain_id.as_str())); - let mut wallet = crate::wallet::load_or_new_from_genesis( - wallet_path, - genesis::genesis_config::open_genesis_config(genesis_path).unwrap(), - ); - self.mode.get_validator_address().map(|addr| { - let sk: common::SecretKey = self - .wl_storage - .read(&pk_key(addr)) - .expect( - "A validator should have a public key associated with \ - it's established account", - ) - .expect( - "A validator should have a public key associated with \ - it's established account", - ); - let pk = sk.ref_to(); - wallet.find_key_by_pk(&pk, None).expect( - "A validator's established keypair should be stored in its \ - wallet", - ) - }) - } - #[cfg(not(feature = "mainnet"))] /// Check if the tx has a valid PoW solution. Unlike /// `apply_pow_solution_if_valid`, this won't invalidate the solution. @@ -1922,17 +1891,18 @@ mod test_utils { .expect("begin_block failed"); let keypair = gen_keypair(); // enqueue a wrapper tx - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Default::default(), - token: native_token, - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Default::default(), + token: native_token, + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -2118,10 +2088,11 @@ mod abciplus_mempool_tests { ext }; let tx = { - let mut tx = Tx::new(TxType::Protocol(Box::new(ProtocolTx { - pk: protocol_key.ref_to(), - tx: ProtocolTxType::BridgePoolVext, - }))); + let mut tx = + Tx::from_type(TxType::Protocol(Box::new(ProtocolTx { + pk: protocol_key.ref_to(), + tx: ProtocolTxType::BridgePoolVext, + }))); // invalid tx type, it doesn't match the // tx type declared in the header tx.set_data(Data::new(ext.try_to_vec().expect("Test falied"))); @@ -2153,7 +2124,7 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); let mut unsigned_wrapper = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: token::Amount::from_uint(100, 0) .expect("This can't fail"), @@ -2190,7 +2161,7 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); let mut invalid_wrapper = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: token::Amount::from_uint(100, 0) .expect("This can't fail"), @@ -2234,10 +2205,8 @@ mod test_mempool_validate { fn test_wrong_tx_type() { let (shell, _recv, _, _) = test_utils::setup(); - // Test Raw TxType - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = shell.chain_id.clone(); - tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + let mut tx = Tx::new(shell.chain_id.clone(), None); + tx.add_code("wasm_code".as_bytes().to_owned()); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -2259,18 +2228,19 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: token::Amount::from_uint(100, 0) - .expect("This can't fail"), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: token::Amount::from_uint(100, 0) + .expect("This can't fail"), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -2366,18 +2336,10 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wrong_chain_id.clone(); - 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( - vec![ - tx.header_hash(), - tx.sections[0].get_hash(), - tx.sections[1].get_hash(), - ], - &keypair, - ))); + let mut tx = Tx::new(wrong_chain_id.clone(), None); + tx.add_code("wasm_code".as_bytes().to_owned()) + .add_data("transaction data".as_bytes().to_owned()) + .sign_wrapper(keypair); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -2401,19 +2363,11 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let mut tx = Tx::new(TxType::Raw); - tx.header.expiration = Some(DateTimeUtc::default()); - tx.header.chain_id = shell.chain_id.clone(); - 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( - vec![ - tx.header_hash(), - tx.sections[0].get_hash(), - tx.sections[1].get_hash(), - ], - &keypair, - ))); + let mut tx = + Tx::new(shell.chain_id.clone(), Some(DateTimeUtc::default())); + tx.add_code("wasm_code".as_bytes().to_owned()) + .add_data("transaction data".as_bytes().to_owned()) + .sign_wrapper(keypair); let result = shell.mempool_validate( tx.to_bytes().as_ref(), diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index e1e94be61e..4c9356f44a 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -217,7 +217,7 @@ where let mut tx = tx.clone(); match tx.decrypt(privkey).ok() { - Some(()) => { + Some(_) => { tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: *has_valid_pow, @@ -543,7 +543,7 @@ mod test_prepare_proposal { #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { let (shell, _recv, _, _) = test_utils::setup(); - let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + let mut tx = Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted { has_valid_pow: true, })); tx.header.chain_id = shell.chain_id.clone(); @@ -562,17 +562,18 @@ mod test_prepare_proposal { let (shell, _recv, _, _) = test_utils::setup(); let keypair = gen_keypair(); // an unsigned wrapper will cause an error in processing - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Default::default(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Default::default(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -938,17 +939,18 @@ mod test_prepare_proposal { // create a request with two new wrappers from mempool and // two wrappers from the previous block to be decrypted for i in 0..2 { - let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Default::default(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut tx = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Default::default(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); tx.header.chain_id = shell.chain_id.clone(); tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); tx.set_data(Data::new( @@ -1006,17 +1008,18 @@ mod test_prepare_proposal { let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -1057,17 +1060,18 @@ mod test_prepare_proposal { let (shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -1097,17 +1101,18 @@ mod test_prepare_proposal { let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -1149,17 +1154,18 @@ mod test_prepare_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); let keypair_2 = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); wrapper.header.chain_id = shell.chain_id.clone(); let tx_code = Code::new("wasm_code".as_bytes().to_owned()); wrapper.set_code(tx_code.clone()); @@ -1171,7 +1177,7 @@ mod test_prepare_proposal { ))); let mut new_wrapper = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1212,7 +1218,7 @@ mod test_prepare_proposal { let keypair = gen_keypair(); let tx_time = DateTimeUtc::now(); let mut wrapper_tx = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ce4f71a6f5..5010a4f211 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -841,14 +841,14 @@ where // transaction key, then the fee payer is effectively // the MASP, otherwise derive // the payer from public key. - let fee_payer = if wrapper.pk != masp_tx_key().ref_to() { - wrapper.fee_payer() + let gas_payer = if wrapper.pk != masp_tx_key().ref_to() { + wrapper.gas_payer() } else { masp() }; // check that the fee payer has sufficient balance let balance = - self.get_balance(&wrapper.fee.token, &fee_payer); + self.get_balance(&wrapper.fee.token, &gas_payer); // In testnets, tx is allowed to skip fees if it // includes a valid PoW @@ -1537,20 +1537,23 @@ mod test_process_proposal { fn test_unsigned_wrapper_rejected() { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); - let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Default::default(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let public_key = keypair.ref_to(); + let mut outer_tx = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Default::default(), + token: shell.wl_storage.storage.native_token.clone(), + }, + public_key, + Epoch(0), + 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())); + let tx = outer_tx.to_bytes(); let response = { @@ -1568,12 +1571,14 @@ mod test_process_proposal { } }; + println!("{}", response.result.info); + assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); assert_eq!( response.result.info, String::from( - "WrapperTx signature verification failed: Transaction doesn't \ - have any data with a signature." + "WrapperTx signature verification failed: The wrapper \ + signature is invalid." ) ); } @@ -1584,17 +1589,18 @@ mod test_process_proposal { fn test_wrapper_bad_signature_rejected() { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); - let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::from_uint(100, 0).expect("Test failed"), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut outer_tx = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::from_uint(100, 0).expect("Test failed"), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -1622,8 +1628,8 @@ mod test_process_proposal { panic!("Test failed") }; let expected_error = "WrapperTx signature verification \ - failed: Transaction doesn't have any \ - data with a signature."; + failed: The wrapper signature is \ + invalid."; assert_eq!( response.result.code, u32::from(ErrorCodes::InvalidSig) @@ -1652,17 +1658,18 @@ mod test_process_proposal { ) .unwrap(); let keypair = gen_keypair(); - let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::from_uint(1, 0).expect("Test failed"), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut outer_tx = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::from_uint(1, 0).expect("Test failed"), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -1721,17 +1728,18 @@ mod test_process_proposal { ) .unwrap(); - let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::native_whole(1_000_100), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut outer_tx = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::native_whole(1_000_100), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -1774,7 +1782,7 @@ mod test_process_proposal { let mut txs = vec![]; for i in 0..3 { let mut outer_tx = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Amount::native_whole(i as u64), token: shell.wl_storage.storage.native_token.clone(), @@ -1852,7 +1860,7 @@ mod test_process_proposal { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); - let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), @@ -1904,7 +1912,7 @@ mod test_process_proposal { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); let keypair = crate::wallet::defaults::daewon_keypair(); - let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), @@ -1961,7 +1969,7 @@ mod test_process_proposal { pow_solution: None, }; - let tx = Tx::new(TxType::Wrapper(Box::new(wrapper))); + let tx = Tx::from_type(TxType::Wrapper(Box::new(wrapper))); let mut decrypted = tx.clone(); decrypted.update_header(TxType::Decrypted(DecryptedTx::Undecryptable)); @@ -1989,7 +1997,7 @@ mod test_process_proposal { #[test] fn test_too_many_decrypted_txs() { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); - let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + let mut tx = Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: false, })); @@ -2023,10 +2031,12 @@ mod test_process_proposal { fn test_raw_tx_rejected() { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = shell.chain_id.clone(); - tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); - tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let mut tx = Tx::new(shell.chain_id.clone(), None); + tx.add_code("wasm_code".as_bytes().to_owned()) + .add_data("transaction data".as_bytes().to_owned()) + .sign_wrapper(keypair); let response = { let request = ProcessProposal { @@ -2062,17 +2072,18 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -2136,17 +2147,18 @@ mod test_process_proposal { ) .unwrap(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -2193,17 +2205,18 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -2282,17 +2295,18 @@ mod test_process_proposal { ) .unwrap(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); @@ -2351,17 +2365,18 @@ mod test_process_proposal { let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); let wrong_chain_id = ChainId("Wrong chain id".to_string()); wrapper.header.chain_id = wrong_chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); @@ -2413,17 +2428,18 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: token::Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: token::Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); wrapper.header.chain_id = wrong_chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper @@ -2473,17 +2489,18 @@ mod test_process_proposal { let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: token::Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: token::Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.header.expiration = Some(DateTimeUtc::default()); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); @@ -2515,17 +2532,18 @@ mod test_process_proposal { let (mut shell, _recv, _, _) = test_utils::setup(); let keypair = crate::wallet::defaults::daewon_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: token::Amount::zero(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: token::Amount::zero(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + Default::default(), + #[cfg(not(feature = "mainnet"))] + None, + )))); wrapper.header.chain_id = shell.chain_id.clone(); wrapper.header.expiration = Some(DateTimeUtc::default()); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); @@ -2571,17 +2589,18 @@ mod test_process_proposal { fn test_include_only_protocol_txs() { let (mut shell, _recv, _, _) = test_utils::setup_at_height(1u64); let keypair = gen_keypair(); - let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut wrapper = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + keypair.ref_to(), + Epoch(0), + 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())); diff --git a/apps/src/lib/node/ledger/shell/utils.rs b/apps/src/lib/node/ledger/shell/utils.rs new file mode 100644 index 0000000000..e55f54e8f8 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/utils.rs @@ -0,0 +1,14 @@ +use borsh::BorshDeserialize; +use namada::ledger::storage_api::{self, StorageRead}; +use namada::types::storage::Key; + +pub(super) fn force_read(storage: &S, key: &Key) -> storage_api::Result +where + S: StorageRead, + T: BorshDeserialize, +{ + storage + .read::(key) + .transpose() + .expect("Storage key must be present.") +} diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 81ca184f16..8ab728c96f 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -8,7 +8,7 @@ pub use dev::{ validator_keys, }; use namada::ledger::wallet::alias::Alias; -use namada::ledger::{eth_bridge, governance, pos}; +use namada::ledger::{eth_bridge, governance, pgf, pos}; use namada::types::address::Address; use namada::types::key::*; @@ -22,6 +22,7 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), ("governance".into(), governance::ADDRESS), ("eth_bridge".into(), eth_bridge::ADDRESS), + ("pgf".into(), pgf::ADDRESS), ]; // Genesis validators let validator_addresses = @@ -75,7 +76,7 @@ mod dev { use borsh::BorshDeserialize; use namada::ledger::wallet::alias::Alias; - use namada::ledger::{governance, pos}; + use namada::ledger::{governance, pgf, pos}; use namada::types::address::{ apfel, btc, dot, eth, kartoffel, nam, schnitzel, Address, }; @@ -146,6 +147,7 @@ mod dev { ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), ("governance".into(), governance::ADDRESS), + ("governance".into(), pgf::ADDRESS), ("validator".into(), validator_address()), ("albert".into(), albert_address()), ("bertha".into(), bertha_address()), diff --git a/core/src/ledger/governance/cli/mod.rs b/core/src/ledger/governance/cli/mod.rs new file mode 100644 index 0000000000..45b839d1f4 --- /dev/null +++ b/core/src/ledger/governance/cli/mod.rs @@ -0,0 +1,6 @@ +/// CLi governance offline structures +pub mod offline; +/// CLi governance on chain structures +pub mod onchain; +/// CLi governance validation +mod validation; diff --git a/core/src/ledger/governance/cli/offline.rs b/core/src/ledger/governance/cli/offline.rs new file mode 100644 index 0000000000..e63ac2ff49 --- /dev/null +++ b/core/src/ledger/governance/cli/offline.rs @@ -0,0 +1,389 @@ +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::{File, ReadDir}; +use std::path::PathBuf; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use super::onchain::ProposalVote; +use super::validation::{is_valid_tally_epoch, ProposalValidation}; +use crate::proto::SignatureIndex; +use crate::types::account::AccountPublicKeysMap; +use crate::types::address::Address; +use crate::types::hash::Hash; +use crate::types::key::{common, RefTo, SigScheme}; +use crate::types::storage::Epoch; + +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +/// The offline proposal structure +pub struct OfflineProposal { + /// The proposal content + pub content: BTreeMap, + /// The proposal author address + pub author: Address, + /// The epoch from which this changes are executed + pub tally_epoch: Epoch, +} + +impl OfflineProposal { + /// Validate the offline proposal + pub fn validate( + self, + current_epoch: Epoch, + ) -> Result { + is_valid_tally_epoch(self.tally_epoch, current_epoch)?; + + Ok(self) + } + + /// Hash an offline proposal + pub fn hash(&self) -> Hash { + let content_serialized = serde_json::to_vec(&self.content) + .expect("Conversion to bytes shouldn't fail."); + let author_serialized = serde_json::to_vec(&self.author) + .expect("Conversion to bytes shouldn't fail."); + let tally_epoch_serialized = serde_json::to_vec(&self.tally_epoch) + .expect("Conversion to bytes shouldn't fail."); + let proposal_serialized = &[ + content_serialized, + author_serialized, + tally_epoch_serialized, + ] + .concat(); + Hash::sha256(proposal_serialized) + } + + /// Sign an offline proposal + pub fn sign( + self, + signing_keys: Vec, + account_public_keys_map: &AccountPublicKeysMap, + ) -> OfflineSignedProposal { + let proposal_hash = self.hash(); + + let signatures_index = compute_signatures_index( + &signing_keys, + account_public_keys_map, + &proposal_hash, + ); + + OfflineSignedProposal { + proposal: self, + signatures: signatures_index, + } + } +} + +impl TryFrom<&[u8]> for OfflineProposal { + type Error = serde_json::Error; + + fn try_from(value: &[u8]) -> Result { + serde_json::from_slice(value) + } +} + +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +/// The signed offline proposal structure +pub struct OfflineSignedProposal { + /// The proposal content + pub proposal: OfflineProposal, + /// The signatures over proposal data + pub signatures: BTreeSet, +} + +impl TryFrom<&[u8]> for OfflineSignedProposal { + type Error = serde_json::Error; + + fn try_from(value: &[u8]) -> Result { + serde_json::from_slice(value) + } +} + +impl OfflineSignedProposal { + /// Serialize the proposal to file. Returns the filename if successful. + pub fn serialize( + &self, + output_folder: Option, + ) -> Result { + let proposal_filename = + format!("offline_proposal_{}.json", self.proposal.hash()); + + let filepath = match output_folder { + Some(base_path) => base_path + .join(proposal_filename) + .to_str() + .unwrap() + .to_owned(), + None => proposal_filename, + }; + + let out = + File::create(&filepath).expect("Should be able to create a file."); + serde_json::to_writer_pretty(out, self)?; + + Ok(filepath) + } + + /// Check whether the signature is valid or not + fn check_signature( + &self, + account_public_keys_map: &AccountPublicKeysMap, + threshold: u8, + ) -> bool { + let proposal_hash = self.proposal.hash(); + if self.signatures.len() < threshold as usize { + return false; + } + + let valid_signatures = compute_total_valid_signatures( + &self.signatures, + account_public_keys_map, + &proposal_hash, + ); + + valid_signatures >= threshold + } + + /// Validate an offline proposal + pub fn validate( + self, + account_public_keys_map: &AccountPublicKeysMap, + threshold: u8, + ) -> Result { + let valid_signature = + self.check_signature(account_public_keys_map, threshold); + if !valid_signature { + Err(ProposalValidation::OkNoSignature) + } else { + Ok(self) + } + } +} + +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +/// The offline proposal structure +pub struct OfflineVote { + /// The proposal data hash + pub proposal_hash: Hash, + /// The proposal vote + pub vote: ProposalVote, + /// The signature over proposal data + pub signatures: BTreeSet, + /// The address corresponding to the signature pk + pub address: Address, + /// The validators address to which this address delegated to + pub delegations: Vec
, +} + +impl OfflineVote { + /// Create an offline vote for a proposal + pub fn new( + proposal: &OfflineSignedProposal, + vote: ProposalVote, + address: Address, + delegations: Vec
, + ) -> Self { + let proposal_hash = proposal.proposal.hash(); + + Self { + proposal_hash, + vote, + delegations, + signatures: BTreeSet::default(), + address, + } + } + + /// Sign the offline vote + pub fn sign( + self, + keypairs: Vec, + account_public_keys_map: &AccountPublicKeysMap, + ) -> Self { + let proposal_vote_data = self + .vote + .try_to_vec() + .expect("Conversion to bytes shouldn't fail."); + let delegations_hash = self + .delegations + .try_to_vec() + .expect("Conversion to bytes shouldn't fail."); + + let vote_hash = Hash::sha256( + [ + self.proposal_hash.to_vec(), + proposal_vote_data, + delegations_hash, + ] + .concat(), + ); + + let signatures = compute_signatures_index( + &keypairs, + account_public_keys_map, + &vote_hash, + ); + + Self { signatures, ..self } + } + + /// Check if the vote is yay + pub fn is_yay(&self) -> bool { + self.vote.is_yay() + } + + /// compute the hash of a proposal + pub fn compute_hash(&self) -> Hash { + let proposal_hash_data = self + .proposal_hash + .try_to_vec() + .expect("Conversion to bytes shouldn't fail."); + let proposal_vote_data = self + .vote + .try_to_vec() + .expect("Conversion to bytes shouldn't fail."); + let delegations_hash = self + .delegations + .try_to_vec() + .expect("Conversion to bytes shouldn't fail."); + let vote_serialized = + &[proposal_hash_data, proposal_vote_data, delegations_hash] + .concat(); + + Hash::sha256(vote_serialized) + } + + /// Check whether the signature is valid or not + pub fn check_signature( + &self, + account_public_keys_map: &AccountPublicKeysMap, + threshold: u8, + ) -> bool { + if self.signatures.len() < threshold as usize { + return false; + } + let vote_data_hash = self.compute_hash(); + + let valid_signatures = compute_total_valid_signatures( + &self.signatures, + account_public_keys_map, + &vote_data_hash, + ); + + valid_signatures >= threshold + } + + /// Serialize the proposal to file. Returns the filename if successful. + pub fn serialize( + &self, + output_folder: Option, + ) -> Result { + let vote_filename = format!( + "offline_vote_{}_{}.json", + self.proposal_hash, self.address + ); + let filepath = match output_folder { + Some(base_path) => { + base_path.join(vote_filename).to_str().unwrap().to_owned() + } + None => vote_filename, + }; + let out = File::create(&filepath).unwrap(); + serde_json::to_writer_pretty(out, self)?; + + Ok(filepath) + } +} + +/// Compute the signatures index +fn compute_signatures_index( + keys: &[common::SecretKey], + account_public_keys_map: &AccountPublicKeysMap, + hashed_data: &Hash, +) -> BTreeSet { + keys.iter() + .filter_map(|signing_key| { + let public_key = signing_key.ref_to(); + let public_key_index = + account_public_keys_map.get_index_from_public_key(&public_key); + if public_key_index.is_some() { + let signature = + common::SigScheme::sign(signing_key, hashed_data); + Some(SignatureIndex::from_single_signature(signature)) + } else { + None + } + }) + .collect::>() +} + +/// Compute the total amount of signatures +fn compute_total_valid_signatures( + signatures: &BTreeSet, + account_public_keys_map: &AccountPublicKeysMap, + hashed_data: &Hash, +) -> u8 { + signatures.iter().fold(0_u8, |acc, signature_index| { + let public_key = account_public_keys_map + .get_public_key_from_index(signature_index.index); + if let Some(pk) = public_key { + let sig_check = common::SigScheme::verify_signature( + &pk, + hashed_data, + &signature_index.signature, + ); + if sig_check.is_ok() { acc + 1 } else { acc } + } else { + acc + } + }) +} + +/// Read all offline files from a folder +pub fn read_offline_files(path: ReadDir) -> Vec { + path.filter_map(|path| { + if let Ok(path) = path { + let file_type = path.file_type(); + if let Ok(file_type) = file_type { + if file_type.is_file() + && path.file_name().to_string_lossy().contains("offline_") + { + Some(path.path()) + } else { + None + } + } else { + None + } + } else { + None + } + }) + .collect::>() +} + +/// Find offline votes from a folder +pub fn find_offline_proposal(files: &[PathBuf]) -> Option { + files + .iter() + .filter(|path| path.to_string_lossy().contains("offline_proposal_")) + .cloned() + .collect::>() + .first() + .cloned() +} + +/// Find offline votes from a folder +pub fn find_offline_votes(files: &[PathBuf]) -> Vec { + files + .iter() + .filter(|path| path.to_string_lossy().contains("offline_vote_")) + .cloned() + .collect::>() +} diff --git a/core/src/ledger/governance/cli/onchain.rs b/core/src/ledger/governance/cli/onchain.rs new file mode 100644 index 0000000000..33cabc6156 --- /dev/null +++ b/core/src/ledger/governance/cli/onchain.rs @@ -0,0 +1,326 @@ +use std::collections::BTreeMap; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use super::validation::{ + is_valid_author_balance, is_valid_content, is_valid_default_proposal_data, + is_valid_end_epoch, is_valid_grace_epoch, is_valid_pgf_funding_data, + is_valid_pgf_stewards_data, is_valid_proposal_period, is_valid_start_epoch, + ProposalValidation, +}; +use crate::ledger::governance::parameters::GovernanceParameters; +use crate::ledger::storage_api::token; +use crate::types::address::Address; +use crate::types::storage::Epoch; + +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +/// The proposal structure +pub struct OnChainProposal { + /// The proposal id + pub id: Option, + /// The proposal content + pub content: BTreeMap, + /// The proposal author address + pub author: Address, + /// The epoch from which voting is allowed + pub voting_start_epoch: Epoch, + /// The epoch from which voting is stopped + pub voting_end_epoch: Epoch, + /// The epoch from which this changes are executed + pub grace_epoch: Epoch, +} + +/// Pgf default proposal +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct DefaultProposal { + /// The proposal data + pub proposal: OnChainProposal, + /// The default proposal extra data + pub data: Option>, +} + +impl DefaultProposal { + /// Validate a default funding proposal + pub fn validate( + self, + governance_parameters: &GovernanceParameters, + current_epoch: Epoch, + balance: token::Amount, + ) -> Result { + is_valid_start_epoch( + self.proposal.voting_start_epoch, + current_epoch, + governance_parameters.min_proposal_voting_period, + )?; + is_valid_end_epoch( + self.proposal.voting_start_epoch, + self.proposal.voting_end_epoch, + current_epoch, + governance_parameters.min_proposal_voting_period, + governance_parameters.min_proposal_voting_period, + governance_parameters.max_proposal_period, + )?; + is_valid_grace_epoch( + self.proposal.grace_epoch, + self.proposal.voting_end_epoch, + governance_parameters.min_proposal_grace_epochs, + )?; + is_valid_proposal_period( + self.proposal.voting_start_epoch, + self.proposal.grace_epoch, + governance_parameters.max_proposal_period, + )?; + is_valid_author_balance( + balance, + governance_parameters.min_proposal_fund, + )?; + is_valid_content( + &self.proposal.content, + governance_parameters.max_proposal_content_size, + )?; + is_valid_default_proposal_data( + &self.data, + governance_parameters.max_proposal_code_size, + )?; + + Ok(self) + } +} + +impl TryFrom<&[u8]> for DefaultProposal { + type Error = serde_json::Error; + + fn try_from(value: &[u8]) -> Result { + serde_json::from_slice(value) + } +} + +/// Pgf stewards proposal +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct PgfStewardProposal { + /// The proposal data + pub proposal: OnChainProposal, + /// The Pgf steward proposal extra data + pub data: Vec, +} + +impl PgfStewardProposal { + /// Validate a Pgf stewards proposal + pub fn validate( + self, + governance_parameters: &GovernanceParameters, + current_epoch: Epoch, + balance: token::Amount, + ) -> Result { + is_valid_start_epoch( + self.proposal.voting_start_epoch, + current_epoch, + governance_parameters.min_proposal_voting_period, + )?; + is_valid_end_epoch( + self.proposal.voting_start_epoch, + self.proposal.voting_end_epoch, + current_epoch, + governance_parameters.min_proposal_voting_period, + governance_parameters.min_proposal_voting_period, + governance_parameters.max_proposal_period, + )?; + is_valid_grace_epoch( + self.proposal.grace_epoch, + self.proposal.voting_end_epoch, + governance_parameters.min_proposal_grace_epochs, + )?; + is_valid_proposal_period( + self.proposal.voting_start_epoch, + self.proposal.grace_epoch, + governance_parameters.max_proposal_period, + )?; + is_valid_author_balance( + balance, + governance_parameters.min_proposal_fund, + )?; + is_valid_content( + &self.proposal.content, + governance_parameters.max_proposal_content_size, + )?; + is_valid_pgf_stewards_data(&self.data)?; + + Ok(self) + } +} + +impl TryFrom<&[u8]> for PgfStewardProposal { + type Error = serde_json::Error; + + fn try_from(value: &[u8]) -> Result { + serde_json::from_slice(value) + } +} + +/// Pgf funding proposal +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct PgfFundingProposal { + /// The proposal data + pub proposal: OnChainProposal, + /// The Pgf funding proposal extra data + pub data: PgfFunding, +} + +impl PgfFundingProposal { + /// Validate a Pgf funding proposal + pub fn validate( + self, + governance_parameters: &GovernanceParameters, + current_epoch: Epoch, + ) -> Result { + is_valid_start_epoch( + self.proposal.voting_start_epoch, + current_epoch, + governance_parameters.min_proposal_voting_period, + )?; + is_valid_end_epoch( + self.proposal.voting_start_epoch, + self.proposal.voting_end_epoch, + current_epoch, + governance_parameters.min_proposal_voting_period, + governance_parameters.min_proposal_voting_period, + governance_parameters.max_proposal_period, + )?; + is_valid_grace_epoch( + self.proposal.grace_epoch, + self.proposal.voting_end_epoch, + governance_parameters.min_proposal_grace_epochs, + )?; + is_valid_proposal_period( + self.proposal.voting_start_epoch, + self.proposal.grace_epoch, + governance_parameters.max_proposal_period, + )?; + is_valid_content( + &self.proposal.content, + governance_parameters.max_proposal_content_size, + )?; + is_valid_pgf_funding_data(&self.data)?; + + Ok(self) + } +} + +impl TryFrom<&[u8]> for PgfFundingProposal { + type Error = serde_json::Error; + + fn try_from(value: &[u8]) -> Result { + serde_json::from_slice(value) + } +} + +/// Pgf stewards +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct PgfSteward { + /// Pgf action + pub action: PgfAction, + /// steward address + pub address: Address, +} + +/// Pgf action +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub enum PgfAction { + /// Add action + Add, + /// Remove action + Remove, +} + +/// Pgf fundings +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct PgfFunding { + /// Pgf continous funding + pub continous: Vec, + /// pgf retro fundings + pub retro: Vec, +} + +/// Pgf continous funding +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct PgfContinous { + /// Pgf target + pub target: PgfFundingTarget, + /// Pgf action + pub action: PgfAction, +} + +/// Pgf retro funding +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct PgfRetro { + /// Pgf retro target + pub target: PgfFundingTarget, +} + +/// Pgf Target +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +pub struct PgfFundingTarget { + /// Target amount + pub amount: token::Amount, + /// Target address + pub address: Address, +} + +/// Rappresent an proposal vote +#[derive( + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, + PartialEq, +)] +pub enum ProposalVote { + /// Rappresent an yay proposal vote + Yay, + /// Rappresent an nay proposal vote + Nay, + /// Rappresent an invalid proposal vote + Invalid, +} + +impl TryFrom for ProposalVote { + type Error = String; + + fn try_from(value: String) -> Result { + match value.trim().to_lowercase().as_str() { + "yay" => Ok(ProposalVote::Yay), + "nay" => Ok(ProposalVote::Nay), + _ => Err("invalid vote".to_string()), + } + } +} + +impl ProposalVote { + /// Check if the proposal type is yay + pub fn is_yay(&self) -> bool { + matches!(self, ProposalVote::Yay) + } +} diff --git a/core/src/ledger/governance/cli/utils.rs b/core/src/ledger/governance/cli/utils.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/src/ledger/governance/cli/validation.rs b/core/src/ledger/governance/cli/validation.rs new file mode 100644 index 0000000000..585a3362ad --- /dev/null +++ b/core/src/ledger/governance/cli/validation.rs @@ -0,0 +1,241 @@ +use std::collections::BTreeMap; + +use thiserror::Error; + +use super::onchain::{PgfFunding, PgfSteward}; +use crate::types::storage::Epoch; +use crate::types::token; + +/// This enum raprresent a proposal data +#[derive(Clone, Debug, PartialEq, Error)] +pub enum ProposalValidation { + /// The proposal field are correct but there is no signature + #[error("The proposal is not signed. Can't vote on it")] + OkNoSignature, + /// The proposal start epoch is invalid + #[error( + "Invalid proposal start epoch: {0} must be greater than current epoch \ + {1} and a multiple of {2}" + )] + InvalidStartEpoch(Epoch, Epoch, u64), + /// The proposal difference between start and end epoch is invalid + #[error( + "Invalid proposal end epoch: difference between proposal start and \ + end epoch must be at least {0}, at max {1} and the end epoch must be \ + a multiple of {0}" + )] + InvalidStartEndDifference(u64, u64), + /// The proposal difference between end and grace epoch is invalid + #[error( + "Invalid proposal grace epoch: difference between proposal grace and \ + end epoch must be at least {0}, but found {1}" + )] + InvalidEndGraceDifference(u64, u64), + /// The proposal difference between end and grace epoch is invalid + #[error( + "Invalid proposal period: difference between proposal start and grace \ + epoch must be at most {1}, but found {0}" + )] + InvalidProposalPeriod(u64, u64), + /// The proposal author does not have enought balance to pay for proposal + /// fees + #[error( + "Invalid proposal minimum funds: the author address has {0} but \ + minimum is {1}" + )] + InvalidBalance(String, String), + /// The proposal content is too large + #[error( + "Invalid proposal content length: the proposal content length is {0} \ + but maximum is {1}" + )] + InvalidContentLength(u64, u64), + /// Invalid offline proposal tally epoch + #[error( + "Invalid proposal tally epoch: tally epoch ({0}) must be less than \ + current epoch ({1})" + )] + InvalidTallyEPoch(Epoch, Epoch), + /// The proposal wasm code is not valid + #[error( + "Invalid proposal extra data: file doesn't exist or content size \ + ({0}) is to big (max {1})" + )] + InvalidDefaultProposalExtraData(u64, u64), + /// The pgf stewards data is not valid + #[error("Invalid proposal extra data: cannot be empty.")] + InvalidPgfStewardsExtraData, + /// The pgf funding data is not valid + #[error("invalid proposal extra data: cannot be empty.")] + InvalidPgfFundingExtraData, +} + +pub fn is_valid_author_balance( + author_balance: token::Amount, + min_proposal_fund: token::Amount, +) -> Result<(), ProposalValidation> { + if author_balance.can_spend(&min_proposal_fund) { + Ok(()) + } else { + Err(ProposalValidation::InvalidBalance( + author_balance.to_string_native(), + min_proposal_fund.to_string_native(), + )) + } +} + +pub fn is_valid_start_epoch( + proposal_start_epoch: Epoch, + current_epoch: Epoch, + proposal_epoch_multiplier: u64, +) -> Result<(), ProposalValidation> { + let start_epoch_greater_than_current = proposal_start_epoch > current_epoch; + let start_epoch_is_multipler = + proposal_start_epoch.0 % proposal_epoch_multiplier == 0; + + if start_epoch_greater_than_current && start_epoch_is_multipler { + Ok(()) + } else { + Err(ProposalValidation::InvalidStartEpoch( + proposal_start_epoch, + current_epoch, + proposal_epoch_multiplier, + )) + } +} + +pub fn is_valid_end_epoch( + proposal_start_epoch: Epoch, + proposal_end_epoch: Epoch, + _current_epoch: Epoch, + proposal_epoch_multiplier: u64, + min_proposal_voting_period: u64, + max_proposal_period: u64, +) -> Result<(), ProposalValidation> { + let voting_period = proposal_end_epoch.0 - proposal_start_epoch.0; + let end_epoch_is_multipler = + proposal_end_epoch % proposal_epoch_multiplier == 0; + let is_valid_voting_period = voting_period > 0 + && voting_period >= min_proposal_voting_period + && min_proposal_voting_period <= max_proposal_period; + + if end_epoch_is_multipler && is_valid_voting_period { + Ok(()) + } else { + Err(ProposalValidation::InvalidStartEndDifference( + min_proposal_voting_period, + max_proposal_period, + )) + } +} + +pub fn is_valid_grace_epoch( + proposal_grace_epoch: Epoch, + proposal_end_epoch: Epoch, + min_proposal_grace_epoch: u64, +) -> Result<(), ProposalValidation> { + let grace_period = proposal_grace_epoch.0 - proposal_end_epoch.0; + + if grace_period > 0 && grace_period >= min_proposal_grace_epoch { + Ok(()) + } else { + Err(ProposalValidation::InvalidEndGraceDifference( + grace_period, + min_proposal_grace_epoch, + )) + } +} + +pub fn is_valid_proposal_period( + proposal_start_epoch: Epoch, + proposal_grace_epoch: Epoch, + max_proposal_period: u64, +) -> Result<(), ProposalValidation> { + let proposal_period = proposal_grace_epoch.0 - proposal_start_epoch.0; + + if proposal_period > 0 && proposal_period <= max_proposal_period { + Ok(()) + } else { + Err(ProposalValidation::InvalidProposalPeriod( + proposal_period, + max_proposal_period, + )) + } +} + +pub fn is_valid_content( + proposal_content: &BTreeMap, + max_content_length: u64, +) -> Result<(), ProposalValidation> { + let proposal_content_keys_length: u64 = + proposal_content.keys().map(|key| key.len() as u64).sum(); + let proposal_content_values_length: u64 = proposal_content + .values() + .map(|value| value.len() as u64) + .sum(); + let proposal_content_length = + proposal_content_values_length + proposal_content_keys_length; + + if proposal_content_length <= max_content_length { + Ok(()) + } else { + Err(ProposalValidation::InvalidContentLength( + proposal_content_length, + max_content_length, + )) + } +} + +pub fn is_valid_tally_epoch( + tally_epoch: Epoch, + current_epoch: Epoch, +) -> Result<(), ProposalValidation> { + if tally_epoch <= current_epoch { + Ok(()) + } else { + Err(ProposalValidation::InvalidTallyEPoch( + tally_epoch, + current_epoch, + )) + } +} + +pub fn is_valid_default_proposal_data( + data: &Option>, + max_extra_data_size: u64, +) -> Result<(), ProposalValidation> { + match data { + Some(content) => { + let extra_data_length = content.len() as u64; + if extra_data_length <= max_extra_data_size { + Ok(()) + } else { + Err(ProposalValidation::InvalidDefaultProposalExtraData( + extra_data_length, + max_extra_data_size, + )) + } + } + None => Ok(()), + } +} + +pub fn is_valid_pgf_stewards_data( + data: &Vec, +) -> Result<(), ProposalValidation> { + if !data.is_empty() { + Ok(()) + } else { + Err(ProposalValidation::InvalidPgfStewardsExtraData) + } +} + +pub fn is_valid_pgf_funding_data( + data: &PgfFunding, +) -> Result<(), ProposalValidation> { + if !data.continous.is_empty() || !data.retro.is_empty() { + Ok(()) + } else { + Err(ProposalValidation::InvalidPgfFundingExtraData) + } +} diff --git a/core/src/ledger/governance/mod.rs b/core/src/ledger/governance/mod.rs index ae488383bf..00fcb3a990 100644 --- a/core/src/ledger/governance/mod.rs +++ b/core/src/ledger/governance/mod.rs @@ -2,10 +2,14 @@ use crate::types::address::{self, Address}; +/// governance CLI structures +pub mod cli; /// governance parameters pub mod parameters; /// governance storage pub mod storage; +/// Governance utility functions/structs +pub mod utils; /// The governance internal address pub const ADDRESS: Address = address::GOV; diff --git a/core/src/ledger/governance/parameters.rs b/core/src/ledger/governance/parameters.rs index 2d247bc24f..ebb28372af 100644 --- a/core/src/ledger/governance/parameters.rs +++ b/core/src/ledger/governance/parameters.rs @@ -2,9 +2,9 @@ use std::fmt::Display; use borsh::{BorshDeserialize, BorshSerialize}; -use super::storage as gov_storage; +use super::storage::keys as goverance_storage; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; -use crate::types::token::Amount; +use crate::types::token; #[derive( Clone, @@ -18,13 +18,13 @@ use crate::types::token::Amount; BorshDeserialize, )] /// Governance parameter structure -pub struct GovParams { +pub struct GovernanceParameters { /// Minimum amount of locked funds - pub min_proposal_fund: u64, + pub min_proposal_fund: token::Amount, /// Maximum kibibyte length for proposal code pub max_proposal_code_size: u64, /// Minimum proposal voting period in epochs - pub min_proposal_period: u64, + pub min_proposal_voting_period: u64, /// Maximum proposal voting period in epochs pub max_proposal_period: u64, /// Maximum number of characters for proposal content @@ -33,16 +33,16 @@ pub struct GovParams { pub min_proposal_grace_epochs: u64, } -impl Display for GovParams { +impl Display for GovernanceParameters { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Min. proposal fund: {}\nMax. proposal code size: {}\nMin. \ proposal period: {}\nMax. proposal period: {}\nMax. proposal \ content size: {}\nMin. proposal grace epochs: {}", - self.min_proposal_fund, + self.min_proposal_fund.to_string_native(), self.max_proposal_code_size, - self.min_proposal_period, + self.min_proposal_voting_period, self.max_proposal_period, self.max_proposal_content_size, self.min_proposal_grace_epochs @@ -50,12 +50,12 @@ impl Display for GovParams { } } -impl Default for GovParams { +impl Default for GovernanceParameters { fn default() -> Self { Self { - min_proposal_fund: 500, + min_proposal_fund: token::Amount::native_whole(500), max_proposal_code_size: 300_000, - min_proposal_period: 3, + min_proposal_voting_period: 3, max_proposal_period: 27, max_proposal_content_size: 10_000, min_proposal_grace_epochs: 6, @@ -63,7 +63,7 @@ impl Default for GovParams { } } -impl GovParams { +impl GovernanceParameters { /// Initialize governance parameters into storage pub fn init_storage(&self, storage: &mut S) -> storage_api::Result<()> where @@ -72,39 +72,42 @@ impl GovParams { let Self { min_proposal_fund, max_proposal_code_size, - min_proposal_period, + min_proposal_voting_period, max_proposal_period, max_proposal_content_size, min_proposal_grace_epochs, } = self; - let min_proposal_fund_key = gov_storage::get_min_proposal_fund_key(); - let amount = Amount::native_whole(*min_proposal_fund); - storage.write(&min_proposal_fund_key, amount)?; + let min_proposal_fund_key = + goverance_storage::get_min_proposal_fund_key(); + storage.write(&min_proposal_fund_key, min_proposal_fund)?; let max_proposal_code_size_key = - gov_storage::get_max_proposal_code_size_key(); + goverance_storage::get_max_proposal_code_size_key(); storage.write(&max_proposal_code_size_key, max_proposal_code_size)?; - let min_proposal_period_key = - gov_storage::get_min_proposal_period_key(); - storage.write(&min_proposal_period_key, min_proposal_period)?; + let min_proposal_voting_period_key = + goverance_storage::get_min_proposal_voting_period_key(); + storage.write( + &min_proposal_voting_period_key, + min_proposal_voting_period, + )?; let max_proposal_period_key = - gov_storage::get_max_proposal_period_key(); + goverance_storage::get_max_proposal_period_key(); storage.write(&max_proposal_period_key, max_proposal_period)?; let max_proposal_content_size_key = - gov_storage::get_max_proposal_content_key(); + goverance_storage::get_max_proposal_content_key(); storage .write(&max_proposal_content_size_key, max_proposal_content_size)?; let min_proposal_grace_epoch_key = - gov_storage::get_min_proposal_grace_epoch_key(); + goverance_storage::get_min_proposal_grace_epoch_key(); storage .write(&min_proposal_grace_epoch_key, min_proposal_grace_epochs)?; - let counter_key = gov_storage::get_counter_key(); + let counter_key = goverance_storage::get_counter_key(); storage.write(&counter_key, u64::MIN) } } diff --git a/core/src/ledger/governance/storage.rs b/core/src/ledger/governance/storage/keys.rs similarity index 78% rename from core/src/ledger/governance/storage.rs rename to core/src/ledger/governance/storage/keys.rs index e00c4be678..a975b6541f 100644 --- a/core/src/ledger/governance/storage.rs +++ b/core/src/ledger/governance/storage/keys.rs @@ -1,27 +1,32 @@ +use namada_macros::StorageKeys; + use crate::ledger::governance::ADDRESS; use crate::types::address::Address; use crate::types::storage::{DbKeySeg, Key, KeySeg}; -const PROPOSAL_PREFIX: &str = "proposal"; -const PROPOSAL_VOTE: &str = "vote"; -const PROPOSAL_AUTHOR: &str = "author"; -const PROPOSAL_TYPE: &str = "type"; -const PROPOSAL_CONTENT: &str = "content"; -const PROPOSAL_START_EPOCH: &str = "start_epoch"; -const PROPOSAL_END_EPOCH: &str = "end_epoch"; -const PROPOSAL_GRACE_EPOCH: &str = "grace_epoch"; -const PROPOSAL_FUNDS: &str = "funds"; -const PROPOSAL_CODE: &str = "proposal_code"; -const PROPOSAL_COMMITTING_EPOCH: &str = "epoch"; - -const MIN_PROPOSAL_FUND_KEY: &str = "min_fund"; -const MAX_PROPOSAL_CODE_SIZE_KEY: &str = "max_code_size"; -const MIN_PROPOSAL_PERIOD_KEY: &str = "min_period"; -const MAX_PROPOSAL_PERIOD_KEY: &str = "max_period"; -const MAX_PROPOSAL_CONTENT_SIZE_KEY: &str = "max_content"; -const MIN_GRACE_EPOCH_KEY: &str = "min_grace_epoch"; -const COUNTER_KEY: &str = "counter"; -const PENDING_PROPOSAL: &str = "pending"; +/// Storage keys for governance internal address. +#[derive(StorageKeys)] +struct Keys { + proposal: &'static str, + vote: &'static str, + author: &'static str, + proposal_type: &'static str, + content: &'static str, + start_epoch: &'static str, + end_epoch: &'static str, + grace_epoch: &'static str, + funds: &'static str, + proposal_code: &'static str, + committing_epoch: &'static str, + min_fund: &'static str, + max_code_size: &'static str, + min_period: &'static str, + max_period: &'static str, + max_content: &'static str, + min_grace_epoch: &'static str, + counter: &'static str, + pending: &'static str, +} /// Check if key is inside governance address space pub fn is_governance_key(key: &Key) -> bool { @@ -39,8 +44,8 @@ pub fn is_vote_key(key: &Key) -> bool { DbKeySeg::AddressSeg(_validator_address), DbKeySeg::AddressSeg(_address), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && vote == PROPOSAL_VOTE => + && prefix == Keys::VALUES.proposal + && vote == Keys::VALUES.vote => { id.parse::().is_ok() } @@ -57,8 +62,8 @@ pub fn is_author_key(key: &Key) -> bool { DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(author), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && author == PROPOSAL_AUTHOR => + && prefix == Keys::VALUES.proposal + && author == Keys::VALUES.author => { id.parse::().is_ok() } @@ -75,8 +80,8 @@ pub fn is_proposal_code_key(key: &Key) -> bool { DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(proposal_code), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && proposal_code == PROPOSAL_CODE => + && prefix == Keys::VALUES.proposal + && proposal_code == Keys::VALUES.proposal_code => { id.parse::().is_ok() } @@ -93,8 +98,8 @@ pub fn is_grace_epoch_key(key: &Key) -> bool { DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(grace_epoch), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && grace_epoch == PROPOSAL_GRACE_EPOCH => + && prefix == Keys::VALUES.proposal + && grace_epoch == Keys::VALUES.grace_epoch => { id.parse::().is_ok() } @@ -111,8 +116,8 @@ pub fn is_content_key(key: &Key) -> bool { DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(content), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && content == PROPOSAL_CONTENT => + && prefix == Keys::VALUES.proposal + && content == Keys::VALUES.content => { id.parse::().is_ok() } @@ -129,8 +134,8 @@ pub fn is_balance_key(key: &Key) -> bool { DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(funds), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && funds == PROPOSAL_FUNDS => + && prefix == Keys::VALUES.proposal + && funds == Keys::VALUES.funds => { id.parse::().is_ok() } @@ -147,8 +152,8 @@ pub fn is_start_epoch_key(key: &Key) -> bool { DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(start_epoch), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && start_epoch == PROPOSAL_START_EPOCH => + && prefix == Keys::VALUES.proposal + && start_epoch == Keys::VALUES.start_epoch => { id.parse::().is_ok() } @@ -165,8 +170,8 @@ pub fn is_end_epoch_key(key: &Key) -> bool { DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(end_epoch), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && end_epoch == PROPOSAL_END_EPOCH => + && prefix == Keys::VALUES.proposal + && end_epoch == Keys::VALUES.end_epoch => { id.parse::().is_ok() } @@ -183,8 +188,8 @@ pub fn is_proposal_type_key(key: &Key) -> bool { DbKeySeg::StringSeg(id), DbKeySeg::StringSeg(proposal_type), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && proposal_type == PROPOSAL_TYPE => + && prefix == Keys::VALUES.proposal + && proposal_type == Keys::VALUES.proposal_type => { id.parse::().is_ok() } @@ -194,7 +199,7 @@ pub fn is_proposal_type_key(key: &Key) -> bool { /// Check if key is counter key pub fn is_counter_key(key: &Key) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(counter)] if addr == &ADDRESS && counter == COUNTER_KEY) + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(counter)] if addr == &ADDRESS && counter == Keys::VALUES.counter) } /// Check if key is a proposal fund parameter key @@ -202,7 +207,7 @@ pub fn is_min_proposal_fund_key(key: &Key) -> bool { matches!(&key.segments[..], [ DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(min_funds_param), - ] if addr == &ADDRESS && min_funds_param == MIN_PROPOSAL_FUND_KEY) + ] if addr == &ADDRESS && min_funds_param == Keys::VALUES.min_fund) } /// Check if key is a proposal max content parameter key @@ -211,7 +216,7 @@ pub fn is_max_content_size_key(key: &Key) -> bool { DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(max_content_size_param), ] if addr == &ADDRESS - && max_content_size_param == MAX_PROPOSAL_CONTENT_SIZE_KEY) + && max_content_size_param == Keys::VALUES.max_content) } /// Check if key is a max proposal size key @@ -220,16 +225,16 @@ pub fn is_max_proposal_code_size_key(key: &Key) -> bool { DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(max_content_size_param), ] if addr == &ADDRESS - && max_content_size_param == MAX_PROPOSAL_CONTENT_SIZE_KEY) + && max_content_size_param == Keys::VALUES.max_code_size) } /// Check if key is a min proposal period param key -pub fn is_min_proposal_period_key(key: &Key) -> bool { +pub fn is_min_proposal_voting_period_key(key: &Key) -> bool { matches!(&key.segments[..], [ DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(min_proposal_period_param), + DbKeySeg::StringSeg(min_proposal_voting_period_param), ] if addr == &ADDRESS - && min_proposal_period_param == MIN_PROPOSAL_PERIOD_KEY) + && min_proposal_voting_period_param == Keys::VALUES.min_period) } /// Check if key is a max proposal period param key @@ -238,7 +243,7 @@ pub fn is_max_proposal_period_key(key: &Key) -> bool { DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(max_proposal_period_param), ] if addr == &ADDRESS - && max_proposal_period_param == MAX_PROPOSAL_PERIOD_KEY) + && max_proposal_period_param == Keys::VALUES.max_period) } /// Check if key is a min grace epoch key @@ -250,8 +255,8 @@ pub fn is_commit_proposal_key(key: &Key) -> bool { DbKeySeg::StringSeg(_epoch), DbKeySeg::StringSeg(_id), ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && epoch_prefix == PROPOSAL_COMMITTING_EPOCH + && prefix == Keys::VALUES.proposal + && epoch_prefix == Keys::VALUES.committing_epoch ) } @@ -261,7 +266,7 @@ pub fn is_min_grace_epoch_key(key: &Key) -> bool { DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(min_grace_epoch_param), ] if addr == &ADDRESS - && min_grace_epoch_param == MIN_GRACE_EPOCH_KEY) + && min_grace_epoch_param == Keys::VALUES.min_grace_epoch) } /// Check if key is parameter key @@ -269,7 +274,7 @@ pub fn is_parameter_key(key: &Key) -> bool { is_min_proposal_fund_key(key) || is_max_content_size_key(key) || is_max_proposal_code_size_key(key) - || is_min_proposal_period_key(key) + || is_min_proposal_voting_period_key(key) || is_max_proposal_period_key(key) || is_min_grace_epoch_key(key) } @@ -282,56 +287,56 @@ pub fn is_start_or_end_epoch_key(key: &Key) -> bool { /// Get governance prefix key pub fn proposal_prefix() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&PROPOSAL_PREFIX.to_owned()) + .push(&Keys::VALUES.proposal.to_owned()) .expect("Cannot obtain a storage key") } /// Get key for the minimum proposal fund pub fn get_min_proposal_fund_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&MIN_PROPOSAL_FUND_KEY.to_owned()) + .push(&Keys::VALUES.min_fund.to_owned()) .expect("Cannot obtain a storage key") } /// Get maximum proposal code size key pub fn get_max_proposal_code_size_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&MAX_PROPOSAL_CODE_SIZE_KEY.to_owned()) + .push(&Keys::VALUES.max_code_size.to_owned()) .expect("Cannot obtain a storage key") } /// Get minimum proposal period key -pub fn get_min_proposal_period_key() -> Key { +pub fn get_min_proposal_voting_period_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&MIN_PROPOSAL_PERIOD_KEY.to_owned()) + .push(&Keys::VALUES.min_period.to_owned()) .expect("Cannot obtain a storage key") } /// Get maximum proposal period key pub fn get_max_proposal_period_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&MAX_PROPOSAL_PERIOD_KEY.to_owned()) + .push(&Keys::VALUES.max_period.to_owned()) .expect("Cannot obtain a storage key") } /// Get maximum proposal content key pub fn get_max_proposal_content_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&MAX_PROPOSAL_CONTENT_SIZE_KEY.to_owned()) + .push(&Keys::VALUES.max_content.to_owned()) .expect("Cannot obtain a storage key") } /// Get min grace epoch proposal key pub fn get_min_proposal_grace_epoch_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&MIN_GRACE_EPOCH_KEY.to_owned()) + .push(&Keys::VALUES.min_grace_epoch.to_owned()) .expect("Cannot obtain a storage key") } /// Get key of proposal ids counter pub fn get_counter_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&COUNTER_KEY.to_owned()) + .push(&Keys::VALUES.counter.to_owned()) .expect("Cannot obtain a storage key") } @@ -340,7 +345,7 @@ pub fn get_content_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_CONTENT.to_owned()) + .push(&Keys::VALUES.content.to_owned()) .expect("Cannot obtain a storage key") } @@ -349,7 +354,7 @@ pub fn get_author_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_AUTHOR.to_owned()) + .push(&Keys::VALUES.author.to_owned()) .expect("Cannot obtain a storage key") } @@ -358,7 +363,7 @@ pub fn get_proposal_type_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_TYPE.to_owned()) + .push(&Keys::VALUES.proposal_type.to_owned()) .expect("Cannot obtain a storage key") } @@ -367,7 +372,7 @@ pub fn get_voting_start_epoch_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_START_EPOCH.to_owned()) + .push(&Keys::VALUES.start_epoch.to_owned()) .expect("Cannot obtain a storage key") } @@ -376,7 +381,7 @@ pub fn get_voting_end_epoch_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_END_EPOCH.to_owned()) + .push(&Keys::VALUES.end_epoch.to_owned()) .expect("Cannot obtain a storage key") } @@ -385,7 +390,7 @@ pub fn get_funds_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_FUNDS.to_owned()) + .push(&Keys::VALUES.funds.to_owned()) .expect("Cannot obtain a storage key") } @@ -394,14 +399,14 @@ pub fn get_grace_epoch_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_GRACE_EPOCH.to_owned()) + .push(&Keys::VALUES.grace_epoch.to_owned()) .expect("Cannot obtain a storage key") } /// Get the proposal committing key prefix pub fn get_commiting_proposals_prefix(epoch: u64) -> Key { proposal_prefix() - .push(&PROPOSAL_COMMITTING_EPOCH.to_owned()) + .push(&Keys::VALUES.committing_epoch.to_owned()) .expect("Cannot obtain a storage key") .push(&epoch.to_string()) .expect("Cannot obtain a storage key") @@ -412,7 +417,7 @@ pub fn get_proposal_code_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_CODE.to_owned()) + .push(&Keys::VALUES.proposal_code.to_owned()) .expect("Cannot obtain a storage key") } @@ -428,7 +433,7 @@ pub fn get_proposal_vote_prefix_key(id: u64) -> Key { proposal_prefix() .push(&id.to_string()) .expect("Cannot obtain a storage key") - .push(&PROPOSAL_VOTE.to_owned()) + .push(&Keys::VALUES.vote.to_owned()) .expect("Cannot obtain a storage key") } @@ -448,7 +453,7 @@ pub fn get_vote_proposal_key( /// Get the proposal execution key pub fn get_proposal_execution_key(id: u64) -> Key { Key::from(ADDRESS.to_db_key()) - .push(&PENDING_PROPOSAL.to_owned()) + .push(&Keys::VALUES.pending.to_owned()) .expect("Cannot obtain a storage key") .push(&id.to_string()) .expect("Cannot obtain a storage key") diff --git a/core/src/ledger/governance/storage/mod.rs b/core/src/ledger/governance/storage/mod.rs new file mode 100644 index 0000000000..e2de8e6ab8 --- /dev/null +++ b/core/src/ledger/governance/storage/mod.rs @@ -0,0 +1,6 @@ +/// Governance proposal keys +pub mod keys; +/// Proposal structures +pub mod proposal; +/// Vote structures +pub mod vote; diff --git a/core/src/ledger/governance/storage/proposal.rs b/core/src/ledger/governance/storage/proposal.rs new file mode 100644 index 0000000000..b5ac1284c4 --- /dev/null +++ b/core/src/ledger/governance/storage/proposal.rs @@ -0,0 +1,270 @@ +use std::collections::{BTreeMap, HashSet}; +use std::fmt::Display; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::ledger::governance::cli::onchain::{ + PgfAction, PgfContinous, PgfRetro, PgfSteward, +}; +use crate::ledger::governance::utils::{ProposalStatus, TallyType}; +use crate::ledger::storage_api::token::Amount; +use crate::types::address::Address; +use crate::types::hash::Hash; +use crate::types::storage::Epoch; + +#[allow(missing_docs)] +#[derive(Debug, Error)] +pub enum ProposalTypeError { + #[error("Invalid proposal type.")] + InvalidProposalType, +} + +/// An add or remove action for PGF +#[derive( + Debug, + Clone, + Hash, + PartialEq, + Eq, + PartialOrd, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +pub enum AddRemove { + /// Add + Add(T), + /// Remove + Remove(T), +} + +/// The target of a PGF payment +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, + Ord, + Eq, + PartialOrd, +)] +pub struct PGFTarget { + /// The target address + pub target: Address, + /// The amount of token to fund the target address + pub amount: Amount, +} + +/// The actions that a PGF Steward can propose to execute +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +pub enum PGFAction { + /// A continuous payment + Continuous(AddRemove), + /// A retro payment + Retro(PGFTarget), +} + +/// The type of a Proposal +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, +)] +pub enum ProposalType { + /// Default governance proposal with the optional wasm code + Default(Option), + /// PGF stewards proposal + PGFSteward(HashSet>), + /// PGF funding proposal + PGFPayment(Vec), +} + +impl ProposalType { + /// Check if the proposal type is default + pub fn is_default(&self) -> bool { + matches!(self, ProposalType::Default(_)) + } +} + +impl Display for ProposalType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProposalType::Default(_) => write!(f, "Default"), + ProposalType::PGFSteward(_) => write!(f, "Pgf steward"), + ProposalType::PGFPayment(_) => write!(f, "Pgf funding"), + } + } +} + +impl TryFrom for AddRemove
{ + type Error = ProposalTypeError; + + fn try_from(value: PgfSteward) -> Result { + match value.action { + PgfAction::Add => Ok(AddRemove::Add(value.address)), + PgfAction::Remove => Ok(AddRemove::Remove(value.address)), + } + } +} + +impl TryFrom for PGFAction { + type Error = ProposalTypeError; + + fn try_from(value: PgfContinous) -> Result { + match value.action { + PgfAction::Add => { + Ok(PGFAction::Continuous(AddRemove::Add(PGFTarget { + target: value.target.address, + amount: value.target.amount, + }))) + } + PgfAction::Remove => { + Ok(PGFAction::Continuous(AddRemove::Remove(PGFTarget { + target: value.target.address, + amount: value.target.amount, + }))) + } + } + } +} + +impl TryFrom for PGFAction { + type Error = ProposalTypeError; + + fn try_from(value: PgfRetro) -> Result { + Ok(PGFAction::Retro(PGFTarget { + target: value.target.address, + amount: value.target.amount, + })) + } +} + +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +/// Proposal rappresentation when fetched from the storage +pub struct StorageProposal { + /// The proposal id + pub id: u64, + /// The proposal content + pub content: BTreeMap, + /// The proposal author address + pub author: Address, + /// The proposal type + pub r#type: ProposalType, + /// The epoch from which voting is allowed + pub voting_start_epoch: Epoch, + /// The epoch from which voting is stopped + pub voting_end_epoch: Epoch, + /// The epoch from which this changes are executed + pub grace_epoch: Epoch, +} + +impl StorageProposal { + /// Check if the proposal can be voted + pub fn can_be_voted( + &self, + current_epoch: Epoch, + is_validator: bool, + ) -> bool { + if is_validator { + self.voting_start_epoch < self.voting_end_epoch + && current_epoch * 3 + <= self.voting_start_epoch + self.voting_end_epoch * 2 + } else { + let valid_start_epoch = current_epoch >= self.voting_start_epoch; + let valid_end_epoch = current_epoch <= self.voting_end_epoch; + valid_start_epoch && valid_end_epoch + } + } + + /// Return the type of tally for the proposal + pub fn get_tally_type(&self) -> TallyType { + TallyType::from(self.r#type.clone()) + } + + /// Return the status of a proposal + pub fn get_status(&self, current_epoch: Epoch) -> ProposalStatus { + if self.voting_start_epoch > self.voting_end_epoch { + ProposalStatus::Pending + } else if self.voting_start_epoch <= current_epoch + && current_epoch <= self.voting_end_epoch + { + ProposalStatus::OnGoing + } else { + ProposalStatus::Ended + } + } + + /// Serialize a proposal to string + pub fn to_string_with_status(&self, current_epoch: Epoch) -> String { + format!( + "Proposal Id: {} + {:2}Type: {} + {:2}Author: {} + {:2}Content: {:?} + {:2}Start Epoch: {} + {:2}End Epoch: {} + {:2}Grace Epoch: {} + {:2}Status: {} + ", + self.id, + "", + self.r#type, + "", + self.author, + "", + self.content, + "", + self.voting_start_epoch, + "", + self.voting_end_epoch, + "", + self.grace_epoch, + "", + self.get_status(current_epoch) + ) + } +} + +impl Display for StorageProposal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Proposal Id: {} + {:2}Type: {} + {:2}Author: {} + {:2}Start Epoch: {} + {:2}End Epoch: {} + {:2}Grace Epoch: {} + ", + self.id, + "", + self.r#type, + "", + self.author, + "", + self.voting_start_epoch, + "", + self.voting_end_epoch, + "", + self.grace_epoch + ) + } +} diff --git a/core/src/ledger/governance/storage/vote.rs b/core/src/ledger/governance/storage/vote.rs new file mode 100644 index 0000000000..3ba8ec2ae2 --- /dev/null +++ b/core/src/ledger/governance/storage/vote.rs @@ -0,0 +1,127 @@ +use std::fmt::Display; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use super::super::cli::onchain::ProposalVote; +use super::proposal::ProposalType; + +/// The type of a governance vote with the optional associated Memo +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Eq, + Serialize, + Deserialize, +)] +pub enum VoteType { + /// A default vote without Memo + Default, + /// A vote for the PGF stewards + PGFSteward, + /// A vote for a PGF payment proposal + PGFPayment, +} + +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + Eq, + Serialize, + Deserialize, +)] +/// The vote for a proposal +pub enum StorageProposalVote { + /// Yes + Yay(VoteType), + /// No + Nay, +} + +impl StorageProposalVote { + /// Check if a vote is yay + pub fn is_yay(&self) -> bool { + matches!(self, StorageProposalVote::Yay(_)) + } + + /// Check if vote is of type default + pub fn is_default_vote(&self) -> bool { + matches!( + self, + StorageProposalVote::Yay(VoteType::Default) + | StorageProposalVote::Nay + ) + } + + /// Check if a vote is compatible with a proposal + pub fn is_compatible(&self, proposal_type: &ProposalType) -> bool { + match self { + StorageProposalVote::Yay(vote_type) => proposal_type.eq(vote_type), + StorageProposalVote::Nay => true, + } + } + + /// Create a new vote + pub fn build( + proposal_vote: &ProposalVote, + proposal_type: &ProposalType, + ) -> Option { + match (proposal_vote, proposal_type) { + (ProposalVote::Yay, ProposalType::Default(_)) => { + Some(StorageProposalVote::Yay(VoteType::Default)) + } + (ProposalVote::Yay, ProposalType::PGFSteward(_)) => { + Some(StorageProposalVote::Yay(VoteType::PGFSteward)) + } + (ProposalVote::Yay, ProposalType::PGFPayment(_)) => { + Some(StorageProposalVote::Yay(VoteType::PGFPayment)) + } + (ProposalVote::Nay, ProposalType::Default(_)) => { + Some(StorageProposalVote::Nay) + } + (ProposalVote::Nay, ProposalType::PGFSteward(_)) => { + Some(StorageProposalVote::Nay) + } + (ProposalVote::Nay, ProposalType::PGFPayment(_)) => { + Some(StorageProposalVote::Nay) + } + _ => None, + } + } +} + +impl Display for StorageProposalVote { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StorageProposalVote::Yay(vote_type) => match vote_type { + VoteType::Default + | VoteType::PGFSteward + | VoteType::PGFPayment => write!(f, "yay"), + }, + + StorageProposalVote::Nay => write!(f, "nay"), + } + } +} + +impl PartialEq for ProposalType { + fn eq(&self, other: &VoteType) -> bool { + match self { + Self::Default(_) => { + matches!(other, VoteType::Default) + } + Self::PGFSteward(_) => { + matches!(other, VoteType::PGFSteward) + } + Self::PGFPayment(_) => { + matches!(other, VoteType::PGFPayment) + } + } + } +} diff --git a/core/src/ledger/governance/utils.rs b/core/src/ledger/governance/utils.rs new file mode 100644 index 0000000000..30df25e3fb --- /dev/null +++ b/core/src/ledger/governance/utils.rs @@ -0,0 +1,291 @@ +use std::collections::HashMap; +use std::fmt::Display; + +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::cli::offline::OfflineVote; +use super::storage::proposal::ProposalType; +use super::storage::vote::StorageProposalVote; +use crate::types::address::Address; +use crate::types::storage::Epoch; +use crate::types::token; + +/// Proposal status +pub enum ProposalStatus { + /// Pending proposal status + Pending, + /// Ongoing proposal status + OnGoing, + /// Ended proposal status + Ended, +} + +impl Display for ProposalStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProposalStatus::Pending => write!(f, "pending"), + ProposalStatus::OnGoing => write!(f, "on-going"), + ProposalStatus::Ended => write!(f, "ended"), + } + } +} + +/// Alias to comulate voting power +pub type VotePower = token::Amount; + +/// Structure rappresenting a proposal vote +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct Vote { + /// Field holding the address of the validator + pub validator: Address, + /// Field holding the address of the delegator + pub delegator: Address, + /// Field holding vote data + pub data: StorageProposalVote, +} + +impl Vote { + /// Check if a vote is from a validator + pub fn is_validator(&self) -> bool { + self.validator.eq(&self.delegator) + } +} + +/// Rappresent a tally type +pub enum TallyType { + /// Rappresent a tally type for proposal requiring 2/3 of the votes + TwoThird, + /// Rappresent a tally type for proposal requiring 1/3 of the votes + OneThird, + /// Rappresent a tally type for proposal requiring less than 1/3 of the + /// votes to be nay + LessOneThirdNay, +} + +impl From for TallyType { + fn from(proposal_type: ProposalType) -> Self { + match proposal_type { + ProposalType::Default(_) => TallyType::TwoThird, + ProposalType::PGFSteward(_) => TallyType::TwoThird, + ProposalType::PGFPayment(_) => TallyType::LessOneThirdNay, + } + } +} + +/// The result of a proposal +pub enum TallyResult { + /// Proposal was accepted with the associated value + Passed, + /// Proposal was rejected + Rejected, +} + +impl Display for TallyResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TallyResult::Passed => write!(f, "passed"), + TallyResult::Rejected => write!(f, "rejected"), + } + } +} + +impl TallyResult { + /// Create a new tally result + pub fn new( + tally_type: &TallyType, + yay_voting_power: VotePower, + nay_voting_power: VotePower, + total_voting_power: VotePower, + ) -> Self { + let passed = match tally_type { + TallyType::TwoThird => { + let at_least_two_third_voted = yay_voting_power + + nay_voting_power + >= total_voting_power / 3 * 2; + let at_last_half_voted_yay = + yay_voting_power > nay_voting_power; + at_least_two_third_voted && at_last_half_voted_yay + } + TallyType::OneThird => { + let at_least_two_third_voted = yay_voting_power + + nay_voting_power + >= total_voting_power / 3; + let at_last_half_voted_yay = + yay_voting_power > nay_voting_power; + at_least_two_third_voted && at_last_half_voted_yay + } + TallyType::LessOneThirdNay => { + nay_voting_power <= total_voting_power / 3 + } + }; + + if passed { Self::Passed } else { Self::Rejected } + } +} + +/// The result with votes of a proposal +pub struct ProposalResult { + /// The result of a proposal + pub result: TallyResult, + /// The total voting power during the proposal tally + pub total_voting_power: VotePower, + /// The total voting power from yay votes + pub total_yay_power: VotePower, + /// The total voting power from nay votes (unused at the moment) + pub total_nay_power: VotePower, +} + +impl Display for ProposalResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let percentage = self + .total_yay_power + .checked_div(self.total_voting_power) + .unwrap_or_default(); + + write!( + f, + "{} with {} yay votes and {} nay votes ({:.2}%)", + self.result, + self.total_yay_power.to_string_native(), + self.total_nay_power.to_string_native(), + percentage + .checked_mul(token::Amount::from_u64(100)) + .unwrap_or_default() + .to_string_native() + ) + } +} + +impl ProposalResult { + /// Return true if two third of total voting power voted nay + pub fn two_third_nay(&self) -> bool { + self.total_nay_power >= (self.total_voting_power / 3) * 2 + } +} + +/// /// General rappresentation of a vote +pub enum TallyVote { + /// Rappresent a vote for a proposal onchain + OnChain(StorageProposalVote), + /// Rappresent a vote for a proposal offline + Offline(OfflineVote), +} + +impl From for TallyVote { + fn from(vote: StorageProposalVote) -> Self { + Self::OnChain(vote) + } +} + +impl From for TallyVote { + fn from(vote: OfflineVote) -> Self { + Self::Offline(vote) + } +} + +impl TallyVote { + /// Check if a vote is yay + pub fn is_yay(&self) -> bool { + match self { + TallyVote::OnChain(vote) => vote.is_yay(), + TallyVote::Offline(vote) => vote.is_yay(), + } + } + + /// Check if two votes are equal + pub fn is_same_side(&self, other: &TallyVote) -> bool { + let both_yay = self.is_yay() && other.is_yay(); + let both_nay = !self.is_yay() && !other.is_yay(); + + both_yay || !both_nay + } +} + +/// Proposal structure holding votes information necessary to compute the +/// outcome +pub struct ProposalVotes { + /// Map from validator address to vote + pub validators_vote: HashMap, + /// Map from validator to their voting power + pub validator_voting_power: HashMap, + /// Map from delegation address to their vote + pub delegators_vote: HashMap, + /// Map from delegator address to the corresponding validator voting power + pub delegator_voting_power: HashMap>, +} + +/// Compute the result of a proposal +pub fn compute_proposal_result( + votes: ProposalVotes, + total_voting_power: VotePower, + tally_at: TallyType, +) -> ProposalResult { + let mut yay_voting_power = VotePower::default(); + let mut nay_voting_power = VotePower::default(); + + for (address, vote_power) in votes.validator_voting_power { + let vote_type = votes.validators_vote.get(&address); + if let Some(vote) = vote_type { + if vote.is_yay() { + yay_voting_power += vote_power; + } else { + nay_voting_power += vote_power; + } + } + } + + for (delegator, degalations) in votes.delegator_voting_power { + let delegator_vote = match votes.delegators_vote.get(&delegator) { + Some(vote) => vote, + None => continue, + }; + for (validator, voting_power) in degalations { + let validator_vote = votes.validators_vote.get(&validator); + if let Some(validator_vote) = validator_vote { + if !validator_vote.is_same_side(delegator_vote) { + if delegator_vote.is_yay() { + yay_voting_power += voting_power; + nay_voting_power -= voting_power; + } else { + nay_voting_power += voting_power; + yay_voting_power -= voting_power; + } + } + } else if delegator_vote.is_yay() { + yay_voting_power += voting_power; + } else { + nay_voting_power += voting_power; + } + } + } + + let tally_result = TallyResult::new( + &tally_at, + yay_voting_power, + nay_voting_power, + total_voting_power, + ); + + ProposalResult { + result: tally_result, + total_voting_power, + total_yay_power: yay_voting_power, + total_nay_power: nay_voting_power, + } +} + +/// Calculate the valid voting window for validator given a proposal epoch +/// details +pub fn is_valid_validator_voting_period( + current_epoch: Epoch, + voting_start_epoch: Epoch, + voting_end_epoch: Epoch, +) -> bool { + if voting_start_epoch >= voting_end_epoch { + false + } else { + let duration = voting_end_epoch - voting_start_epoch; + let two_third_duration = (duration / 3) * 2; + current_epoch <= voting_start_epoch + two_third_duration + } +} diff --git a/core/src/ledger/mod.rs b/core/src/ledger/mod.rs index 89b8105551..9a84fbc126 100644 --- a/core/src/ledger/mod.rs +++ b/core/src/ledger/mod.rs @@ -6,8 +6,8 @@ pub mod governance; #[cfg(any(feature = "abciplus", feature = "abcipp"))] pub mod ibc; pub mod parameters; +pub mod pgf; pub mod replay_protection; -pub mod slash_fund; pub mod storage; pub mod storage_api; pub mod testnet_pow; diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index c507e55d49..03d27fa2da 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -46,6 +46,8 @@ pub struct Parameters { pub implicit_vp_code_hash: Hash, /// Expected number of epochs per year (read only) pub epochs_per_year: u64, + /// Maximum number of signature per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p (read only) pub pos_gain_p: Dec, /// PoS gain d (read only) @@ -117,6 +119,7 @@ impl Parameters { tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -168,6 +171,13 @@ impl Parameters { let epochs_per_year_key = storage::get_epochs_per_year_key(); storage.write(&epochs_per_year_key, epochs_per_year)?; + let max_signatures_per_transaction_key = + storage::get_max_signatures_per_transaction_key(); + storage.write( + &max_signatures_per_transaction_key, + max_signatures_per_transaction, + )?; + let pos_gain_p_key = storage::get_pos_gain_p_key(); storage.write(&pos_gain_p_key, pos_gain_p)?; @@ -197,6 +207,17 @@ impl Parameters { } } +/// Get the max signatures per transactio parameter +pub fn max_signatures_per_transaction( + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = storage::get_max_signatures_per_transaction_key(); + storage.read(&key) +} + /// Update the max_expected_time_per_block parameter in storage. Returns the /// parameters and gas cost. pub fn update_max_expected_time_per_block_parameter( @@ -340,6 +361,20 @@ where storage.write_bytes(&key, implicit_vp) } +/// Update the max signatures per transaction storage parameter +pub fn update_max_signature_per_tx( + storage: &mut S, + value: u8, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage::get_max_signatures_per_transaction_key(); + // Using `fn write_bytes` here, because implicit_vp doesn't need to be + // encoded, it's bytes already. + storage.write(&key, value) +} + /// Read the the epoch duration parameter from store pub fn read_epoch_duration_parameter( storage: &S, @@ -434,6 +469,15 @@ where .ok_or(ReadError::ParametersMissing) .into_storage_result()?; + // read the maximum signatures per transaction + let max_signatures_per_transaction_key = + storage::get_max_signatures_per_transaction_key(); + let value: Option = + storage.read(&max_signatures_per_transaction_key)?; + let max_signatures_per_transaction: u8 = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; + // read PoS gain P let pos_gain_p_key = storage::get_pos_gain_p_key(); let value = storage.read(&pos_gain_p_key)?; @@ -478,6 +522,7 @@ where tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, diff --git a/core/src/ledger/parameters/storage.rs b/core/src/ledger/parameters/storage.rs index 94498e3578..d32b1d221f 100644 --- a/core/src/ledger/parameters/storage.rs +++ b/core/src/ledger/parameters/storage.rs @@ -44,6 +44,7 @@ struct Keys { max_proposal_bytes: &'static str, faucet_account: &'static str, wrapper_tx_fees: &'static str, + max_signatures_per_transaction: &'static str, } /// Returns if the key is a parameter key. @@ -183,3 +184,8 @@ pub fn get_faucet_account_key() -> Key { pub fn get_wrapper_tx_fees_key() -> Key { get_wrapper_tx_fees_key_at_addr(ADDRESS) } + +/// Storage key used for the max signatures per transaction key +pub fn get_max_signatures_per_transaction_key() -> Key { + get_max_signatures_per_transaction_key_at_addr(ADDRESS) +} diff --git a/core/src/ledger/slash_fund/mod.rs b/core/src/ledger/pgf/mod.rs similarity index 55% rename from core/src/ledger/slash_fund/mod.rs rename to core/src/ledger/pgf/mod.rs index 7a7d53963b..a36621e49e 100644 --- a/core/src/ledger/slash_fund/mod.rs +++ b/core/src/ledger/pgf/mod.rs @@ -1,8 +1,11 @@ -//! SlashFund library code +//! Pgf library code use crate::types::address::{Address, InternalAddress}; -/// Internal SlashFund address -pub const ADDRESS: Address = Address::Internal(InternalAddress::SlashFund); - +/// Pgf parameters +pub mod parameters; +/// Pgf storage pub mod storage; + +/// The Pgf internal address +pub const ADDRESS: Address = Address::Internal(InternalAddress::Pgf); diff --git a/core/src/ledger/pgf/parameters.rs b/core/src/ledger/pgf/parameters.rs new file mode 100644 index 0000000000..335ec56607 --- /dev/null +++ b/core/src/ledger/pgf/parameters.rs @@ -0,0 +1,70 @@ +use std::collections::BTreeSet; + +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::storage::keys as pgf_storage; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::address::Address; +use crate::types::dec::Dec; + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, +)] +/// Pgf parameter structure +pub struct PgfParams { + /// The set of stewards + pub stewards: BTreeSet
, + /// The set of continous payments + pub payments: BTreeSet, + /// The pgf funding inflation rate + pub pgf_inflation_rate: Dec, + /// The pgf stewards inflation rate + pub stewards_inflation_rate: Dec, +} + +impl Default for PgfParams { + fn default() -> Self { + Self { + stewards: BTreeSet::default(), + payments: BTreeSet::default(), + pgf_inflation_rate: Dec::new(5, 2).unwrap(), + stewards_inflation_rate: Dec::new(1, 2).unwrap(), + } + } +} + +impl PgfParams { + /// Initialize governance parameters into storage + pub fn init_storage(&self, storage: &mut S) -> storage_api::Result<()> + where + S: StorageRead + StorageWrite, + { + let Self { + stewards, + payments, + pgf_inflation_rate, + stewards_inflation_rate, + } = self; + + let stewards_key = pgf_storage::get_stewards_key(); + storage.write(&stewards_key, stewards)?; + + let payments_key = pgf_storage::get_payments_key(); + storage.write(&payments_key, payments)?; + + let pgf_inflation_rate_key = pgf_storage::get_pgf_inflation_rate_key(); + storage.write(&pgf_inflation_rate_key, pgf_inflation_rate)?; + + let steward_inflation_rate_key = + pgf_storage::get_steward_inflation_rate_key(); + storage.write(&steward_inflation_rate_key, stewards_inflation_rate) + } +} diff --git a/core/src/ledger/pgf/storage/keys.rs b/core/src/ledger/pgf/storage/keys.rs new file mode 100644 index 0000000000..fa4875bb77 --- /dev/null +++ b/core/src/ledger/pgf/storage/keys.rs @@ -0,0 +1,67 @@ +use namada_macros::StorageKeys; + +use crate::ledger::pgf::ADDRESS; +use crate::types::address::Address; +use crate::types::storage::{DbKeySeg, Key, KeySeg}; + +/// Storage keys for pgf internal address. +#[derive(StorageKeys)] +struct Keys { + stewards: &'static str, + payments: &'static str, + pgf_inflation_rate: &'static str, + steward_inflation_rate: &'static str, +} + +/// Check if key is inside governance address space +pub fn is_pgf_key(key: &Key) -> bool { + matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &ADDRESS) +} + +/// Check if key is a steward key +pub fn is_stewards_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix)] if addr == &ADDRESS && prefix == Keys::VALUES.stewards) +} + +/// Check if key is a payments key +pub fn is_payments_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix)] if addr == &ADDRESS && prefix == Keys::VALUES.payments) +} + +/// Check if key is a pgf inflation rate key +pub fn is_pgf_inflation_rate_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix)] if addr == &ADDRESS && prefix == Keys::VALUES.pgf_inflation_rate) +} + +/// Check if key is a steward inflation rate key +pub fn is_steward_inflation_rate_key(key: &Key) -> bool { + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix)] if addr == &ADDRESS && prefix == Keys::VALUES.steward_inflation_rate) +} + +/// Get key for stewards key +pub fn get_stewards_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&Keys::VALUES.stewards.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Get key for payments key +pub fn get_payments_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&Keys::VALUES.payments.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Get key for inflation rate key +pub fn get_pgf_inflation_rate_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&Keys::VALUES.pgf_inflation_rate.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Get key for inflation rate key +pub fn get_steward_inflation_rate_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&Keys::VALUES.steward_inflation_rate.to_owned()) + .expect("Cannot obtain a storage key") +} diff --git a/core/src/ledger/pgf/storage/mod.rs b/core/src/ledger/pgf/storage/mod.rs new file mode 100644 index 0000000000..fb9d6f8896 --- /dev/null +++ b/core/src/ledger/pgf/storage/mod.rs @@ -0,0 +1,2 @@ +/// Pgf storage keys +pub mod keys; diff --git a/core/src/ledger/slash_fund/storage.rs b/core/src/ledger/slash_fund/storage.rs deleted file mode 100644 index 9c437da591..0000000000 --- a/core/src/ledger/slash_fund/storage.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Slash fund storage - -use crate::types::storage::{DbKeySeg, Key}; - -/// Check if a key is a slash fund key -pub fn is_slash_fund_key(key: &Key) -> bool { - matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &super::ADDRESS) -} diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 6509c02d34..94669a34f4 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -1225,6 +1225,7 @@ mod tests { tx_whitelist: vec![], implicit_vp_code_hash: Hash::zero(), epochs_per_year: 100, + max_signatures_per_transaction: 15, 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"), diff --git a/core/src/ledger/storage_api/account.rs b/core/src/ledger/storage_api/account.rs new file mode 100644 index 0000000000..4896de635f --- /dev/null +++ b/core/src/ledger/storage_api/account.rs @@ -0,0 +1,106 @@ +//! Cryptographic signature keys storage API + +use super::*; +use crate::types::account::AccountPublicKeysMap; +use crate::types::address::Address; +use crate::types::key::*; +use crate::types::storage::Key; + +/// Init the subspace of a new account +pub fn init_account_storage( + storage: &mut S, + owner: &Address, + public_keys: &[common::PublicKey], + threshold: u8, +) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + for (index, public_key) in public_keys.iter().enumerate() { + let index = index as u8; + pks_handle(owner).insert(storage, index, public_key.clone())?; + } + let threshold_key = threshold_key(owner); + storage.write(&threshold_key, threshold) +} + +/// Get the threshold associated with an account +pub fn threshold(storage: &S, owner: &Address) -> Result> +where + S: StorageRead, +{ + let threshold_key = threshold_key(owner); + storage.read(&threshold_key) +} + +/// Get the public keys associated with an account +pub fn public_keys( + storage: &S, + owner: &Address, +) -> Result> +where + S: StorageRead, +{ + let public_keys = pks_handle(owner) + .iter(storage)? + .filter_map(|data| match data { + Ok((_index, public_key)) => Some(public_key), + Err(_) => None, + }) + .collect::>(); + + Ok(public_keys) +} + +/// Get the public key index map associated with an account +pub fn public_keys_index_map( + storage: &S, + owner: &Address, +) -> Result +where + S: StorageRead, +{ + let public_keys = public_keys(storage, owner)?; + + Ok(AccountPublicKeysMap::from_iter(public_keys)) +} + +/// Check if an account exists in storage +pub fn exists(storage: &S, owner: &Address) -> Result +where + S: StorageRead, +{ + match owner { + Address::Established(_) => { + let vp_key = Key::validity_predicate(owner); + storage.has_key(&vp_key) + } + Address::Implicit(_) | Address::Internal(_) => Ok(true), + } +} + +/// Set public key at specific index +pub fn set_public_key_at( + storage: &mut S, + owner: &Address, + public_key: &common::PublicKey, + index: u8, +) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + pks_handle(owner).insert(storage, index, public_key.clone())?; + Ok(()) +} + +/// Clear the public keys account subtorage space +pub fn clear_public_keys(storage: &mut S, owner: &Address) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + let total_pks = pks_handle(owner).len(storage)?; + for index in 0..total_pks as u8 { + pks_handle(owner).remove(storage, &index)?; + } + Ok(()) +} diff --git a/core/src/ledger/storage_api/governance.rs b/core/src/ledger/storage_api/governance.rs index c2316bffa7..1d4d3f767e 100644 --- a/core/src/ledger/storage_api/governance.rs +++ b/core/src/ledger/storage_api/governance.rs @@ -1,12 +1,25 @@ //! Governance +use std::collections::BTreeMap; + +use borsh::BorshDeserialize; + use super::token; -use crate::ledger::governance::{storage, ADDRESS as governance_address}; +use crate::ledger::governance::storage::keys as governance_keys; +use crate::ledger::governance::storage::proposal::{ + ProposalType, StorageProposal, +}; +use crate::ledger::governance::storage::vote::StorageProposalVote; +use crate::ledger::governance::utils::Vote; +use crate::ledger::governance::ADDRESS as governance_address; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::address::Address; +use crate::types::storage::Epoch; use crate::types::transaction::governance::{ - InitProposalData, ProposalType, VoteProposalData, + InitProposalData, VoteProposalData, }; +/// A proposal creation transaction. /// A proposal creation transaction. pub fn init_proposal( storage: &mut S, @@ -17,25 +30,26 @@ pub fn init_proposal( where S: StorageRead + StorageWrite, { - let counter_key = storage::get_counter_key(); + let counter_key = governance_keys::get_counter_key(); let proposal_id = if let Some(id) = data.id { id } else { storage.read(&counter_key)?.unwrap() }; - let content_key = storage::get_content_key(proposal_id); + let content_key = governance_keys::get_content_key(proposal_id); storage.write_bytes(&content_key, content)?; - let author_key = storage::get_author_key(proposal_id); + let author_key = governance_keys::get_author_key(proposal_id); storage.write(&author_key, data.author.clone())?; - let proposal_type_key = storage::get_proposal_type_key(proposal_id); + let proposal_type_key = governance_keys::get_proposal_type_key(proposal_id); match data.r#type { 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); + let proposal_code_key = + governance_keys::get_proposal_code_key(proposal_id); let proposal_code = code.clone().ok_or( storage_api::Error::new_const("Missing proposal code"), )?; @@ -45,17 +59,19 @@ where } let voting_start_epoch_key = - storage::get_voting_start_epoch_key(proposal_id); + governance_keys::get_voting_start_epoch_key(proposal_id); storage.write(&voting_start_epoch_key, data.voting_start_epoch)?; - let voting_end_epoch_key = storage::get_voting_end_epoch_key(proposal_id); + let voting_end_epoch_key = + governance_keys::get_voting_end_epoch_key(proposal_id); storage.write(&voting_end_epoch_key, data.voting_end_epoch)?; - let grace_epoch_key = storage::get_grace_epoch_key(proposal_id); + let grace_epoch_key = governance_keys::get_grace_epoch_key(proposal_id); storage.write(&grace_epoch_key, data.grace_epoch)?; if let ProposalType::Default(Some(_)) = data.r#type { - let proposal_code_key = storage::get_proposal_code_key(proposal_id); + let proposal_code_key = + governance_keys::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)?; @@ -63,16 +79,19 @@ where storage.write(&counter_key, proposal_id + 1)?; - let min_proposal_funds_key = storage::get_min_proposal_fund_key(); + let min_proposal_funds_key = governance_keys::get_min_proposal_fund_key(); let min_proposal_funds: token::Amount = storage.read(&min_proposal_funds_key)?.unwrap(); - let funds_key = storage::get_funds_key(proposal_id); + let funds_key = governance_keys::get_funds_key(proposal_id); storage.write(&funds_key, min_proposal_funds)?; // this key must always be written for each proposal let committing_proposals_key = - storage::get_committing_proposals_key(proposal_id, data.grace_epoch.0); + governance_keys::get_committing_proposals_key( + proposal_id, + data.grace_epoch.0, + ); storage.write(&committing_proposals_key, ())?; token::transfer( @@ -93,7 +112,7 @@ where S: StorageRead + StorageWrite, { for delegation in data.delegations { - let vote_key = storage::get_vote_proposal_key( + let vote_key = governance_keys::get_vote_proposal_key( data.id, data.voter.clone(), delegation, @@ -102,3 +121,99 @@ where } Ok(()) } + +/// Read a proposal by id from storage +pub fn get_proposal_by_id( + storage: &S, + id: u64, +) -> storage_api::Result> +where + S: StorageRead, +{ + let author_key = governance_keys::get_author_key(id); + let content = governance_keys::get_content_key(id); + let start_epoch_key = governance_keys::get_voting_start_epoch_key(id); + let end_epoch_key = governance_keys::get_voting_end_epoch_key(id); + let grace_epoch_key = governance_keys::get_grace_epoch_key(id); + let proposal_type_key = governance_keys::get_proposal_type_key(id); + + let author: Option
= storage.read(&author_key)?; + let content: Option> = storage.read(&content)?; + let voting_start_epoch: Option = storage.read(&start_epoch_key)?; + let voting_end_epoch: Option = storage.read(&end_epoch_key)?; + let grace_epoch: Option = storage.read(&grace_epoch_key)?; + let proposal_type: Option = + storage.read(&proposal_type_key)?; + + let proposal = proposal_type.map(|proposal_type| StorageProposal { + id, + content: content.unwrap(), + author: author.unwrap(), + r#type: proposal_type, + voting_start_epoch: voting_start_epoch.unwrap(), + voting_end_epoch: voting_end_epoch.unwrap(), + grace_epoch: grace_epoch.unwrap(), + }); + + Ok(proposal) +} + +/// Query all the votes for a proposal_id +pub fn get_proposal_votes( + storage: &S, + proposal_id: u64, +) -> storage_api::Result> +where + S: storage_api::StorageRead, +{ + let vote_prefix_key = + governance_keys::get_proposal_vote_prefix_key(proposal_id); + let vote_iter = storage_api::iter_prefix::( + storage, + &vote_prefix_key, + )?; + + let votes = vote_iter + .filter_map(|vote_result| { + if let Ok((vote_key, vote)) = vote_result { + let voter_address = + governance_keys::get_voter_address(&vote_key); + let delegator_address = + governance_keys::get_vote_delegation_address(&vote_key); + match (voter_address, delegator_address) { + (Some(delegator_address), Some(validator_address)) => { + Some(Vote { + validator: validator_address.to_owned(), + delegator: delegator_address.to_owned(), + data: vote, + }) + } + _ => None, + } + } else { + None + } + }) + .collect::>(); + + Ok(votes) +} + +/// Check if an accepted proposal is being executed +pub fn is_proposal_accepted( + storage: &S, + tx_data: &[u8], +) -> storage_api::Result +where + S: storage_api::StorageRead, +{ + let proposal_id = u64::try_from_slice(tx_data).ok(); + match proposal_id { + Some(id) => { + let proposal_execution_key = + governance_keys::get_proposal_execution_key(id); + storage.has_key(&proposal_execution_key) + } + None => Ok(false), + } +} diff --git a/core/src/ledger/storage_api/key.rs b/core/src/ledger/storage_api/key.rs index 6e3eba64aa..69bdd50720 100644 --- a/core/src/ledger/storage_api/key.rs +++ b/core/src/ledger/storage_api/key.rs @@ -4,23 +4,17 @@ use super::*; use crate::types::address::Address; use crate::types::key::*; -/// Get the public key associated with the given address. Returns `Ok(None)` if -/// not found. -pub fn get(storage: &S, owner: &Address) -> Result> -where - S: StorageRead, -{ - let key = pk_key(owner); - storage.read(&key) -} - /// Reveal a PK of an implicit account - the PK is written into the storage /// of the address derived from the PK. -pub fn reveal_pk(storage: &mut S, pk: &common::PublicKey) -> Result<()> +pub fn reveal_pk( + storage: &mut S, + public_key: &common::PublicKey, +) -> Result<()> where - S: StorageWrite, + S: StorageWrite + StorageRead, { - let addr: Address = pk.into(); - let key = pk_key(&addr); - storage.write(&key, pk) + let owner: Address = public_key.into(); + pks_handle(&owner).insert(storage, 0, public_key.clone())?; + + Ok(()) } diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 451996d0e5..1108c44e3d 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -1,10 +1,12 @@ //! The common storage read trait is implemented in the storage, client RPC, tx //! and VPs (both native and WASM). +pub mod account; pub mod collections; mod error; pub mod governance; pub mod key; +pub mod pgf; pub mod token; pub mod validation; diff --git a/core/src/ledger/storage_api/pgf.rs b/core/src/ledger/storage_api/pgf.rs new file mode 100644 index 0000000000..8dfafee0fb --- /dev/null +++ b/core/src/ledger/storage_api/pgf.rs @@ -0,0 +1,30 @@ +//! Pgf + +use std::collections::BTreeSet; + +use crate::ledger::governance::storage::proposal::PGFTarget; +use crate::ledger::pgf::storage::keys as pgf_keys; +use crate::ledger::storage_api::{self}; +use crate::types::address::Address; + +/// Query the current pgf steward set +pub fn get_stewards(storage: &S) -> storage_api::Result> +where + S: storage_api::StorageRead, +{ + let stewards_key = pgf_keys::get_stewards_key(); + let stewards: Option> = storage.read(&stewards_key)?; + + Ok(stewards.unwrap_or_default()) +} + +/// Query the current pgf continous payments +pub fn get_payments(storage: &S) -> storage_api::Result> +where + S: storage_api::StorageRead, +{ + let payment_key = pgf_keys::get_payments_key(); + let payments: Option> = storage.read(&payment_key)?; + + Ok(payments.unwrap_or_default()) +} diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 1985d8325c..95987d6a83 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -134,3 +134,35 @@ where storage.write(&balance_key, new_balance)?; storage.write(&total_supply_key, new_supply) } + +/// Burn an amount of token for a specific address. +pub fn burn( + storage: &mut S, + token: &Address, + source: &Address, + amount: token::Amount, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = token::balance_key(token, source); + let balance = read_balance(storage, token, source)?; + + let amount_to_burn = match balance.checked_sub(amount) { + Some(new_balance) => { + storage.write(&key, new_balance)?; + amount + } + None => { + storage.write(&key, token::Amount::default())?; + balance + } + }; + + let total_supply = read_total_supply(&*storage, source)?; + let new_total_supply = + total_supply.checked_sub(amount_to_burn).unwrap_or_default(); + + let total_supply_key = token::minted_balance_key(token); + storage.write(&total_supply_key, new_total_supply) +} diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 27ae37ff69..e8411a41d2 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -4,8 +4,9 @@ pub mod generated; mod types; pub use types::{ - Code, Commitment, Data, Dkg, Error, Header, MaspBuilder, Section, Signable, - SignableEthMessage, Signature, Signed, Tx, TxError, + Code, Commitment, Data, Dkg, Error, Header, MaspBuilder, MultiSignature, + Section, Signable, SignableEthMessage, Signature, SignatureIndex, Signed, + Tx, TxError, }; #[cfg(test)] diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 610d453654..59c035561e 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; +use std::cmp::Ordering; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::convert::TryFrom; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; @@ -10,6 +11,7 @@ use ark_ec::AffineCurve; use ark_ec::PairingEngine; use borsh::schema::{Declaration, Definition}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; use masp_primitives::transaction::builder::Builder; use masp_primitives::transaction::components::sapling::builder::SaplingMetadata; use masp_primitives::transaction::Transaction; @@ -22,8 +24,10 @@ use thiserror::Error; use super::generated::types; use crate::ledger::storage::{KeccakHasher, Sha256Hasher, StorageHasher}; +use crate::ledger::testnet_pow; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; +use crate::types::account::AccountPublicKeysMap; use crate::types::address::Address; use crate::types::chain::ChainId; use crate::types::keccak::{keccak_hash, KeccakHash}; @@ -41,7 +45,9 @@ use crate::types::transaction::EllipticCurve; use crate::types::transaction::EncryptionKey; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::WrapperTxErr; -use crate::types::transaction::{hash_tx, DecryptedTx, TxType, WrapperTx}; +use crate::types::transaction::{ + hash_tx, DecryptedTx, Fee, GasLimit, TxType, WrapperTx, +}; #[derive(Error, Debug)] pub enum Error { @@ -49,6 +55,8 @@ pub enum Error { TxDecodingError(prost::DecodeError), #[error("Error deserializing transaction field bytes: {0}")] TxDeserializingError(std::io::Error), + #[error("Error deserializing transaction")] + OfflineTxDeserializationError, #[error("Error decoding an DkgGossipMessage from bytes: {0}")] DkgDecodingError(prost::DecodeError), #[error("Dkg is empty")] @@ -57,6 +65,12 @@ pub enum Error { NoTimestampError, #[error("Timestamp is invalid: {0}")] InvalidTimestamp(prost_types::TimestampError), + #[error("The section signature is invalid: {0}")] + InvalidSectionSignature(String), + #[error("Couldn't serialize transaction from JSON at {0}")] + InvalidJSONDeserialization(String), + #[error("The wrapper signature is invalid.")] + InvalidWrapperSignature, } pub type Result = std::result::Result; @@ -360,6 +374,164 @@ impl Code { } } +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, + Eq, + PartialEq, +)] +pub struct SignatureIndex { + pub signature: common::Signature, + pub index: u8, +} + +impl SignatureIndex { + pub fn from_single_signature(signature: common::Signature) -> Self { + Self { + signature, + index: 0, + } + } + + pub fn to_vec(&self) -> Vec { + vec![self.clone()] + } + + pub fn verify( + &self, + public_key_index_map: &AccountPublicKeysMap, + data: &impl SignableBytes, + ) -> std::result::Result<(), VerifySigError> { + let public_key = + public_key_index_map.get_public_key_from_index(self.index); + if let Some(public_key) = public_key { + common::SigScheme::verify_signature( + &public_key, + data, + &self.signature, + ) + } else { + Err(VerifySigError::MissingData) + } + } + + pub fn serialize(&self) -> String { + let signature_bytes = + self.try_to_vec().expect("Signature should be serializable"); + HEXUPPER.encode(&signature_bytes) + } + + pub fn deserialize(data: &[u8]) -> Result { + if let Ok(hex) = serde_json::from_slice::(data) { + match HEXUPPER.decode(hex.as_bytes()) { + Ok(bytes) => Self::try_from_slice(&bytes) + .map_err(Error::TxDeserializingError), + Err(_) => Err(Error::OfflineTxDeserializationError), + } + } else { + Err(Error::OfflineTxDeserializationError) + } + } +} + +impl Ord for SignatureIndex { + fn cmp(&self, other: &Self) -> Ordering { + self.index.cmp(&other.index) + } +} + +impl PartialOrd for SignatureIndex { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// A section representing a multisig over another section +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct MultiSignature { + /// The hash of the section being signed + pub targets: Vec, + /// The signature over the above hash + pub signatures: BTreeSet, +} + +impl MultiSignature { + /// Sign the given section hash with the given key and return a section + pub fn new( + targets: Vec, + secret_keys: &[common::SecretKey], + public_keys_index_map: &AccountPublicKeysMap, + ) -> Self { + let target = Self { + targets: targets.clone(), + signatures: BTreeSet::new(), + } + .get_hash(); + + let signatures_public_keys_map = + secret_keys.iter().map(|secret_key: &common::SecretKey| { + let signature = common::SigScheme::sign(secret_key, target); + let public_key = secret_key.ref_to(); + (public_key, signature) + }); + + let signatures = signatures_public_keys_map + .filter_map(|(public_key, signature)| { + let public_key_index = public_keys_index_map + .get_index_from_public_key(&public_key); + public_key_index + .map(|index| SignatureIndex { signature, index }) + }) + .collect::>(); + + Self { + targets, + signatures, + } + } + + pub fn total_signatures(&self) -> u8 { + self.signatures.len() as u8 + } + + /// Hash this signature section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec() + .expect("unable to serialize multisignature section"), + ); + 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(), + ) + } + + pub fn get_raw_hash(&self) -> crate::types::hash::Hash { + Self { + signatures: BTreeSet::new(), + ..self.clone() + } + .get_hash() + } +} + /// A section representing the signature over another section #[derive( Clone, @@ -371,26 +543,19 @@ impl Code { Deserialize, )] pub struct Signature { - /// Additional random data - salt: [u8; 8], /// The hash of the section being signed 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( targets: Vec, sec_key: &common::SecretKey, ) -> Self { let mut sec = Self { - salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), targets, - pub_key: sec_key.ref_to(), signature: None, }; sec.signature = Some(common::SigScheme::sign(sec_key, sec.get_hash())); @@ -414,11 +579,14 @@ impl Signature { } /// Verify that the signature contained in this section is valid - pub fn verify_signature(&self) -> std::result::Result<(), VerifySigError> { + pub fn verify_signature( + &self, + public_key: &common::PublicKey, + ) -> std::result::Result<(), VerifySigError> { let signature = self.signature.as_ref().ok_or(VerifySigError::MissingData)?; common::SigScheme::verify_signature( - &self.pub_key, + public_key, &Self { signature: None, ..self.clone() @@ -730,6 +898,8 @@ pub enum Section { /// Transaction code. Sending to hardware wallets optional Code(Code), /// A transaction signature. Often produced by hardware wallets + SectionSignature(MultiSignature), + /// A transaction header/protocol signature Signature(Signature), /// Ciphertext obtained by encrypting arbitrary transaction sections Ciphertext(Ciphertext), @@ -759,7 +929,8 @@ impl Section { Self::Data(data) => data.hash(hasher), Self::ExtraData(extra) => extra.hash(hasher), Self::Code(code) => code.hash(hasher), - Self::Signature(sig) => sig.hash(hasher), + Self::Signature(signature) => signature.hash(hasher), + Self::SectionSignature(signatures) => signatures.hash(hasher), Self::Ciphertext(ct) => ct.hash(hasher), Self::MaspBuilder(mb) => mb.hash(hasher), Self::MaspTx(tx) => { @@ -831,6 +1002,15 @@ impl Section { } } + /// Extract the section signature from this section if possible + pub fn section_signature(&self) -> Option { + if let Self::SectionSignature(data) = self { + Some(data.clone()) + } else { + None + } + } + /// Extract the ciphertext from this section if possible pub fn ciphertext(&self) -> Option { if let Self::Ciphertext(data) = self { @@ -978,15 +1158,57 @@ impl TryFrom<&[u8]> for Tx { } } +impl Default for Tx { + fn default() -> Self { + Self { + header: Header::new(TxType::Raw), + sections: vec![], + } + } +} + impl Tx { + /// Initialize a new transaction builder + pub fn new(chain_id: ChainId, expiration: Option) -> Self { + Tx { + sections: vec![], + header: Header { + chain_id, + expiration, + ..Header::new(TxType::Raw) + }, + } + } + /// Create a transaction of the given type - pub fn new(header: TxType) -> Self { + pub fn from_type(header: TxType) -> Self { Tx { header: Header::new(header), sections: vec![], } } + /// Serialize tx to hex string + pub fn serialize(&self) -> String { + let tx_bytes = self + .try_to_vec() + .expect("Transation should be serializable"); + HEXUPPER.encode(&tx_bytes) + } + + // Deserialize from hex encoding + pub fn deserialize(data: &[u8]) -> Result { + if let Ok(hex) = serde_json::from_slice::(data) { + match HEXUPPER.decode(hex.as_bytes()) { + Ok(bytes) => Tx::try_from_slice(&bytes) + .map_err(Error::TxDeserializingError), + Err(_) => Err(Error::OfflineTxDeserializationError), + } + } else { + Err(Error::OfflineTxDeserializationError) + } + } + /// Get the transaction header pub fn header(&self) -> Header { self.header.clone() @@ -1105,36 +1327,113 @@ impl Tx { bytes } + /// Get the inner section hashes + pub fn inner_section_targets(&self) -> Vec { + self.sections + .iter() + .filter_map(|section| match section { + Section::Data(_) | Section::Code(_) => Some(section.get_hash()), + _ => None, + }) + .collect() + } + + /// Verify that the section with the given hash has been signed by the given + /// public key + pub fn verify_section_signatures( + &self, + hashes: &[crate::types::hash::Hash], + public_keys_index_map: AccountPublicKeysMap, + threshold: u8, + max_signatures: Option, + ) -> std::result::Result<(), Error> { + let max_signatures = max_signatures.unwrap_or(u8::MAX); + let mut valid_signatures = 0; + + for section in &self.sections { + if let Section::SectionSignature(signatures) = section { + if !hashes.iter().all(|x| { + signatures.targets.contains(x) || section.get_hash() == *x + }) { + return Err(Error::InvalidSectionSignature( + "missing target hash.".to_string(), + )); + } + + for target in &signatures.targets { + if self.get_section(target).is_none() { + return Err(Error::InvalidSectionSignature( + "Missing target section.".to_string(), + )); + } + } + + if signatures.total_signatures() > max_signatures { + return Err(Error::InvalidSectionSignature( + "too many signatures.".to_string(), + )); + } + + if signatures.total_signatures() < threshold { + return Err(Error::InvalidSectionSignature( + "too few signatures.".to_string(), + )); + } + + for signature_index in &signatures.signatures { + let is_valid_signature = signature_index + .verify( + &public_keys_index_map, + &signatures.get_raw_hash(), + ) + .is_ok(); + if is_valid_signature { + valid_signatures += 1; + } + if valid_signatures >= threshold { + return Ok(()); + } + } + } + } + Err(Error::InvalidSectionSignature( + "invalid signatures.".to_string(), + )) + } + /// 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, + public_key: &common::PublicKey, hashes: &[crate::types::hash::Hash], - ) -> std::result::Result<&Signature, VerifySigError> { + ) -> Result<&Signature> { for section in &self.sections { - if let Section::Signature(sig_sec) = section { - // Check that the signer is matched and that the hashes being + if let Section::Signature(signature) = section { + // Check 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 - }) - { + if hashes.iter().all(|x| { + signature.targets.contains(x) || section.get_hash() == *x + }) { // Ensure that all the sections the signature signs over are // present - for target in &sig_sec.targets { + for target in &signature.targets { if self.get_section(target).is_none() { - return Err(VerifySigError::MissingData); + return Err(Error::InvalidSectionSignature( + "Target section is missing.".to_string(), + )); } } // Finally verify that the signature itself is valid - return sig_sec.verify_signature().map(|_| sig_sec); + return signature + .verify_signature(public_key) + .map(|_| signature) + .map_err(|_| Error::InvalidWrapperSignature); } } } - Err(VerifySigError::MissingData) + Err(Error::InvalidWrapperSignature) } /// Validate any and all ciphertexts stored in this transaction @@ -1153,6 +1452,16 @@ impl Tx { valid } + pub fn compute_section_signature( + &self, + secret_keys: &[common::SecretKey], + public_keys_index_map: &AccountPublicKeysMap, + ) -> BTreeSet { + let targets = [*self.data_sechash(), *self.code_sechash()].to_vec(); + MultiSignature::new(targets, secret_keys, public_keys_index_map) + .signatures + } + /// Decrypt any and all ciphertexts stored in this transaction use the /// given decryption key #[cfg(feature = "ferveo-tpke")] @@ -1180,7 +1489,7 @@ impl Tx { /// Encrypt all sections in this transaction other than the header and /// signatures over it #[cfg(feature = "ferveo-tpke")] - pub fn encrypt(&mut self, pubkey: &EncryptionKey) { + pub fn encrypt(&mut self, pubkey: &EncryptionKey) -> &mut Self { let header_hash = self.header_hash(); let mut plaintexts = vec![]; // Iterate backwrds to sidestep the effects of deletion on indexing @@ -1195,6 +1504,7 @@ impl Tx { // Encrypt all eligible sections in one go self.sections .push(Section::Ciphertext(Ciphertext::new(plaintexts, pubkey))); + self } /// Determines the type of the input Tx @@ -1280,6 +1590,139 @@ impl Tx { } filtered } + + /// Add an extra section to the tx builder by hash + pub fn add_extra_section_from_hash( + &mut self, + hash: crate::types::hash::Hash, + ) -> crate::types::hash::Hash { + let sechash = self + .add_section(Section::ExtraData(Code::from_hash(hash))) + .get_hash(); + sechash + } + + /// Add an extra section to the tx builder by code + pub fn add_extra_section( + &mut self, + code: Vec, + ) -> (&mut Self, crate::types::hash::Hash) { + let sechash = self + .add_section(Section::ExtraData(Code::new(code))) + .get_hash(); + (self, sechash) + } + + /// Add a masp tx section to the tx builder + pub fn add_masp_tx_section( + &mut self, + tx: Transaction, + ) -> (&mut Self, crate::types::hash::Hash) { + let sechash = self.add_section(Section::MaspTx(tx)).get_hash(); + (self, sechash) + } + + /// Add a masp builder section to the tx builder + pub fn add_masp_builder(&mut self, builder: MaspBuilder) -> &mut Self { + let _sec = self.add_section(Section::MaspBuilder(builder)); + self + } + + /// Add wasm code to the tx builder from hash + pub fn add_code_from_hash( + &mut self, + code_hash: crate::types::hash::Hash, + ) -> &mut Self { + self.set_code(Code::from_hash(code_hash)); + self + } + + /// Add wasm code to the tx builder + pub fn add_code(&mut self, code: Vec) -> &mut Self { + self.set_code(Code::new(code)); + self + } + + /// Add wasm data to the tx builder + pub fn add_data(&mut self, data: impl BorshSerialize) -> &mut Self { + let bytes = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + self.set_data(Data::new(bytes)); + self + } + + /// Add wasm data already serialized to the tx builder + pub fn add_serialized_data(&mut self, bytes: Vec) -> &mut Self { + self.set_data(Data::new(bytes)); + self + } + + /// Add wrapper tx to the tx builder + pub fn add_wrapper( + &mut self, + fee: Fee, + gas_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + #[cfg(not(feature = "mainnet"))] requires_pow: Option< + testnet_pow::Solution, + >, + ) -> &mut Self { + self.header.tx_type = TxType::Wrapper(Box::new(WrapperTx::new( + fee, + gas_payer, + epoch, + gas_limit, + #[cfg(not(feature = "mainnet"))] + requires_pow, + ))); + self + } + + /// Add fee payer keypair to the tx builder + pub fn sign_wrapper(&mut self, keypair: common::SecretKey) -> &mut Self { + self.protocol_filter(); + self.add_section(Section::Signature(Signature::new( + self.sechashes(), + &keypair, + ))); + self + } + + /// Add signing keys to the tx builder + pub fn sign_raw( + &mut self, + keypairs: Vec, + account_public_keys_map: AccountPublicKeysMap, + ) -> &mut Self { + self.protocol_filter(); + let hashes = self + .sections + .iter() + .filter_map(|section| match section { + Section::Data(_) | Section::Code(_) => Some(section.get_hash()), + _ => None, + }) + .collect(); + self.add_section(Section::SectionSignature(MultiSignature::new( + hashes, + &keypairs, + &account_public_keys_map, + ))); + self + } + + /// Add signature + pub fn add_signatures( + &mut self, + signatures: BTreeSet, + ) -> &mut Self { + self.protocol_filter(); + self.add_section(Section::SectionSignature(MultiSignature { + targets: self.inner_section_targets(), + signatures, + })); + self + } } #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] diff --git a/core/src/types/account.rs b/core/src/types/account.rs new file mode 100644 index 0000000000..d66876ba37 --- /dev/null +++ b/core/src/types/account.rs @@ -0,0 +1,92 @@ +//! Helper structures to manage accounts + +use std::collections::HashMap; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use super::address::Address; +use super::key::common; + +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +/// Account data +pub struct Account { + /// The map between indexes and public keys for an account + pub public_keys_map: AccountPublicKeysMap, + /// The account signature threshold + pub threshold: u8, + /// The address corresponding to the account owner + pub address: Address, +} + +impl Account { + /// Retrive a public key from the index + pub fn get_public_key_from_index( + &self, + index: u8, + ) -> Option { + self.public_keys_map.get_public_key_from_index(index) + } + + /// Retrive the index of a public key + pub fn get_index_from_public_key( + &self, + public_key: &common::PublicKey, + ) -> Option { + self.public_keys_map.get_index_from_public_key(public_key) + } +} + +#[derive( + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, + Default, +)] +/// Holds the public key map data as a bimap for efficient quering +pub struct AccountPublicKeysMap { + /// Hashmap from public key to index + pub pk_to_idx: HashMap, + /// Hashmap from index key to public key + pub idx_to_pk: HashMap, +} + +impl FromIterator for AccountPublicKeysMap { + fn from_iter>(iter: T) -> Self { + let mut pk_to_idx = HashMap::new(); + let mut idx_to_pk = HashMap::new(); + + for (index, public_key) in iter.into_iter().enumerate() { + pk_to_idx.insert(public_key.to_owned(), index as u8); + idx_to_pk.insert(index as u8, public_key.to_owned()); + } + + Self { + pk_to_idx, + idx_to_pk, + } + } +} + +impl AccountPublicKeysMap { + /// Retrive a public key from the index + pub fn get_public_key_from_index( + &self, + index: u8, + ) -> Option { + self.idx_to_pk.get(&index).cloned() + } + + /// Retrive the index of a public key + pub fn get_index_from_public_key( + &self, + public_key: &common::PublicKey, + ) -> Option { + self.pk_to_idx.get(public_key).cloned() + } +} diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 924d4412bb..d587d20280 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -75,8 +75,6 @@ mod internal { "ano::Protocol Parameters "; pub const GOVERNANCE: &str = "ano::Governance "; - pub const SLASH_FUND: &str = - "ano::Slash Fund "; pub const IBC: &str = "ibc::Inter-Blockchain Communication "; pub const ETH_BRIDGE: &str = @@ -87,6 +85,8 @@ mod internal { "ano::Replay Protection "; pub const MULTITOKEN: &str = "ano::Multitoken "; + pub const PGF: &str = + "ano::Pgf "; } /// Fixed-length address strings prefix for established addresses. @@ -219,9 +219,6 @@ impl Address { InternalAddress::Governance => { internal::GOVERNANCE.to_string() } - InternalAddress::SlashFund => { - internal::SLASH_FUND.to_string() - } InternalAddress::Ibc => internal::IBC.to_string(), InternalAddress::IbcToken(hash) => { format!("{}::{}", PREFIX_IBC, hash) @@ -243,6 +240,7 @@ impl Address { InternalAddress::Multitoken => { internal::MULTITOKEN.to_string() } + InternalAddress::Pgf => internal::PGF.to_string(), }; debug_assert_eq!(string.len(), FIXED_LEN_STRING_BYTES); string @@ -304,9 +302,6 @@ impl Address { internal::GOVERNANCE => { Ok(Address::Internal(InternalAddress::Governance)) } - internal::SLASH_FUND => { - Ok(Address::Internal(InternalAddress::SlashFund)) - } internal::ETH_BRIDGE => { Ok(Address::Internal(InternalAddress::EthBridge)) } @@ -319,6 +314,7 @@ impl Address { internal::MULTITOKEN => { Ok(Address::Internal(InternalAddress::Multitoken)) } + internal::PGF => Ok(Address::Internal(InternalAddress::Pgf)), _ => Err(Error::new( ErrorKind::InvalidData, "Invalid internal address", @@ -541,8 +537,6 @@ pub enum InternalAddress { IbcToken(String), /// Governance address Governance, - /// SlashFund address for governance - SlashFund, /// Bridge to Ethereum EthBridge, /// The pool of transactions to be relayed to Ethereum @@ -553,6 +547,8 @@ pub enum InternalAddress { ReplayProtection, /// Multitoken Multitoken, + /// Pgf + Pgf, } impl Display for InternalAddress { @@ -565,7 +561,6 @@ impl Display for InternalAddress { Self::PosSlashPool => "PosSlashPool".to_string(), Self::Parameters => "Parameters".to_string(), Self::Governance => "Governance".to_string(), - Self::SlashFund => "SlashFund".to_string(), Self::Ibc => "IBC".to_string(), Self::IbcToken(hash) => format!("IbcToken: {}", hash), Self::EthBridge => "EthBridge".to_string(), @@ -573,6 +568,7 @@ impl Display for InternalAddress { Self::Erc20(eth_addr) => format!("Erc20: {}", eth_addr), Self::ReplayProtection => "ReplayProtection".to_string(), Self::Multitoken => "Multitoken".to_string(), + Self::Pgf => "PublicGoodFundings".to_string(), } ) } @@ -859,7 +855,6 @@ pub mod testing { InternalAddress::PoS => {} InternalAddress::PosSlashPool => {} InternalAddress::Governance => {} - InternalAddress::SlashFund => {} InternalAddress::Parameters => {} InternalAddress::Ibc => {} InternalAddress::IbcToken(_) => {} @@ -867,6 +862,7 @@ pub mod testing { InternalAddress::EthBridgePool => {} InternalAddress::Erc20(_) => {} InternalAddress::ReplayProtection => {} + InternalAddress::Pgf => {} InternalAddress::Multitoken => {} /* Add new addresses in the * `prop_oneof` below. */ }; @@ -877,12 +873,12 @@ pub mod testing { Just(InternalAddress::Parameters), arb_ibc_token(), Just(InternalAddress::Governance), - Just(InternalAddress::SlashFund), Just(InternalAddress::EthBridge), Just(InternalAddress::EthBridgePool), Just(arb_erc20()), Just(InternalAddress::ReplayProtection), Just(InternalAddress::Multitoken), + Just(InternalAddress::Pgf), ] } diff --git a/core/src/types/dec.rs b/core/src/types/dec.rs index fdff5dc620..48589a1c32 100644 --- a/core/src/types/dec.rs +++ b/core/src/types/dec.rs @@ -410,7 +410,8 @@ impl Display for Dec { str_pre.push_str(string.as_str()); string = str_pre; }; - let stripped_string = string.trim_end_matches(['.', '0']); + let stripped_string = string.trim_end_matches('0'); + let stripped_string = stripped_string.trim_end_matches('.'); if stripped_string.is_empty() { f.write_str("0") } else if is_neg { @@ -620,4 +621,11 @@ mod test_dec { let larger = Dec::from_str("32418116583.390243854642").unwrap(); assert!(smaller < larger); } + + #[test] + fn test_dec_display() { + let num = Dec::from_str("14000.0000").unwrap(); + let s = format!("{}", num); + assert_eq!(s, String::from("14000")); + } } diff --git a/core/src/types/governance.rs b/core/src/types/governance.rs deleted file mode 100644 index d3450dccd1..0000000000 --- a/core/src/types/governance.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Files defyining the types used in governance. - -use std::collections::{BTreeMap, HashSet}; -use std::fmt::{self, Display}; - -use borsh::{BorshDeserialize, BorshSerialize}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::types::address::Address; -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, DenominatedAmount, NATIVE_MAX_DECIMAL_PLACES, -}; -use crate::types::uint::Uint; - -/// Type alias for vote power -pub type VotePower = Uint; - -/// A PGF cocuncil composed of the address and spending cap -pub type Council = (Address, Amount); - -/// The type of a governance vote with the optional associated Memo -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - Eq, -)] -pub enum VoteType { - /// A default vote without Memo - Default, - /// A vote for the PGF council - PGFCouncil(HashSet), - /// A vote for ETH bridge carrying the signature over the proposed message - ETHBridge(Signature), -} - -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - Eq, -)] -/// The vote for a proposal -pub enum ProposalVote { - /// Yes - Yay(VoteType), - /// No - Nay, -} - -impl ProposalVote { - /// Check if a vote is yay - pub fn is_yay(&self) -> bool { - matches!(self, ProposalVote::Yay(_)) - } - - /// Check if vote is of type default - pub fn is_default_vote(&self) -> bool { - matches!( - self, - ProposalVote::Yay(VoteType::Default) | ProposalVote::Nay - ) - } -} - -impl Display for ProposalVote { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ProposalVote::Yay(vote_type) => match vote_type { - VoteType::Default => write!(f, "yay"), - VoteType::PGFCouncil(councils) => { - writeln!(f, "yay with councils:")?; - for (address, spending_cap) in councils { - writeln!( - f, - "Council: {}, spending cap: {}", - address, - spending_cap.to_string_native() - )? - } - - Ok(()) - } - VoteType::ETHBridge(sig) => { - write!(f, "yay with signature: {:#?}", sig) - } - }, - - ProposalVote::Nay => write!(f, "nay"), - } - } -} - -#[allow(missing_docs)] -#[derive(Debug, Error)] -pub enum ProposalVoteParseError { - #[error("Invalid vote. Vote shall be yay or nay.")] - InvalidVote, -} - -/// The type of the tally -#[derive(Clone, Debug)] -pub enum Tally { - /// Default proposal - Default, - /// PGF proposal - PGFCouncil(Council), - /// ETH Bridge proposal - ETHBridge, -} - -/// The result of a proposal -#[derive(Clone, Debug)] -pub enum TallyResult { - /// Proposal was accepted with the associated value - Passed(Tally), - /// Proposal was rejected - Rejected, -} - -/// The result with votes of a proposal -pub struct ProposalResult { - /// The result of a proposal - pub result: TallyResult, - /// The total voting power during the proposal tally - pub total_voting_power: VotePower, - /// The total voting power from yay votes - pub total_yay_power: VotePower, - /// The total voting power from nay votes (unused at the moment) - pub total_nay_power: VotePower, -} - -impl Display for ProposalResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - 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 {} ({}%)", - self.result, - 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 - ) - } -} - -impl Display for TallyResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TallyResult::Passed(vote) => match vote { - Tally::Default | Tally::ETHBridge => write!(f, "passed"), - Tally::PGFCouncil((council, cap)) => write!( - f, - "passed with PGF council address: {}, spending cap: {}", - council, - cap.to_string_native() - ), - }, - TallyResult::Rejected => write!(f, "rejected"), - } - } -} - -/// The type of a governance proposal -#[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -pub enum ProposalType { - /// A default proposal with the optional path to wasm code - Default(Option), - /// A PGF council proposal - PGFCouncil, - /// An ETH bridge proposal - ETHBridge, -} - -#[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -/// The proposal structure -pub struct Proposal { - /// The proposal id - pub id: Option, - /// The proposal content - pub content: BTreeMap, - /// The proposal author address - pub author: Address, - /// The proposal type - pub r#type: ProposalType, - /// The epoch from which voting is allowed - pub voting_start_epoch: Epoch, - /// The epoch from which voting is stopped - pub voting_end_epoch: Epoch, - /// The epoch from which this changes are executed - pub grace_epoch: Epoch, -} - -impl Display for Proposal { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "id: {:?}, author: {:?}", self.id, self.author) - } -} - -#[allow(missing_docs)] -#[derive(Debug, Error)] -pub enum ProposalError { - #[error("Invalid proposal data.")] - InvalidProposalData, -} - -#[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -/// The offline proposal structure -pub struct OfflineProposal { - /// The proposal content - pub content: BTreeMap, - /// The proposal author address - pub author: Address, - /// The epoch from which this changes are executed - pub tally_epoch: Epoch, - /// The signature over proposal data - pub signature: Signature, - /// The address corresponding to the signature pk - pub address: Address, -} - -impl OfflineProposal { - /// Create an offline proposal with a signature - pub fn new( - proposal: Proposal, - address: Address, - signing_key: &common::SecretKey, - ) -> Self { - let content_serialized = serde_json::to_vec(&proposal.content) - .expect("Conversion to bytes shouldn't fail."); - let author_serialized = serde_json::to_vec(&proposal.author) - .expect("Conversion to bytes shouldn't fail."); - let tally_epoch_serialized = serde_json::to_vec(&proposal.grace_epoch) - .expect("Conversion to bytes shouldn't fail."); - let proposal_serialized = &[ - content_serialized, - author_serialized, - tally_epoch_serialized, - ] - .concat(); - let proposal_data_hash = Hash::sha256(proposal_serialized); - let signature = - common::SigScheme::sign(signing_key, proposal_data_hash); - Self { - content: proposal.content, - author: proposal.author, - tally_epoch: proposal.grace_epoch, - signature, - address, - } - } - - /// Check whether the signature is valid or not - pub fn check_signature(&self, public_key: &common::PublicKey) -> bool { - let proposal_data_hash = self.compute_hash(); - common::SigScheme::verify_signature( - public_key, - &proposal_data_hash, - &self.signature, - ) - .is_ok() - } - - /// Compute the hash of the proposal - pub fn compute_hash(&self) -> Hash { - let content_serialized = serde_json::to_vec(&self.content) - .expect("Conversion to bytes shouldn't fail."); - let author_serialized = serde_json::to_vec(&self.author) - .expect("Conversion to bytes shouldn't fail."); - let tally_epoch_serialized = serde_json::to_vec(&self.tally_epoch) - .expect("Conversion to bytes shouldn't fail."); - let proposal_serialized = &[ - content_serialized, - author_serialized, - tally_epoch_serialized, - ] - .concat(); - Hash::sha256(proposal_serialized) - } -} - -#[derive( - Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, -)] -/// The offline proposal structure -pub struct OfflineVote { - /// The proposal data hash - pub proposal_hash: Hash, - /// The proposal vote - pub vote: ProposalVote, - /// The signature over proposal data - pub signature: Signature, - /// The address corresponding to the signature pk - pub address: Address, -} - -impl OfflineVote { - /// Create an offline vote for a proposal - pub fn new( - proposal: &OfflineProposal, - vote: ProposalVote, - address: Address, - signing_key: &common::SecretKey, - ) -> Self { - let proposal_hash = proposal.compute_hash(); - let proposal_hash_data = proposal_hash - .try_to_vec() - .expect("Conversion to bytes shouldn't fail."); - let proposal_vote_data = vote - .try_to_vec() - .expect("Conversion to bytes shouldn't fail."); - let vote_serialized = - &[proposal_hash_data, proposal_vote_data].concat(); - let signature = common::SigScheme::sign(signing_key, vote_serialized); - Self { - proposal_hash, - vote, - signature, - address, - } - } - - /// compute the hash of a proposal - pub fn compute_hash(&self) -> Hash { - let proposal_hash_data = self - .proposal_hash - .try_to_vec() - .expect("Conversion to bytes shouldn't fail."); - let proposal_vote_data = self - .vote - .try_to_vec() - .expect("Conversion to bytes shouldn't fail."); - let vote_serialized = - &[proposal_hash_data, proposal_vote_data].concat(); - - Hash::sha256(vote_serialized) - } - - /// Check whether the signature is valid or not - pub fn check_signature(&self, public_key: &common::PublicKey) -> bool { - let vote_data_hash = self.compute_hash(); - common::SigScheme::verify_signature( - public_key, - &vote_data_hash, - &self.signature, - ) - .is_ok() - } -} diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 8c59262f72..685e3464f7 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -12,6 +12,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; +use lazy_map::LazyMap; +use namada_macros::StorageKeys; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::Serialize; @@ -19,25 +21,45 @@ use sha2::{Digest, Sha256}; use thiserror::Error; use super::address::Address; -use super::storage::{self, DbKeySeg, Key, KeySeg}; +use super::storage::{self, DbKeySeg, Key}; use crate::ledger::storage::{Sha256Hasher, StorageHasher}; +use crate::ledger::storage_api::collections::{lazy_map, LazyCollection}; use crate::types::address; -const PK_STORAGE_KEY: &str = "public_key"; -const PROTOCOL_PK_STORAGE_KEY: &str = "protocol_public_key"; +/// Storage keys for account. +#[derive(StorageKeys)] +struct Keys { + public_keys: &'static str, + threshold: &'static str, + protocol_public_keys: &'static str, +} /// Obtain a storage key for user's public key. -pub fn pk_key(owner: &Address) -> storage::Key { - Key::from(owner.to_db_key()) - .push(&PK_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +pub fn pks_key_prefix(owner: &Address) -> storage::Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.public_keys.to_string()), + ], + } +} + +/// Object that LazyMap handler for the user's public key subspace +pub fn pks_handle(owner: &Address) -> LazyMap { + LazyMap::open(pks_key_prefix(owner)) } /// Check if the given storage key is a public key. If it is, returns the owner. -pub fn is_pk_key(key: &Key) -> Option<&Address> { +pub fn is_pks_key(key: &Key) -> Option<&Address> { match &key.segments[..] { - [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(key)] - if key == PK_STORAGE_KEY => + [ + DbKeySeg::AddressSeg(owner), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(index), + ] if prefix.as_str() == Keys::VALUES.public_keys + && data.as_str() == lazy_map::DATA_SUBKEY + && index.parse::().is_ok() => { Some(owner) } @@ -45,18 +67,43 @@ pub fn is_pk_key(key: &Key) -> Option<&Address> { } } +/// Check if the given storage key is a threshol key. +pub fn is_threshold_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(prefix)] + if prefix.as_str() == Keys::VALUES.threshold => + { + Some(owner) + } + _ => None, + } +} + +/// Obtain the storage key for a user threshold +pub fn threshold_key(owner: &Address) -> storage::Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.threshold.to_string()), + ], + } +} + /// Obtain a storage key for user's protocol public key. pub fn protocol_pk_key(owner: &Address) -> storage::Key { - Key::from(owner.to_db_key()) - .push(&PROTOCOL_PK_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.protocol_public_keys.to_string()), + ], + } } /// Check if the given storage key is a public key. If it is, returns the owner. pub fn is_protocol_pk_key(key: &Key) -> Option<&Address> { match &key.segments[..] { [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(key)] - if key == PROTOCOL_PK_STORAGE_KEY => + if key.as_str() == Keys::VALUES.protocol_public_keys => { Some(owner) } diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index c35b314296..8aee038d9b 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -1,5 +1,6 @@ //! Types definitions. +pub mod account; pub mod address; pub mod chain; pub mod dec; @@ -7,7 +8,6 @@ pub mod eth_abi; pub mod eth_bridge_pool; pub mod ethereum_events; pub mod ethereum_structs; -pub mod governance; pub mod hash; pub mod ibc; pub mod internal; diff --git a/core/src/types/token.rs b/core/src/types/token.rs index bf4e23cbe3..6056495cd1 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -76,6 +76,11 @@ impl Amount { self.raw = self.raw.checked_sub(amount.raw).unwrap(); } + /// Check if there are enough funds. + pub fn can_spend(&self, amount: &Amount) -> bool { + self.raw >= amount.raw + } + /// Receive a given amount. /// Panics on overflow and when [`uint::MAX_SIGNED_VALUE`] is exceeded. pub fn receive(&mut self, amount: &Amount) { @@ -154,6 +159,20 @@ impl Amount { Self { raw: change.abs() } } + /// Checked division. Returns `None` on underflow. + pub fn checked_div(&self, amount: Amount) -> Option { + self.raw + .checked_div(amount.raw) + .map(|result| Self { raw: result }) + } + + /// Checked division. Returns `None` on overflow. + pub fn checked_mul(&self, amount: Amount) -> Option { + self.raw + .checked_mul(amount.raw) + .map(|result| Self { raw: result }) + } + /// Given a string and a denomination, parse an amount from string. pub fn from_str( string: impl AsRef, diff --git a/core/src/types/transaction/account.rs b/core/src/types/transaction/account.rs new file mode 100644 index 0000000000..f2eaafe7ef --- /dev/null +++ b/core/src/types/transaction/account.rs @@ -0,0 +1,52 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::types::address::Address; +use crate::types::hash::Hash; +use crate::types::key::common; + +/// A tx data type to initialize a new established account +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct InitAccount { + /// Public keys to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub public_keys: Vec, + /// The VP code hash + pub vp_code_hash: Hash, + /// The account signature threshold + pub threshold: u8, +} + +/// A tx data type to update an account's validity predicate +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct UpdateAccount { + /// An address of the account + pub addr: Address, + /// The new VP code hash + pub vp_code_hash: Option, + /// Public keys to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub public_keys: Vec, + /// The account signature threshold + pub threshold: Option, +} diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index bfc17eeaef..0d653c44cf 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -1,86 +1,25 @@ -use std::fmt::Display; +use std::collections::HashSet; use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; +use thiserror::Error; -use crate::types::address::Address; -use crate::types::governance::{ - self, Proposal, ProposalError, ProposalVote, VoteType, +use crate::ledger::governance::cli::onchain::{ + DefaultProposal, PgfFundingProposal, PgfStewardProposal, +}; +use crate::ledger::governance::storage::proposal::{ + AddRemove, PGFAction, ProposalType, }; +use crate::ledger::governance::storage::vote::StorageProposalVote; +use crate::types::address::Address; use crate::types::hash::Hash; use crate::types::storage::Epoch; -/// The type of a Proposal -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, -)] -pub enum ProposalType { - /// Default governance proposal with the optional wasm code - Default(Option), - /// PGF council proposal - PGFCouncil, - /// ETH proposal - ETHBridge, -} - -impl Display for ProposalType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - ProposalType::Default(_) => write!(f, "Default"), - ProposalType::PGFCouncil => write!(f, "PGF Council"), - ProposalType::ETHBridge => write!(f, "ETH Bridge"), - } - } -} - -impl PartialEq for ProposalType { - fn eq(&self, other: &VoteType) -> bool { - match self { - Self::Default(_) => { - matches!(other, VoteType::Default) - } - Self::PGFCouncil => { - matches!(other, VoteType::PGFCouncil(..)) - } - Self::ETHBridge => { - matches!(other, VoteType::ETHBridge(_)) - } - } - } -} - -impl TryFrom for (ProposalType, Option>) { - type Error = ProposalError; - - fn try_from(value: governance::ProposalType) -> Result { - match value { - governance::ProposalType::Default(path) => { - if let Some(p) = path { - match std::fs::read(p) { - Ok(code) => Ok(( - ProposalType::Default(Some(Hash::default())), - Some(code), - )), - Err(_) => Err(Self::Error::InvalidProposalData), - } - } else { - Ok((ProposalType::Default(None), None)) - } - } - governance::ProposalType::PGFCouncil => { - Ok((ProposalType::PGFCouncil, None)) - } - governance::ProposalType::ETHBridge => { - Ok((ProposalType::ETHBridge, None)) - } - } - } +#[allow(missing_docs)] +#[derive(Debug, Error)] +pub enum ProposalError { + #[error("Invalid proposal data.")] + InvalidProposalData, } /// A tx data type to hold proposal data @@ -110,6 +49,16 @@ pub struct InitProposalData { pub grace_epoch: Epoch, } +impl InitProposalData { + /// Get the hash of the corresponding extra data section + pub fn get_section_code_hash(&self) -> Option { + match self.r#type { + ProposalType::Default(hash) => hash, + _ => None, + } + } +} + /// A tx data type to hold vote proposal data #[derive( Debug, @@ -124,30 +73,82 @@ pub struct VoteProposalData { /// The proposal id pub id: u64, /// The proposal vote - pub vote: ProposalVote, + pub vote: StorageProposalVote, /// The proposal author address pub voter: Address, /// Delegator addreses pub delegations: Vec
, } -impl TryFrom for (InitProposalData, Vec, Option>) { +impl TryFrom for InitProposalData { + type Error = ProposalError; + + fn try_from(value: DefaultProposal) -> Result { + Ok(InitProposalData { + id: value.proposal.id, + content: Hash::default(), + author: value.proposal.author, + r#type: ProposalType::Default(None), + voting_start_epoch: value.proposal.voting_start_epoch, + voting_end_epoch: value.proposal.voting_end_epoch, + grace_epoch: value.proposal.grace_epoch, + }) + } +} + +impl TryFrom for InitProposalData { type Error = ProposalError; - fn try_from(proposal: Proposal) -> Result { - 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, - )) + fn try_from(value: PgfStewardProposal) -> Result { + let extra_data = value + .data + .iter() + .cloned() + .map(|steward| AddRemove::
::try_from(steward).unwrap()) + .collect::>>(); + + Ok(InitProposalData { + id: value.proposal.id, + content: Hash::default(), + author: value.proposal.author, + r#type: ProposalType::PGFSteward(extra_data), + voting_start_epoch: value.proposal.voting_start_epoch, + voting_end_epoch: value.proposal.voting_end_epoch, + grace_epoch: value.proposal.grace_epoch, + }) + } +} + +impl TryFrom for InitProposalData { + type Error = ProposalError; + + fn try_from(value: PgfFundingProposal) -> Result { + let continous_fundings = value + .data + .continous + .iter() + .cloned() + .map(|funding| PGFAction::try_from(funding).unwrap()) + .collect::>(); + + let retro_fundings = value + .data + .retro + .iter() + .cloned() + .map(|funding| PGFAction::try_from(funding).unwrap()) + .collect::>(); + + let extra_data = [continous_fundings, retro_fundings].concat(); + + Ok(InitProposalData { + id: value.proposal.id, + content: Hash::default(), + author: value.proposal.author, + r#type: ProposalType::PGFPayment(extra_data), + voting_start_epoch: value.proposal.voting_start_epoch, + voting_end_epoch: value.proposal.voting_end_epoch, + grace_epoch: value.proposal.grace_epoch, + }) } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index c6cc23dffa..112bc5de4b 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -1,5 +1,7 @@ //! Types that are used in transactions. +/// txs to manage accounts +pub mod account; /// txs that contain decrypted payloads or assertions of /// non-decryptability pub mod decrypted; @@ -7,6 +9,7 @@ pub mod decrypted; pub mod encrypted; /// txs to manage governance pub mod governance; +/// txs to manage pos pub mod pos; /// transaction protocols made by validators pub mod protocol; @@ -27,10 +30,8 @@ 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::*; use crate::types::storage; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::protocol::ProtocolTx; @@ -131,80 +132,6 @@ fn iterable_to_string( } } -/// A tx data type to update an account's validity predicate -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct UpdateVp { - /// An address of the account - pub addr: Address, - /// The new VP code hash - pub vp_code_hash: Hash, -} - -/// A tx data type to initialize a new established account -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct InitAccount { - /// Public key to be written into the account's storage. This can be used - /// for signature verification of transactions for the newly created - /// account. - pub public_key: common::PublicKey, - /// The VP code hash - pub vp_code_hash: Hash, -} - -/// A tx data type to initialize a new validator account. -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct InitValidator { - /// Public key to be written into the account's storage. This can be used - /// for signature verification of transactions for the newly created - /// account. - pub account_key: common::PublicKey, - /// A key to be used for signing blocks and votes on blocks. - pub consensus_key: common::PublicKey, - /// An Eth bridge governance public key - pub eth_cold_key: secp256k1::PublicKey, - /// An Eth bridge hot signing public key used for validator set updates and - /// cross-chain transactions - pub eth_hot_key: secp256k1::PublicKey, - /// Public key used to sign protocol transactions - pub protocol_key: common::PublicKey, - /// 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: Dec, - /// The maximum change allowed per epoch to the commission rate. This is - /// immutable once set here. - pub max_commission_rate_change: Dec, - /// The VP code for validator account - pub validator_vp_code_hash: Hash, -} - /// Struct that classifies that kind of Tx /// based on the contents of its data. #[derive( @@ -241,6 +168,7 @@ mod test_process_tx { use super::*; use crate::proto::{Code, Data, Section, Signature, Tx, TxError}; use crate::types::address::nam; + use crate::types::key::*; use crate::types::storage::Epoch; use crate::types::token::Amount; @@ -256,7 +184,7 @@ mod test_process_tx { /// data and returns an identical copy #[test] fn test_process_tx_raw_tx_no_data() { - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); let code_sec = outer_tx .set_code(Code::new("wasm code".as_bytes().to_owned())) .clone(); @@ -274,7 +202,7 @@ mod test_process_tx { /// of the inner data #[test] fn test_process_tx_raw_tx_some_data() { - let mut tx = Tx::new(TxType::Raw); + let mut tx = Tx::from_type(TxType::Raw); let code_sec = tx .set_code(Code::new("wasm code".as_bytes().to_owned())) .clone(); @@ -296,7 +224,7 @@ mod test_process_tx { /// signed data and returns an identical copy of the inner data #[test] fn test_process_tx_raw_tx_some_signed_data() { - let mut tx = Tx::new(TxType::Raw); + let mut tx = Tx::from_type(TxType::Raw); let code_sec = tx .set_code(Code::new("wasm code".as_bytes().to_owned())) .clone(); @@ -324,7 +252,7 @@ mod test_process_tx { fn test_process_tx_wrapper_tx() { let keypair = gen_keypair(); // the signed tx - let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), @@ -359,7 +287,7 @@ mod test_process_tx { fn test_process_tx_wrapper_tx_unsigned() { let keypair = gen_keypair(); // the signed tx - let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + let mut tx = Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), @@ -382,7 +310,7 @@ mod test_process_tx { #[test] fn test_process_tx_decrypted_unsigned() { use crate::proto::{Code, Data, Tx}; - let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + let mut tx = Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: false, })); @@ -411,6 +339,8 @@ fn test_process_tx_decrypted_unsigned() { #[test] fn test_process_tx_decrypted_signed() { use crate::proto::{Code, Data, Section, Signature, Tx}; + use crate::types::key::*; + fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; use rand::thread_rng; @@ -420,10 +350,11 @@ fn test_process_tx_decrypted_signed() { } use crate::types::key::Signature as S; - let mut decrypted = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })); + let mut decrypted = + Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); // Invalid signed data let ed_sig = ed25519::Signature::try_from_slice([0u8; 64].as_ref()).unwrap(); diff --git a/core/src/types/transaction/pos.rs b/core/src/types/transaction/pos.rs index d334047ffe..fa0e3d0891 100644 --- a/core/src/types/transaction/pos.rs +++ b/core/src/types/transaction/pos.rs @@ -5,8 +5,48 @@ use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::dec::Dec; +use crate::types::hash::Hash; +use crate::types::key::{common, secp256k1}; use crate::types::token; +/// A tx data type to initialize a new validator account. +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct InitValidator { + /// Public key to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub account_keys: Vec, + /// The minimum number of signatures needed + pub threshold: u8, + /// A key to be used for signing blocks and votes on blocks. + pub consensus_key: common::PublicKey, + /// An Eth bridge governance public key + pub eth_cold_key: secp256k1::PublicKey, + /// An Eth bridge hot signing public key used for validator set updates and + /// cross-chain transactions + pub eth_hot_key: secp256k1::PublicKey, + /// Public key used to sign protocol transactions + pub protocol_key: common::PublicKey, + /// 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: Dec, + /// The maximum change allowed per epoch to the commission rate. This is + /// immutable once set here. + pub max_commission_rate_change: Dec, + /// The VP code for validator account + pub validator_vp_code_hash: Hash, +} + /// A bond is a validator's self-bond or a delegation from non-validator to a /// validator. #[derive( diff --git a/core/src/types/transaction/protocol.rs b/core/src/types/transaction/protocol.rs index d546168bf4..aa9abe8cec 100644 --- a/core/src/types/transaction/protocol.rs +++ b/core/src/types/transaction/protocol.rs @@ -193,7 +193,7 @@ mod protocol_txs { ) -> Tx { let (tx_data, tx_type) = self.serialize(); let mut outer_tx = - Tx::new(TxType::Protocol(Box::new(ProtocolTx { + Tx::from_type(TxType::Protocol(Box::new(ProtocolTx { pk: signing_key.ref_to(), tx: tx_type, }))); @@ -323,7 +323,7 @@ mod protocol_txs { TX_NEW_DKG_KP_WASM, ); let mut outer_tx = - Tx::new(TxType::Protocol(Box::new(ProtocolTx { + Tx::from_type(TxType::Protocol(Box::new(ProtocolTx { pk: signing_key.ref_to(), tx: Self::NewDkgKeypair, }))); diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 403641a0b6..321435687b 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -13,6 +13,7 @@ pub mod wrapper_tx { use sha2::{Digest, Sha256}; use thiserror::Error; + use crate::ledger::testnet_pow; use crate::types::address::Address; use crate::types::key::*; use crate::types::storage::Epoch; @@ -240,7 +241,7 @@ pub mod wrapper_tx { epoch: Epoch, gas_limit: GasLimit, #[cfg(not(feature = "mainnet"))] pow_solution: Option< - crate::ledger::testnet_pow::Solution, + testnet_pow::Solution, >, ) -> WrapperTx { Self { @@ -255,7 +256,7 @@ pub mod wrapper_tx { /// Get the address of the implicit account associated /// with the public key - pub fn fee_payer(&self) -> Address { + pub fn gas_payer(&self) -> Address { Address::from(&self.pk) } @@ -370,7 +371,7 @@ pub mod wrapper_tx { fn test_encryption_round_trip() { let keypair = gen_keypair(); let mut wrapper = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), @@ -403,7 +404,7 @@ pub mod wrapper_tx { fn test_decryption_invalid_hash() { let keypair = gen_keypair(); let mut wrapper = - Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), @@ -438,17 +439,18 @@ pub mod wrapper_tx { fn test_malleability_attack_detection() { let keypair = gen_keypair(); // the signed tx - let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: Amount::from_uint(10, 0).expect("Test failed"), - token: nam(), - }, - keypair.ref_to(), - Epoch(0), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - )))); + let mut tx = + Tx::from_type(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: Amount::from_uint(10, 0).expect("Test failed"), + token: nam(), + }, + keypair.ref_to(), + Epoch(0), + 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())); diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index d9e578eed5..a7215c79b2 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -13,7 +13,7 @@ Also, it introduces some protocol parameters: - `min_proposal_fund` - `max_proposal_code_size` -- `min_proposal_period` +- `min_proposal_voting_period` - `max_proposal_period` - `max_proposal_content_size` - `min_proposal_grace_epochs` @@ -26,7 +26,7 @@ On-chain proposals are created under the `governance_address` storage space and, /$GovernanceAddress/counter: u64 /$GovernanceAddress/min_proposal_fund: u64 /$GovernanceAddress/max_proposal_code_size: u64 -/$GovernanceAddress/min_proposal_period: u64 +/$GovernanceAddress/min_proposal_voting_period: u64 /$GovernanceAddress/max_proposal_period: u64 /$GovernanceAddress/max_proposal_content_size: u64 /$GovernanceAddress/min_proposal_grace_epochs: u64 @@ -50,11 +50,11 @@ and follow these rules: - `$id` must be equal to `counter + 1`. - `startEpoch` must: - be greater than `currentEpoch`, where current epoch is the epoch in which the transaction is executed and included in a block - - be a multiple of `min_proposal_period`. + - be a multiple of `min_proposal_voting_period`. - `endEpoch` must: - - be at least `min_proposal_period` epochs greater than `startEpoch` + - be at least `min_proposal_voting_period` epochs greater than `startEpoch` - be at most `max_proposal_period` epochs greater than `startEpoch` - - be a multiple of `min_proposal_period` + - be a multiple of `min_proposal_voting_period` - `graceEpoch` must: - be at least `min_grace_epoch` epochs greater than `endEpoch` - `proposalCode` can be empty and must be a valid transaction with size less than `max_proposal_code_size` kibibytes. diff --git a/documentation/dev/src/specs/ledger/default-transactions.md b/documentation/dev/src/specs/ledger/default-transactions.md index fb254f0cd3..d1e36b923a 100644 --- a/documentation/dev/src/specs/ledger/default-transactions.md +++ b/documentation/dev/src/specs/ledger/default-transactions.md @@ -28,7 +28,7 @@ Transparently transfer `amount` of fungible `token` from the `source` to the `ta Attach [Transfer](../encoding.md#transfer) to the `data`. -### tx_update_vp +### tx_update_account Update a validity predicate of an established account. diff --git a/encoding_spec/src/main.rs b/encoding_spec/src/main.rs index b9e2b034ef..5889b03b8d 100644 --- a/encoding_spec/src/main.rs +++ b/encoding_spec/src/main.rs @@ -70,10 +70,13 @@ fn main() -> Result<(), Box> { let public_key_schema = PublicKey::schema_container(); // TODO update after let signature_schema = Signature::schema_container(); - let init_account_schema = transaction::InitAccount::schema_container(); - let init_validator_schema = transaction::InitValidator::schema_container(); + let init_account_schema = + transaction::account::InitAccount::schema_container(); + let init_validator_schema = + transaction::pos::InitValidator::schema_container(); let token_transfer_schema = token::Transfer::schema_container(); - let update_vp_schema = transaction::UpdateVp::schema_container(); + let update_account = + transaction::account::UpdateAccount::schema_container(); let pos_bond_schema = pos::Bond::schema_container(); let pos_withdraw_schema = pos::Withdraw::schema_container(); let wrapper_tx_schema = transaction::WrapperTx::schema_container(); @@ -98,7 +101,7 @@ fn main() -> Result<(), Box> { definitions.extend(init_account_schema.definitions); definitions.extend(init_validator_schema.definitions); definitions.extend(token_transfer_schema.definitions); - definitions.extend(update_vp_schema.definitions); + definitions.extend(update_account.definitions); definitions.extend(pos_bond_schema.definitions); definitions.extend(pos_withdraw_schema.definitions); definitions.extend(wrapper_tx_schema.definitions); @@ -179,11 +182,11 @@ fn main() -> Result<(), Box> { ).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/token/struct.Transfer.html"); tables.push(token_transfer_table); - let update_vp_definition = - definitions.remove(&update_vp_schema.declaration).unwrap(); - let update_vp_table = - definition_to_table(update_vp_schema.declaration, update_vp_definition).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/transaction/struct.UpdateVp.html"); - tables.push(update_vp_table); + let update_account_definition = + definitions.remove(&update_account.declaration).unwrap(); + let update_accoun_table = + definition_to_table(update_account.declaration, update_account_definition).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/transaction/struct.UpdateVp.html"); + tables.push(update_accoun_table); let pos_bond_definition = definitions.remove(&pos_bond_schema.declaration).unwrap(); diff --git a/genesis/dev.toml b/genesis/dev.toml index 19985a3b9e..a2320a1cf0 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -1,148 +1,203 @@ -# Example genesis with dev settings. -genesis_time = "2021-09-30:10:00.00Z" +# Developer network +genesis_time = "2021-12-20T15:00:00.00Z" native_token = "NAM" - -# A genesis validator with alias "validator". -[validator.validator] -# Validator's public key for consensus. -consensus_public_key = "5e704c4e46265e1ccc87505149f79b9d2e414d01a4e3806dfc65f0a73901c1d0" -# Public key of the validator's Namada account. -account_public_key = "5e704c4e46265e1ccc87505149f79b9d2e414d01a4e3806dfc65f0a73901c1d0" -# Address of the validator. -address = "a1qq5qqqqqgfqnsd6pxse5zdj9g5crzsf5x4zyzv6yxerr2d2rxpryzwp5g5m5zvfjxv6ygsekjmraj0" -# Validator's token balance at genesis. -tokens = 200000 -# Amount of the validator's genesis token balance which is not staked. -non_staked_balance = 100000 -# VP for the validator account -validator_vp = "vp_validator" -# Commission rate for rewards -commission_rate = "0.05" -# Maximum change per epoch in the commission rate -max_commission_rate_change = "0.01" -# Public IP:port address -net_address = "127.0.0.1:26656" +faucet_pow_difficulty = 0 +faucet_withdrawal_limit = "1000" # Some tokens present at genesis. [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" -denom = 8 +denom = 6 +vp = "vp_token" [token.NAM.balances] -# In token balances, we can use: -# 1. An address any account -a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 -atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 -atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 -# 2. An alias of any account +Albert = "1000000" +"Albert.public_key" = "100" 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 -"validator.public_key" = 100 +"Bertha.public_key" = "2000" +Christel = "1000000" +"Christel.public_key" = "100" +Daewon = "1000000" +Ester = "1000000" +faucet = "922337203685400000000" +"faucet.public_key" = "100" +[token.NAM.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target_key = "0.6667" [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" denom = 8 +vp = "vp_token" [token.BTC.balances] -atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 -atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 -atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 -a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" +[token.BTC.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target_key = "0.6667" [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" denom = 18 +vp = "vp_token" [token.ETH.balances] -atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 -atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 -atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 -a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" +[token.ETH.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target_key = "0.6667" [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" denom = 10 +vp = "vp_token" [token.DOT.balances] -atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 -atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 -atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 -a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 - -[token.schnitzel] +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" +[token.DOT.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target_key = "0.6667" + +[token.Schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" denom = 6 -[token.schnitzel.balances] -atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 -atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 -atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 -a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 - -[token.apfel] +vp = "vp_token" +[token.Schnitzel.balances] +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" +[token.Schnitzel.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target_key = "0.6667" + +[token.Apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" denom = 6 -[token.apfel.balances] -atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 -atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 -atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 -a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 - -[token.kartoffel] +vp = "vp_token" +[token.Apfel.balances] +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" +[token.Apfel.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target_key = "0.6667" + +[token.Kartoffel] address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" -denom = 6 public_key = "" -[token.kartoffel.balances] -atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 -atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 -atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 -a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 +denom = 6 +vp = "vp_token" +[token.Kartoffel.balances] +Albert = "1000000" +Bertha = "1000000" +Christel = "1000000" +Daewon = "1000000" +Ester = "1000000" +faucet = "9223372036854" +[token.Kartoffel.parameters] +max_reward_rate = "0.1" +kd_gain_nom = "0.1" +kp_gain_nom = "0.1" +locked_ratio_target_key = "0.6667" # Some established accounts present at genesis. +[established.faucet] +vp = "vp_testnet_faucet" -[established.albert] -address = "atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4" -public_key = "a57281e1dd9fd39ec3e8a162a1643ca7c836c0f2dae3bef1412a3a61a2fde1a7" +[established.Albert] vp = "vp_user" -[established.bertha] -address = "atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw" -public_key = "572512a95b190d615b1987f7072572a64951ad50f4f97ef9dbb83545c46ae600" +[established.Bertha] vp = "vp_user" -[established.christel] -address = "atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p" -public_key = "d06f8d4f897f329a50fd23ba5d2503bbe22fab2f14d5f625e07a65f617eb2778" +[established.Christel] vp = "vp_user" -# An implicit account present at genesis. +[established.masp] +address = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5" +vp = "vp_masp" + +[implicit.Daewon] + +[implicit.Ester] -# Daewon (a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz) -[implicit.daewon] -public_key = "2b5b8fda66fb6fc4ef0d86f84b21f250034077effc459fc2403a77a35aa95e3f" +# Wasm VP definitions # Wasm VP definitions -# Default user VP +# Implicit VP +[wasm.vp_implicit] +filename = "vp_implicit.wasm" + +# Default user VP in established accounts [wasm.vp_user] -# filename (relative to wasm path used by the node) filename = "vp_user.wasm" -# SHA-256 hash of the wasm file -sha256 = "dc7b97f0448f2369bd2401c3c1d8898f53cac8c464a8c1b1f7f81415a658625d" # Default validator VP [wasm.vp_validator] # filename (relative to wasm path used by the node) filename = "vp_validator.wasm" +# Faucet VP +[wasm.vp_testnet_faucet] +filename = "vp_testnet_faucet.wasm" + +# MASP VP +[wasm.vp_masp] +filename = "vp_masp.wasm" + # General protocol parameters. [parameters] # Minimum number of blocks in an epoch. -min_num_of_blocks = 10 +min_num_of_blocks = 4 # Maximum expected time per block (in seconds). max_expected_time_per_block = 30 -# Expected epochs per year (also sets the minimum duration of an epoch in seconds) -epochs_per_year = 525_600 # Max payload size, in bytes, for a tx batch proposal. max_proposal_bytes = 22020096 +# vp whitelist +vp_whitelist = [] +# tx whitelist +tx_whitelist = [] +# Implicit VP WASM name +implicit_vp = "vp_implicit" +# Expected number of epochs per year (also sets the min duration of an epoch in seconds) +epochs_per_year = 105_120 # 5 minute epochs +# The P gain factor in the Proof of Stake rewards controller +pos_gain_p = "0.1" +# The D gain factor in the Proof of Stake rewards controller +pos_gain_d = "0.1" +# The maximum number of signatures allowed per transaction +max_signatures_per_transaction = 15 # Proof of stake parameters. [pos_params] @@ -153,9 +208,9 @@ max_validator_slots = 128 pipeline_len = 2 # Unbonding length (in epochs). Validators may have their stake slashed # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. -unbonding_len = 21 +unbonding_len = 3 # Votes per fundamental staking token (namnam) -tm_votes_per_token = "1" +tm_votes_per_token = "0.1" # Reward for proposing a block. block_proposer_reward = "0.125" # Reward for voting on a block. @@ -173,18 +228,21 @@ 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] -# minimum amount of NAM token to lock +# minimum amount of nam token to lock min_proposal_fund = 500 # proposal code size in bytes -max_proposal_code_size = 300000 -# min proposal period length in epochs -min_proposal_period = 3 +max_proposal_code_size = 500000 +# min proposal voting period length in epochs +min_proposal_voting_period = 3 # max proposal period length in epochs max_proposal_period = 27 # maximum number of characters in the proposal content -max_proposal_content_size = 5000 +max_proposal_content_size = 10000 # minimum epochs between end and grace epoch -min_proposal_grace_epochs = 6 +min_proposal_grace_epochs = 6 \ No newline at end of file diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 83f02b3c8e..b2596c6767 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -4,7 +4,7 @@ genesis_time = "2021-09-30T10:00:00Z" native_token = "NAM" -faucet_pow_difficulty = 1 +faucet_pow_difficulty = 0 faucet_withdrawal_limit = "1000" [validator.validator-0] @@ -174,6 +174,8 @@ epochs_per_year = 31_536_000 pos_gain_p = "0.1" # The D gain factor in the Proof of Stake rewards controller pos_gain_d = "0.1" +# The maximum number of signatures allowed per transaction +max_signatures_per_transaction = 15 # Proof of stake parameters. [pos_params] @@ -215,7 +217,7 @@ min_proposal_fund = 500 # proposal code size in bytes max_proposal_code_size = 1000000 # min proposal period length in epochs -min_proposal_period = 3 +min_proposal_voting_period = 3 # max proposal period length in epochs max_proposal_period = 27 # maximum number of characters in the proposal content diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 4c087dbbd6..c18a626268 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -869,7 +869,7 @@ pub fn is_validator( address: &Address, ) -> storage_api::Result where - S: StorageRead + StorageWrite, + S: StorageRead, { let rate = read_validator_max_commission_rate_change(storage, address)?; Ok(rate.is_some()) @@ -3927,11 +3927,8 @@ where // Check that the validator is jailed up to the pipeline epoch for epoch in current_epoch.iter_range(params.pipeline_len + 1) { - let state = validator_state_handle(validator).get( - storage, - current_epoch, - ¶ms, - )?; + let state = + validator_state_handle(validator).get(storage, epoch, ¶ms)?; if let Some(state) = state { if state != ValidatorState::Jailed { return Err(UnjailValidatorError::NotJailed( diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index 6476827417..b7463c8ea5 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -51,9 +51,10 @@ use crate::{ staking_token_address, store_total_consensus_stake, total_deltas_handle, unbond_handle, unbond_tokens, unjail_validator, update_validator_deltas, update_validator_set, validator_consensus_key_handle, - validator_set_update_tendermint, validator_slashes_handle, - validator_state_handle, withdraw_tokens, write_validator_address_raw_hash, - BecomeValidator, STORE_VALIDATOR_SETS_LEN, + validator_set_positions_handle, validator_set_update_tendermint, + validator_slashes_handle, validator_state_handle, withdraw_tokens, + write_validator_address_raw_hash, BecomeValidator, + STORE_VALIDATOR_SETS_LEN, }; proptest! { @@ -124,6 +125,22 @@ proptest! { } } +proptest! { + // Generate arb valid input for `test_unjail_validator_aux` + #![proptest_config(Config { + cases: 5, + .. Config::default() + })] + #[test] + fn test_unjail_validator( + (pos_params, genesis_validators) + in arb_params_and_genesis_validators(Some(4),6..9) + ) { + test_unjail_validator_aux(pos_params, + genesis_validators) + } +} + fn arb_params_and_genesis_validators( num_max_validator_slots: Option, val_size: Range, @@ -2112,3 +2129,127 @@ fn arb_genesis_validators( }, ) } + +fn test_unjail_validator_aux( + params: PosParams, + mut validators: Vec, +) { + println!("\nTest inputs: {params:?}, genesis validators: {validators:#?}"); + let mut s = TestWlStorage::default(); + + // Find the validator with the most stake and 100x his stake to keep the + // cubic slash rate small + let num_vals = validators.len(); + validators.sort_by_key(|a| a.tokens); + validators[num_vals - 1].tokens = 100 * validators[num_vals - 1].tokens; + + // Get second highest stake validator tomisbehave + let val_addr = &validators[num_vals - 2].address; + let val_tokens = validators[num_vals - 2].tokens; + println!( + "Validator that will misbehave addr {val_addr}, tokens {}", + val_tokens.to_string_native() + ); + + // Genesis + let mut current_epoch = s.storage.block.epoch; + init_genesis( + &mut s, + ¶ms, + validators.clone().into_iter(), + current_epoch, + ) + .unwrap(); + s.commit_block().unwrap(); + + current_epoch = advance_epoch(&mut s, ¶ms); + super::process_slashes(&mut s, current_epoch).unwrap(); + + // Discover first slash + let slash_0_evidence_epoch = current_epoch; + let evidence_block_height = BlockHeight(0); // doesn't matter for slashing logic + let slash_0_type = SlashType::DuplicateVote; + slash( + &mut s, + ¶ms, + current_epoch, + slash_0_evidence_epoch, + evidence_block_height, + slash_0_type, + val_addr, + current_epoch.next(), + ) + .unwrap(); + + assert_eq!( + validator_state_handle(val_addr) + .get(&s, current_epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Consensus) + ); + + for epoch in Epoch::iter_bounds_inclusive( + current_epoch.next(), + current_epoch + params.pipeline_len, + ) { + // Check the validator state + assert_eq!( + validator_state_handle(val_addr) + .get(&s, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + // Check the validator set positions + assert!( + validator_set_positions_handle() + .at(&epoch) + .get(&s, val_addr) + .unwrap() + .is_none(), + ); + } + + // Advance past an epoch in which we can unbond + let unfreeze_epoch = + slash_0_evidence_epoch + params.slash_processing_epoch_offset(); + while current_epoch < unfreeze_epoch + 4u64 { + current_epoch = advance_epoch(&mut s, ¶ms); + super::process_slashes(&mut s, current_epoch).unwrap(); + } + + // Unjail the validator + unjail_validator(&mut s, val_addr, current_epoch).unwrap(); + + // Check the validator state + for epoch in + Epoch::iter_bounds_inclusive(current_epoch, current_epoch.next()) + { + assert_eq!( + validator_state_handle(val_addr) + .get(&s, epoch, ¶ms) + .unwrap(), + Some(ValidatorState::Jailed) + ); + } + + assert_eq!( + validator_state_handle(val_addr) + .get(&s, current_epoch + params.pipeline_len, ¶ms) + .unwrap(), + Some(ValidatorState::Consensus) + ); + assert!( + validator_set_positions_handle() + .at(&(current_epoch + params.pipeline_len)) + .get(&s, val_addr) + .unwrap() + .is_some(), + ); + + // Advance another epoch + current_epoch = advance_epoch(&mut s, ¶ms); + super::process_slashes(&mut s, current_epoch).unwrap(); + + let second_att = unjail_validator(&mut s, val_addr, current_epoch); + assert!(second_att.is_err()); +} diff --git a/scripts/generator.sh b/scripts/generator.sh index 701f8fbabe..3fe1792a49 100755 --- a/scripts/generator.sh +++ b/scripts/generator.sh @@ -212,79 +212,79 @@ elif [ "$1" = "client" ]; then # 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 -- 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 + cargo run --bin namadac --features std -- 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)') + PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- 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 -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --address 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 -- vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote nay --address 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 + cargo run --bin namadac --features std -- vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --address 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 -- 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 -- 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 -- 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 + cargo run --bin namadac --features std -- vote-proposal --data-path proposal_offline_proposal --vote yay --address 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 -- 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 + cargo run --bin namadac --features std -- 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)') + PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- 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 -- vote-proposal --force --proposal-id 0 --vote yay --eth '011586062748ba53bc53155e817ec1ea708de75878dcb9a5713bf6986d87fe14e7 fd34672ab5' --address 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 + cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --eth '011586062748ba53bc53155e817ec1ea708de75878dcb9a5713bf6986d87fe14e7 fd34672ab5' --address 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 -- 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 + cargo run --bin namadac --features std -- 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_0=$(cargo run --bin namadac --features std -- 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)') + PROPOSAL_ID_1=$(cargo run --bin namadac --features std -- 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 -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --pgf "$ALBERT_ADDRESS 1000" --address 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 -- vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --pgf "$ALBERT_ADDRESS 900" --address 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 + cargo run --bin namadac --features std -- vote-proposal --force --proposal-id $PROPOSAL_ID_1 --vote yay --pgf "$ALBERT_ADDRESS 900" --address 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 -- transfer --source bertha --target christel --token btc --amount 23 --force --signing-keys 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 -- bond --validator bertha --amount 25 --signing-keys 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 -- 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 -- reveal-pk --public-key albert-key --gas-payer 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 + cargo run --bin namadac --features std -- update-account --code-path vp_user.wasm --address bertha --signing-keys bertha-key --force --ledger-address 127.0.0.1:27657 - cargo run --bin namadac --features std -- init-validator --alias bertha-validator --source bertha --commission-rate 0.05 --max-commission-rate-change 0.01 --signing-key bertha-key --unsafe-dont-encrypt --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- init-validator --alias bertha-validator --account-keys bertha --commission-rate 0.05 --max-commission-rate-change 0.01 --signing-keys bertha-key --unsafe-dont-encrypt --force --ledger-address 127.0.0.1:27657 - cargo run --bin namadac --features std -- unbond --validator christel --amount 5 --signing-key christel-key --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- unbond --validator christel --amount 5 --signing-keys christel-key --force --ledger-address 127.0.0.1:27657 - cargo run --bin namadac --features std -- withdraw --validator albert --signing-key albert-key --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- withdraw --validator albert --signing-keys albert-key --force --ledger-address 127.0.0.1:27657 - cargo run --bin namadac --features std -- init-account --alias albert-account --source albert --public-key albert-key --signing-key albert-key --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- init-account --alias albert-account --public-keys albert-key --signing-keys albert-key --force --ledger-address 127.0.0.1:27657 - cargo run --bin namadac --features std -- tx --code-path $NAMADA_DIR/wasm_for_tests/tx_no_op.wasm --data-path README.md --signing-key albert-key --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- tx --code-path $NAMADA_DIR/wasm_for_tests/tx_no_op.wasm --data-path README.md --signing-keys albert-key --owner albert --force --ledger-address 127.0.0.1:27657 - 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 --features std -- ibc-transfer --source bertha --receiver christel --token btc --amount 24 --channel-id channel-141 --signing-keys bertha-key --force --ledger-address 127.0.0.1:27657 cargo run --bin namadaw -- masp add --alias a_spending_key --value xsktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxcvedhsv --unsafe-dont-encrypt diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 04b92401e1..2d426d5510 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -99,9 +99,13 @@ pub struct TxCustom { /// Common tx arguments pub tx: Tx, /// Path to the tx WASM code file - pub code_path: C::Data, + pub code_path: Option, /// Path to the data file pub data_path: Option, + /// Path to the serialized transaction + pub serialized_tx: Option, + /// The address that correspond to the signatures/signing-keys + pub owner: C::Address, } /// Transfer transaction arguments @@ -161,19 +165,57 @@ pub struct TxIbcTransfer { pub tx_code_path: PathBuf, } +/// Transaction to initialize create a new proposal +#[derive(Clone, Debug)] +pub struct InitProposal { + /// Common tx arguments + pub tx: Tx, + /// The proposal data + pub proposal_data: C::Data, + /// Native token address + pub native_token: C::NativeAddress, + /// Flag if proposal should be run offline + pub is_offline: bool, + /// Flag if proposal is of type Pgf stewards + pub is_pgf_stewards: bool, + /// Flag if proposal is of type Pgf funding + pub is_pgf_funding: bool, + /// Path to the tx WASM file + pub tx_code_path: PathBuf, +} + +/// Transaction to vote on a proposal +#[derive(Clone, Debug)] +pub struct VoteProposal { + /// Common tx arguments + pub tx: Tx, + /// Proposal id + pub proposal_id: Option, + /// The vote + pub vote: String, + /// The address of the voter + pub voter: C::Address, + /// Flag if proposal vote should be run offline + pub is_offline: bool, + /// The proposal file path + pub proposal_data: Option, + /// Path to the TX WASM code file + pub tx_code_path: PathBuf, +} + /// Transaction to initialize a new account #[derive(Clone, Debug)] pub struct TxInitAccount { /// Common tx arguments pub tx: Tx, - /// Address of the source account - pub source: C::Address, /// Path to the VP WASM code file for the new account pub vp_code_path: PathBuf, /// Path to the TX WASM code file pub tx_code_path: PathBuf, /// Public key for the new account - pub public_key: C::PublicKey, + pub public_keys: Vec, + /// The account multisignature threshold + pub threshold: Option, } /// Transaction to initialize a new account @@ -181,12 +223,12 @@ pub struct TxInitAccount { pub struct TxInitValidator { /// Common tx arguments pub tx: Tx, - /// Source - pub source: C::Address, /// Signature scheme pub scheme: SchemeType, - /// Account key - pub account_key: Option, + /// Account keys + pub account_keys: Vec, + /// The account multisignature threshold + pub threshold: Option, /// Consensus key pub consensus_key: Option, /// Ethereum cold key @@ -209,15 +251,19 @@ pub struct TxInitValidator { /// Transaction to update a VP arguments #[derive(Clone, Debug)] -pub struct TxUpdateVp { +pub struct TxUpdateAccount { /// Common tx arguments pub tx: Tx, /// Path to the VP WASM code file - pub vp_code_path: PathBuf, + pub vp_code_path: Option, /// Path to the TX WASM code file pub tx_code_path: PathBuf, /// Address of the account whose VP is to be updated pub addr: C::Address, + /// Public keys + pub public_keys: Vec, + /// The account threshold + pub threshold: Option, } /// Bond arguments @@ -279,6 +325,13 @@ pub struct QueryProtocolParameters { pub query: Query, } +/// Query pgf data +#[derive(Clone, Debug)] +pub struct QueryPgf { + /// Common query args + pub query: Query, +} + /// Withdraw arguments #[derive(Clone, Debug)] pub struct Withdraw { @@ -304,6 +357,15 @@ pub struct QueryConversions { pub epoch: Option, } +/// Query token balance(s) +#[derive(Clone, Debug)] +pub struct QueryAccount { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: C::Address, +} + /// Query token balance(s) #[derive(Clone, Debug)] pub struct QueryBalance { @@ -385,6 +447,17 @@ pub struct TxUnjailValidator { pub tx_code_path: PathBuf, } +#[derive(Clone, Debug)] +/// Sign a transaction offline +pub struct SignTx { + /// Common tx arguments + pub tx: Tx, + /// Transaction data + pub tx_data: C::Data, + /// The account address + pub owner: C::Address, +} + /// Query PoS commission rate #[derive(Clone, Debug)] pub struct QueryCommissionRate { @@ -437,8 +510,10 @@ pub struct QueryRawBytes { pub struct Tx { /// Simulate applying the transaction pub dry_run: bool, - /// Dump the transaction bytes + /// Dump the transaction bytes to file pub dump_tx: bool, + /// The output directory path to where serialize the data + pub output_folder: Option, /// Submit the transaction even if it doesn't pass client checks pub force: bool, /// Do not wait for the transaction to be added to the blockchain @@ -451,10 +526,12 @@ pub struct Tx { /// Whether to force overwrite the above alias, if it is provided, in the /// wallet. pub wallet_alias_force: bool, + /// The fee payer signing key + pub gas_payer: Option, /// The amount being payed to include the transaction - pub fee_amount: InputAmount, + pub gas_amount: InputAmount, /// The token in which the fee is being paid - pub fee_token: C::Address, + pub gas_token: C::Address, /// The max amount of gas used to process tx pub gas_limit: GasLimit, /// The optional expiration of the transaction @@ -462,9 +539,9 @@ pub struct Tx { /// The chain id for which the transaction is intended pub chain_id: Option, /// Sign the tx with the key for the given alias from your wallet - pub signing_key: Option, - /// Sign the tx with the keypair of the public key of the given address - pub signer: Option, + pub signing_keys: Vec, + /// List of signatures to attach to the transaction + pub signatures: Vec, /// Path to the TX WASM code file to reveal PK pub tx_reveal_code_path: PathBuf, /// Sign the tx with the public key for the given alias from your wallet @@ -635,11 +712,11 @@ pub struct EthereumBridgePool { /// The amount to be transferred pub amount: InputAmount, /// The amount of fees (in NAM) - pub gas_amount: token::Amount, + pub fee_amount: token::Amount, /// The account of fee payer. - pub gas_payer: C::Address, + pub fee_payer: C::Address, /// Path to the tx WASM code file - pub code_path: C::Data, + pub code_path: PathBuf, } /// Bridge pool proof arguments. diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 30b1f4e171..5b8a8eaa5e 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -18,11 +18,9 @@ use crate::eth_bridge::ethers::abi::AbiDecode; use crate::eth_bridge::structs::RelayProof; use crate::ledger::args; use crate::ledger::queries::{Client, RPC}; -use crate::ledger::rpc::validate_amount; -use crate::ledger::signing::TxSigningKey; +use crate::ledger::rpc::{query_wasm_code_hash, validate_amount}; use crate::ledger::tx::{prepare_tx, Error}; -use crate::ledger::wallet::{Wallet, WalletUtils}; -use crate::proto::{Code, Data, Tx}; +use crate::proto::Tx; use crate::types::address::Address; use crate::types::control_flow::time::{Duration, Instant}; use crate::types::control_flow::{ @@ -34,66 +32,60 @@ use crate::types::eth_bridge_pool::{ }; use crate::types::keccak::KeccakHash; use crate::types::token::{Amount, DenominatedAmount}; -use crate::types::transaction::TxType; use crate::types::voting_power::FractionalVotingPower; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. -pub async fn build_bridge_pool_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_bridge_pool_tx( client: &C, - wallet: &mut Wallet, - args: args::EthereumBridgePool, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let args::EthereumBridgePool { - ref tx, + args::EthereumBridgePool { + tx: tx_args, asset, recipient, sender, amount, - gas_amount, - gas_payer, - code_path: wasm_code, - } = args; + fee_amount, + fee_payer, + code_path, + }: args::EthereumBridgePool, + gas_payer: common::PublicKey, +) -> Result { let DenominatedAmount { amount, .. } = - validate_amount(client, amount, &BRIDGE_ADDRESS, tx.force) + validate_amount(client, amount, &BRIDGE_ADDRESS, tx_args.force) .await .expect("Failed to validate amount"); + let transfer = PendingTransfer { transfer: TransferToEthereum { asset, recipient, - sender, + sender: sender.clone(), amount, }, gas_fee: GasFee { - amount: gas_amount, - payer: gas_payer, + amount: fee_amount, + payer: fee_payer, }, }; - let mut transfer_tx = Tx::new(TxType::Raw); - transfer_tx.header.chain_id = tx.chain_id.clone().unwrap(); - transfer_tx.header.expiration = tx.expiration; - transfer_tx.set_data(Data::new( - transfer - .try_to_vec() - .expect("Serializing tx should not fail"), - )); - // TODO: change the wasm code to a hash - transfer_tx.set_code(Code::new(wasm_code)); - - prepare_tx::( + let tx_code_hash = + query_wasm_code_hash(client, code_path.to_str().unwrap()) + .await + .unwrap(); + + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + tx.add_code_from_hash(tx_code_hash).add_data(transfer); + + prepare_tx::( client, - wallet, - tx, - transfer_tx, - TxSigningKey::None, + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + Ok(tx) } /// A json serializable representation of the Ethereum diff --git a/shared/src/ledger/events.rs b/shared/src/ledger/events.rs index 03e68417e2..0b5aa0253b 100644 --- a/shared/src/ledger/events.rs +++ b/shared/src/ledger/events.rs @@ -10,7 +10,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use thiserror::Error; -use crate::ledger::native_vp::governance::utils::ProposalEvent; +use crate::ledger::governance::utils::ProposalEvent; use crate::tendermint_proto::abci::EventAttribute; use crate::types::ibc::IbcEvent; #[cfg(feature = "ferveo-tpke")] @@ -50,6 +50,8 @@ pub enum EventType { Ibc(String), /// The proposal that has been executed Proposal, + /// The pgf payment + PgfPayment, } impl Display for EventType { @@ -59,6 +61,7 @@ impl Display for EventType { EventType::Applied => write!(f, "applied"), EventType::Ibc(t) => write!(f, "{}", t), EventType::Proposal => write!(f, "proposal"), + EventType::PgfPayment => write!(f, "pgf_payment"), }?; Ok(()) } @@ -72,6 +75,7 @@ impl FromStr for EventType { "accepted" => Ok(EventType::Accepted), "applied" => Ok(EventType::Applied), "proposal" => Ok(EventType::Proposal), + "pgf_payments" => Ok(EventType::PgfPayment), // IBC "update_client" => Ok(EventType::Ibc("update_client".to_string())), "send_packet" => Ok(EventType::Ibc("send_packet".to_string())), diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/governance/mod.rs similarity index 50% rename from shared/src/ledger/native_vp/governance/mod.rs rename to shared/src/ledger/governance/mod.rs index d41242a477..14dd40ae22 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -4,19 +4,23 @@ pub mod utils; use std::collections::BTreeSet; -use namada_core::ledger::governance::storage as gov_storage; -use namada_core::ledger::storage; +use borsh::BorshDeserialize; +use namada_core::ledger::governance::storage::keys as gov_storage; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::ledger::governance::storage::vote::StorageProposalVote; +use namada_core::ledger::governance::utils::is_valid_validator_voting_period; +use namada_core::ledger::storage_api::governance::is_proposal_accepted; use namada_core::ledger::vp_env::VpEnv; -use namada_core::types::governance::{ProposalVote, VoteType}; -use namada_core::types::transaction::governance::ProposalType; +use namada_core::ledger::{pgf, storage}; +use namada_core::proto::Tx; +use namada_proof_of_stake::is_validator; use thiserror::Error; -use utils::is_valid_validator_voting_period; +use self::utils::ReadType; use crate::ledger::native_vp::{Ctx, NativeVp}; use crate::ledger::storage_api::StorageRead; use crate::ledger::{native_vp, pos}; -use crate::proto::Tx; -use crate::types::address::Address; +use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Epoch, Key}; use crate::types::token; use crate::vm::WasmCacheAccess; @@ -24,11 +28,23 @@ use crate::vm::WasmCacheAccess; /// for handling Governance NativeVP errors pub type Result = std::result::Result; +/// The governance internal address +pub const ADDRESS: Address = Address::Internal(InternalAddress::Governance); + +/// The maximum number of item in a pgf proposal +pub const MAX_PGF_ACTIONS: usize = 20; + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { #[error("Native VP error: {0}")] NativeVpError(#[from] native_vp::Error), + #[error("Proposal field should not be empty: {0}")] + EmptyProposalField(String), + #[error("Vote key is not valid: {0}")] + InvalidVoteKey(String), + #[error("Vote type is not compatible with proposal type.")] + InvalidVoteType, } /// Governance VP @@ -99,13 +115,7 @@ where (KeyType::PROPOSAL_COMMIT, _) => { self.is_valid_proposal_commit() } - (KeyType::PARAMETER, _) => self.is_valid_parameter( - if let Some(data) = &tx_data.data() { - data - } else { - return false; - }, - ), + (KeyType::PARAMETER, _) => self.is_valid_parameter(tx_data), (KeyType::BALANCE, _) => self.is_valid_balance(&native_token), (KeyType::UNKNOWN_GOVERNANCE, _) => Ok(false), (KeyType::UNKNOWN, _) => Ok(true), @@ -126,10 +136,9 @@ where { fn is_valid_key_set(&self, keys: &BTreeSet) -> Result<(bool, u64)> { let counter_key = gov_storage::get_counter_key(); - let pre_counter: u64 = - self.ctx.pre().read(&counter_key)?.unwrap_or_default(); + let pre_counter: u64 = self.force_read(&counter_key, ReadType::Pre)?; let post_counter: u64 = - self.ctx.post().read(&counter_key)?.unwrap_or_default(); + self.force_read(&counter_key, ReadType::Post)?; if post_counter < pre_counter { return Ok((false, 0)); @@ -150,7 +159,7 @@ where gov_storage::get_grace_epoch_key(counter), ]); - // Check that expected set is a subset the actual one + // Check that expected set is a subset of the actual one if !keys.is_superset(&mandatory_keys) { return Ok((false, 0)); } @@ -170,122 +179,80 @@ where gov_storage::get_voting_start_epoch_key(proposal_id); let voting_end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); - let current_epoch = self.ctx.get_block_epoch().ok(); + let current_epoch = self.ctx.get_block_epoch()?; - let pre_counter: Option = self.ctx.pre().read(&counter_key)?; - let pre_voting_start_epoch: Option = - self.ctx.pre().read(&voting_start_epoch_key)?; - let pre_voting_end_epoch: Option = - self.ctx.pre().read(&voting_end_epoch_key)?; + let pre_counter: u64 = self.force_read(&counter_key, ReadType::Pre)?; + let pre_voting_start_epoch: Epoch = + self.force_read(&voting_start_epoch_key, ReadType::Pre)?; + let pre_voting_end_epoch: Epoch = + self.force_read(&voting_end_epoch_key, ReadType::Pre)?; + let proposal_type: ProposalType = + self.force_read(&proposal_type_key, ReadType::Pre)?; let voter = gov_storage::get_voter_address(key); let delegation_address = gov_storage::get_vote_delegation_address(key); - let vote: Option = self.ctx.read_post(key)?; - let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); - let proposal_type: Option = - self.ctx.read_pre(&proposal_type_key)?; - - match ( - pre_counter, - proposal_type, - vote, - voter, - delegation_address, + let vote: StorageProposalVote = self.force_read(key, ReadType::Post)?; + + let (voter_address, delegation_address) = + match (voter, delegation_address) { + (Some(voter_address), Some(delegator_address)) => { + (voter_address, delegator_address) + } + _ => return Err(Error::InvalidVoteKey(key.to_string())), + }; + + // Invalid proposal id + if pre_counter <= proposal_id { + return Ok(false); + } + + // Voted outside of voting window. We dont check for validator because + // if the proposal type is validator, we need to let + // them vote for the entire voting window. + if !self.is_valid_voting_window( current_epoch, pre_voting_start_epoch, pre_voting_end_epoch, + false, ) { - ( - Some(pre_counter), - Some(proposal_type), - Some(vote), - Some(voter_address), - Some(delegation_address), - Some(current_epoch), - Some(pre_voting_start_epoch), - Some(pre_voting_end_epoch), - ) => { - if pre_counter <= proposal_id { - // Invalid proposal id - return Ok(false); - } - if current_epoch < pre_voting_start_epoch - || current_epoch > pre_voting_end_epoch - { - // Voted outside of voting window - return Ok(false); - } + return Ok(false); + } - if let ProposalVote::Yay(vote_type) = vote { - if proposal_type != vote_type { - return Ok(false); - } - - // Vote type specific checks - if let VoteType::PGFCouncil(set) = vote_type { - // Check that all the addresses are established - for (address, _) in set { - match address { - Address::Established(_) => { - // Check that established address exists in - // storage - let vp_key = - Key::validity_predicate(&address); - if !self.ctx.has_key_pre(&vp_key)? { - return Ok(false); - } - } - _ => return Ok(false), - } - } - } else if let VoteType::ETHBridge(_sig) = vote_type { - // TODO: Check the validity of the signature with the - // governance ETH key in storage for the given validator - // - } - } + if !vote.is_compatible(&proposal_type) { + return Err(Error::InvalidVoteType); + } - match proposal_type { - ProposalType::Default(_) | ProposalType::PGFCouncil => { - if self - .is_validator( - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ) - .unwrap_or(false) - { - Ok(is_valid_validator_voting_period( - current_epoch, - pre_voting_start_epoch, - pre_voting_end_epoch, - )) - } else { - Ok(self - .is_delegator( - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ) - .unwrap_or(false)) - } - } - ProposalType::ETHBridge => Ok(self - .is_validator( - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ) - .unwrap_or(false)), - } - } - _ => Ok(false), + // first check if validator, then check if delegator + let is_validator = self + .is_validator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false); + + if is_validator { + let valid_voting_period = is_valid_validator_voting_period( + current_epoch, + pre_voting_start_epoch, + pre_voting_end_epoch, + ); + return Ok(valid_voting_period); } + + let is_delegator = self + .is_delegator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false); + Ok(is_delegator) } /// Validate a content key @@ -299,39 +266,53 @@ where return Ok(false); } - let max_content_length: Option = - self.ctx.pre().read(&max_content_length_parameter_key)?; - let post_content: Option> = - self.ctx.read_bytes_post(&content_key)?; + let max_content_length: usize = + self.force_read(&max_content_length_parameter_key, ReadType::Pre)?; + let post_content = + self.ctx.read_bytes_post(&content_key)?.unwrap_or_default(); - match (post_content, max_content_length) { - (Some(post_content), Some(max_content_length)) => { - Ok(post_content.len() < max_content_length) - } - _ => Ok(false), - } + Ok(post_content.len() < max_content_length) } /// Validate the proposal type pub fn is_valid_proposal_type(&self, proposal_id: u64) -> Result { let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); - Ok(self - .ctx - .read_post::(&proposal_type_key)? - .is_some()) + let proposal_type: ProposalType = + self.force_read(&proposal_type_key, ReadType::Post)?; + + match proposal_type { + ProposalType::PGFSteward(stewards) => { + Ok(stewards.len() < MAX_PGF_ACTIONS) + } + ProposalType::PGFPayment(payments) => { + if payments.len() < MAX_PGF_ACTIONS { + return Ok(true); + } + let stewards_key = pgf::storage::keys::get_stewards_key(); + let author_key = gov_storage::get_author_key(proposal_id); + + let author: Option
= + self.ctx.pre().read(&author_key)?; + let stewards: BTreeSet
= + self.force_read(&stewards_key, ReadType::Pre)?; + + match author { + Some(address) => Ok(stewards.contains(&address)), + None => Ok(false), + } + } + _ => Ok(true), // default proposal + } } /// Validate a proposal code pub fn is_valid_proposal_code(&self, proposal_id: u64) -> Result { - let proposal_type_key: Key = - gov_storage::get_proposal_type_key(proposal_id); - let proposal_type: Option = - self.ctx.read_post(&proposal_type_key)?; + let proposal_type_key = gov_storage::get_proposal_type_key(proposal_id); + let proposal_type: ProposalType = + self.force_read(&proposal_type_key, ReadType::Post)?; - // Check that the proposal type admits wasm code - match proposal_type { - Some(ProposalType::Default(_)) => (), - _ => return Ok(false), + if !proposal_type.is_default() { + return Ok(false); } let code_key = gov_storage::get_proposal_code_key(proposal_id); @@ -343,22 +324,21 @@ where return Ok(false); } - let max_proposal_length: Option = - self.ctx.pre().read(&max_code_size_parameter_key)?; - let post_code: Option> = self.ctx.read_bytes_post(&code_key)?; + let max_proposal_length: usize = + self.force_read(&max_code_size_parameter_key, ReadType::Pre)?; + let post_code: Vec = + self.ctx.read_bytes_post(&code_key)?.unwrap_or_default(); - match (post_code, max_proposal_length) { - (Some(post_code), Some(max_content_length)) => { - Ok(post_code.len() < max_content_length) - } - _ => Ok(false), - } + Ok(post_code.len() <= max_proposal_length) } /// Validate a grace_epoch key pub fn is_valid_grace_epoch(&self, proposal_id: u64) -> Result { + let start_epoch_key = + gov_storage::get_voting_start_epoch_key(proposal_id); let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); let grace_epoch_key = gov_storage::get_grace_epoch_key(proposal_id); + let max_proposal_period = gov_storage::get_max_proposal_period_key(); let min_grace_epoch_key = gov_storage::get_min_proposal_grace_epoch_key(); @@ -367,27 +347,32 @@ where return Ok(false); } - let end_epoch: Option = self.ctx.post().read(&end_epoch_key)?; - let grace_epoch: Option = - self.ctx.post().read(&grace_epoch_key)?; - let min_grace_epoch: Option = - self.ctx.pre().read(&min_grace_epoch_key)?; - match (min_grace_epoch, grace_epoch, end_epoch) { - (Some(min_grace_epoch), Some(grace_epoch), Some(end_epoch)) => { - let committing_epoch_key = - gov_storage::get_committing_proposals_key( - proposal_id, - grace_epoch, - ); - let has_post_committing_epoch = - self.ctx.has_key_post(&committing_epoch_key)?; - - Ok(has_post_committing_epoch - && end_epoch < grace_epoch - && grace_epoch - end_epoch >= min_grace_epoch) - } - _ => Ok(false), - } + let start_epoch: Epoch = + self.force_read(&start_epoch_key, ReadType::Post)?; + let end_epoch: Epoch = + self.force_read(&end_epoch_key, ReadType::Post)?; + let grace_epoch: Epoch = + self.force_read(&grace_epoch_key, ReadType::Post)?; + let min_grace_epoch: u64 = + self.force_read(&min_grace_epoch_key, ReadType::Pre)?; + let max_proposal_period: u64 = + self.force_read(&max_proposal_period, ReadType::Pre)?; + + let committing_epoch_key = gov_storage::get_committing_proposals_key( + proposal_id, + grace_epoch.into(), + ); + let has_post_committing_epoch = + self.ctx.has_key_post(&committing_epoch_key)?; + + let is_valid_grace_epoch = end_epoch < grace_epoch + && (grace_epoch - end_epoch).0 >= min_grace_epoch; + let is_valid_max_proposal_perido = start_epoch < grace_epoch + && grace_epoch.0 - start_epoch.0 <= max_proposal_period; + + Ok(has_post_committing_epoch + && is_valid_grace_epoch + && is_valid_max_proposal_perido) } /// Validate a start_epoch key @@ -396,9 +381,9 @@ where gov_storage::get_voting_start_epoch_key(proposal_id); let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); let min_period_parameter_key = - gov_storage::get_min_proposal_period_key(); + gov_storage::get_min_proposal_voting_period_key(); - let current_epoch = self.ctx.get_block_epoch().ok(); + let current_epoch = self.ctx.get_block_epoch()?; let has_pre_start_epoch = self.ctx.has_key_pre(&start_epoch_key)?; let has_pre_end_epoch = self.ctx.has_key_pre(&end_epoch_key)?; @@ -407,27 +392,19 @@ where return Ok(false); } - let start_epoch: Option = - self.ctx.post().read(&start_epoch_key)?; - let end_epoch: Option = self.ctx.post().read(&end_epoch_key)?; - let min_period: Option = - self.ctx.pre().read(&min_period_parameter_key)?; - - match (min_period, start_epoch, end_epoch, current_epoch) { - ( - Some(min_period), - Some(start_epoch), - Some(end_epoch), - Some(current_epoch), - ) => { - if end_epoch <= start_epoch || start_epoch <= current_epoch { - return Ok(false); - } - Ok((end_epoch - start_epoch) % min_period == 0 - && (end_epoch - start_epoch).0 >= min_period) - } - _ => Ok(false), + let start_epoch: Epoch = + self.force_read(&start_epoch_key, ReadType::Post)?; + let end_epoch: Epoch = + self.force_read(&end_epoch_key, ReadType::Post)?; + let min_period: u64 = + self.force_read(&min_period_parameter_key, ReadType::Pre)?; + + if end_epoch <= start_epoch || start_epoch <= current_epoch { + return Ok(false); } + + Ok((end_epoch - start_epoch) % min_period == 0 + && (end_epoch - start_epoch).0 >= min_period) } /// Validate a end_epoch key @@ -436,11 +413,11 @@ where gov_storage::get_voting_start_epoch_key(proposal_id); let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); let min_period_parameter_key = - gov_storage::get_min_proposal_period_key(); + gov_storage::get_min_proposal_voting_period_key(); let max_period_parameter_key = gov_storage::get_max_proposal_period_key(); - let current_epoch = self.ctx.get_block_epoch().ok(); + let current_epoch = self.ctx.get_block_epoch()?; let has_pre_start_epoch = self.ctx.has_key_pre(&start_epoch_key)?; let has_pre_end_epoch = self.ctx.has_key_pre(&end_epoch_key)?; @@ -449,36 +426,21 @@ where return Ok(false); } - let start_epoch: Option = - self.ctx.post().read(&start_epoch_key)?; - let end_epoch: Option = self.ctx.post().read(&end_epoch_key)?; - let min_period: Option = - self.ctx.pre().read(&min_period_parameter_key)?; - let max_period: Option = - self.ctx.pre().read(&max_period_parameter_key)?; - match ( - min_period, - max_period, - start_epoch, - end_epoch, - current_epoch, - ) { - ( - Some(min_period), - Some(max_period), - Some(start_epoch), - Some(end_epoch), - Some(current_epoch), - ) => { - if end_epoch <= start_epoch || start_epoch <= current_epoch { - return Ok(false); - } - Ok((end_epoch - start_epoch) % min_period == 0 - && (end_epoch - start_epoch).0 >= min_period - && (end_epoch - start_epoch).0 <= max_period) - } - _ => Ok(false), + let start_epoch: Epoch = + self.force_read(&start_epoch_key, ReadType::Post)?; + let end_epoch: Epoch = + self.force_read(&end_epoch_key, ReadType::Post)?; + let min_period: u64 = + self.force_read(&min_period_parameter_key, ReadType::Pre)?; + let max_period: u64 = + self.force_read(&max_period_parameter_key, ReadType::Pre)?; + + if end_epoch <= start_epoch || start_epoch <= current_epoch { + return Ok(false); } + Ok((end_epoch - start_epoch) % min_period == 0 + && (end_epoch - start_epoch).0 >= min_period + && (end_epoch - start_epoch).0 <= max_period) } /// Validate a funds key @@ -492,33 +454,20 @@ where token::balance_key(native_token_address, self.ctx.address); let min_funds_parameter_key = gov_storage::get_min_proposal_fund_key(); - let min_funds_parameter: Option = - self.ctx.pre().read(&min_funds_parameter_key)?; + let min_funds_parameter: token::Amount = + self.force_read(&min_funds_parameter_key, ReadType::Pre)?; let pre_balance: Option = self.ctx.pre().read(&balance_key)?; - let post_balance: Option = - self.ctx.post().read(&balance_key)?; - let post_funds: Option = - self.ctx.post().read(&funds_key)?; - - match (min_funds_parameter, pre_balance, post_balance, post_funds) { - ( - Some(min_funds_parameter), - Some(pre_balance), - Some(post_balance), - Some(post_funds), - ) => Ok(post_funds >= min_funds_parameter - && post_balance - pre_balance == post_funds), - ( - Some(min_funds_parameter), - None, - Some(post_balance), - Some(post_funds), - ) => { - Ok(post_funds >= min_funds_parameter - && post_balance == post_funds) - } - _ => Ok(false), + let post_balance: token::Amount = + self.force_read(&balance_key, ReadType::Post)?; + let post_funds: token::Amount = + self.force_read(&funds_key, ReadType::Post)?; + + if let Some(pre_balance) = pre_balance { + Ok(post_funds >= min_funds_parameter + && post_balance - pre_balance == post_funds) + } else { + Ok(post_funds >= min_funds_parameter && post_balance == post_funds) } } @@ -528,24 +477,19 @@ where token::balance_key(native_token_address, self.ctx.address); let min_funds_parameter_key = gov_storage::get_min_proposal_fund_key(); - let min_funds_parameter: Option = - self.ctx.pre().read(&min_funds_parameter_key)?; let pre_balance: Option = self.ctx.pre().read(&balance_key)?; - let post_balance: Option = - self.ctx.post().read(&balance_key)?; - - match (min_funds_parameter, pre_balance, post_balance) { - ( - Some(min_funds_parameter), - Some(pre_balance), - Some(post_balance), - ) => Ok(post_balance > pre_balance - && post_balance - pre_balance >= min_funds_parameter), - (Some(min_funds_parameter), None, Some(post_balance)) => { - Ok(post_balance >= min_funds_parameter) - } - _ => Ok(false), + + let min_funds_parameter: token::Amount = + self.force_read(&min_funds_parameter_key, ReadType::Pre)?; + let post_balance: token::Amount = + self.force_read(&balance_key, ReadType::Post)?; + + if let Some(pre_balance) = pre_balance { + Ok(post_balance > pre_balance + && post_balance - pre_balance >= min_funds_parameter) + } else { + Ok(post_balance >= min_funds_parameter) } } @@ -558,70 +502,61 @@ where let author_key = gov_storage::get_author_key(proposal_id); let has_pre_author = self.ctx.has_key_pre(&author_key)?; - if has_pre_author { return Ok(false); } - let author = self.ctx.post().read(&author_key)?; + let author = self.force_read(&author_key, ReadType::Post)?; match author { - Some(author) => match author { - Address::Established(_) => { - let address_exist_key = Key::validity_predicate(&author); - let address_exist = - self.ctx.has_key_post(&address_exist_key)?; + Address::Established(_) => { + let address_exist_key = Key::validity_predicate(&author); + let address_exist = + self.ctx.has_key_post(&address_exist_key)?; - Ok(address_exist && verifiers.contains(&author)) - } - Address::Implicit(_) => Ok(verifiers.contains(&author)), - Address::Internal(_) => Ok(false), - }, - _ => Ok(false), + Ok(address_exist && verifiers.contains(&author)) + } + Address::Implicit(_) => Ok(verifiers.contains(&author)), + Address::Internal(_) => Ok(false), } } /// Validate a counter key pub fn is_valid_counter(&self, set_count: u64) -> Result { let counter_key = gov_storage::get_counter_key(); - let pre_counter: Option = self.ctx.pre().read(&counter_key)?; - let post_counter: Option = self.ctx.post().read(&counter_key)?; + let pre_counter: u64 = self.force_read(&counter_key, ReadType::Pre)?; + let post_counter: u64 = + self.force_read(&counter_key, ReadType::Post)?; - match (pre_counter, post_counter) { - (Some(pre_counter), Some(post_counter)) => { - Ok(pre_counter + set_count == post_counter) - } - _ => Ok(false), - } + Ok(pre_counter + set_count == post_counter) } /// Validate a commit key pub fn is_valid_proposal_commit(&self) -> Result { let counter_key = gov_storage::get_counter_key(); - let pre_counter: Option = self.ctx.pre().read(&counter_key)?; - let post_counter: Option = self.ctx.post().read(&counter_key)?; - - match (pre_counter, post_counter) { - (Some(pre_counter), Some(post_counter)) => { - // NOTE: can't do pre_counter + set_count == post_counter here - // because someone may update an empty proposal that just - // register a committing key causing a bug - Ok(pre_counter < post_counter) - } - _ => Ok(false), - } + let pre_counter: u64 = self.force_read(&counter_key, ReadType::Pre)?; + let post_counter: u64 = + self.force_read(&counter_key, ReadType::Post)?; + + // NOTE: can't do pre_counter + set_count == post_counter here + // because someone may update an empty proposal that just + // register a committing key causing a bug + Ok(pre_counter < post_counter) } /// Validate a governance parameter - pub fn is_valid_parameter(&self, tx_data: &[u8]) -> Result { - utils::is_proposal_accepted(&self.ctx.pre(), tx_data) - .map_err(Error::NativeVpError) + pub fn is_valid_parameter(&self, tx: &Tx) -> Result { + match tx.data() { + Some(data) => is_proposal_accepted(&self.ctx.pre(), data.as_ref()) + .map_err(Error::NativeVpError), + None => Ok(true), + } } /// Check if a vote is from a validator pub fn is_validator( &self, - epoch: Epoch, + _epoch: Epoch, verifiers: &BTreeSet
, address: &Address, delegation_address: &Address, @@ -631,23 +566,47 @@ where H: 'static + storage::StorageHasher, CA: 'static + WasmCacheAccess, { - let all_validators = - pos::namada_proof_of_stake::read_all_validator_addresses( - &self.ctx.pre(), - epoch, - )?; - if !all_validators.is_empty() { - let is_voter_validator = all_validators - .into_iter() - .any(|validator| validator.eq(address)); - let is_signer_validator = verifiers.contains(address); - let is_delegation_address = delegation_address.eq(address); - - Ok(is_voter_validator - && is_signer_validator - && is_delegation_address) + if !address.eq(delegation_address) { + return Ok(false); + } + + let is_validator = is_validator(&self.ctx.pre(), address)?; + + Ok(is_validator && verifiers.contains(address)) + } + + /// Private method to read from storage data that are 100% in storage. + fn force_read(&self, key: &Key, read_type: ReadType) -> Result + where + T: BorshDeserialize, + { + let res = match read_type { + ReadType::Pre => self.ctx.pre().read::(key), + ReadType::Post => self.ctx.post().read::(key), + }?; + + if let Some(data) = res { + Ok(data) } else { - Ok(false) + Err(Error::EmptyProposalField(key.to_string())) + } + } + + fn is_valid_voting_window( + &self, + current_epoch: Epoch, + start_epoch: Epoch, + end_epoch: Epoch, + is_validator: bool, + ) -> bool { + if is_validator { + is_valid_validator_voting_period( + current_epoch, + start_epoch, + end_epoch, + ) + } else { + current_epoch >= start_epoch && current_epoch <= end_epoch } } @@ -659,10 +618,6 @@ where address: &Address, delegation_address: &Address, ) -> Result { - // let bond_key = pos::bond_key(&BondId { - // source: address.clone(), - // validator: delegation_address.clone(), - // }); let bond_handle = pos::namada_proof_of_stake::bond_handle( address, delegation_address, @@ -670,7 +625,6 @@ where let params = pos::namada_proof_of_stake::read_pos_params(&self.ctx.pre())?; let bond = bond_handle.get_sum(&self.ctx.pre(), epoch, ¶ms)?; - // let bonds: Option = self.ctx.pre().read(&bond_key)?; if bond.is_some() && verifiers.contains(address) { Ok(true) @@ -681,7 +635,7 @@ where } #[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Debug)] +#[derive(Debug)] enum KeyType { #[allow(non_camel_case_types)] COUNTER, diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs new file mode 100644 index 0000000000..d4b4d1316c --- /dev/null +++ b/shared/src/ledger/governance/utils.rs @@ -0,0 +1,122 @@ +//! Governance utility functions + +use std::collections::HashMap; + +use namada_core::ledger::governance::utils::TallyResult; +use thiserror::Error; + +use crate::ledger::events::EventType; + +pub(super) enum ReadType { + Pre, + Post, +} + +/// Proposal errors +#[derive(Error, Debug)] +pub enum Error { + /// Invalid validator set deserialization + #[error("Invalid validator set")] + InvalidValidatorSet, + /// Invalid proposal field deserialization + #[error("Invalid proposal {0}")] + InvalidProposal(u64), + /// Error during tally + #[error("Error while tallying proposal: {0}")] + Tally(String), +} + +/// Proposal event definition +pub struct ProposalEvent { + /// Proposal event type + pub event_type: String, + /// Proposal event attributes + pub attributes: HashMap, +} + +impl ProposalEvent { + /// Create a proposal event + pub fn new( + event_type: String, + tally: TallyResult, + id: u64, + has_proposal_code: bool, + proposal_code_exit_status: bool, + ) -> Self { + let attributes = HashMap::from([ + ("tally_result".to_string(), tally.to_string()), + ("proposal_id".to_string(), id.to_string()), + ( + "has_proposal_code".to_string(), + (!has_proposal_code as u64).to_string(), + ), + ( + "proposal_code_exit_status".to_string(), + (!proposal_code_exit_status as u64).to_string(), + ), + ]); + Self { + event_type, + attributes, + } + } + + /// Create a new proposal event for rejected proposal + pub fn rejected_proposal_event(proposal_id: u64) -> Self { + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Rejected, + proposal_id, + false, + false, + ) + } + + /// Create a new proposal event for default proposal + pub fn default_proposal_event( + proposal_id: u64, + has_code: bool, + execution_status: bool, + ) -> Self { + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + proposal_id, + has_code, + execution_status, + ) + } + + /// Create a new proposal event for pgf stewards proposal + pub fn pgf_steward_proposal_event(proposal_id: u64, result: bool) -> Self { + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + proposal_id, + false, + result, + ) + } + + /// Create a new proposal event for pgf payments proposal + pub fn pgf_payments_proposal_event(proposal_id: u64, result: bool) -> Self { + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + proposal_id, + false, + result, + ) + } + + /// Create a new proposal event for eth proposal + pub fn eth_proposal_event(proposal_id: u64, result: bool) -> Self { + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + proposal_id, + false, + result, + ) + } +} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 37ea00b907..ec10688066 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -715,7 +715,7 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let verifiers = BTreeSet::new(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); @@ -788,14 +788,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = @@ -921,14 +917,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = @@ -1030,7 +1022,7 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); @@ -1127,14 +1119,10 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = @@ -1256,14 +1244,12 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1364,7 +1350,7 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); @@ -1449,7 +1435,7 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); @@ -1571,7 +1557,7 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); @@ -1692,7 +1678,7 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); @@ -1798,7 +1784,7 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); @@ -1902,14 +1888,12 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2044,14 +2028,12 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2232,14 +2214,12 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2379,14 +2359,12 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2530,14 +2508,12 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2682,14 +2658,12 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(keypair_1()); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index a843e050cf..d5322e9bf6 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -1,7 +1,6 @@ //! MASP verification wrappers. -use std::collections::hash_map::Entry; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{btree_map, BTreeMap, BTreeSet, HashMap, HashSet}; use std::env; use std::fmt::Debug; #[cfg(feature = "masp-tx-gen")] @@ -79,6 +78,17 @@ use crate::types::transaction::{EllipticCurve, PairingEngine, WrapperTx}; /// the default OS specific path is used. pub const ENV_VAR_MASP_PARAMS_DIR: &str = "NAMADA_MASP_PARAMS_DIR"; +/// Env var to either "save" proofs into files or to "load" them from +/// files. +pub const ENV_VAR_MASP_TEST_PROOFS: &str = "NAMADA_MASP_TEST_PROOFS"; + +/// Randomness seed for MASP integration tests to build proofs with +/// deterministic rng. +pub const ENV_VAR_MASP_TEST_SEED: &str = "NAMADA_MASP_TEST_SEED"; + +/// A directory to save serialized proofs for tests. +pub const MASP_TEST_PROOFS_DIR: &str = "test_fixtures/masp_proofs"; + /// The network to use for MASP #[cfg(feature = "mainnet")] const NETWORK: MainNetwork = MainNetwork; @@ -93,6 +103,26 @@ pub const OUTPUT_NAME: &str = "masp-output.params"; /// Convert circuit name pub const CONVERT_NAME: &str = "masp-convert.params"; +/// Shielded transfer +#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] +pub struct ShieldedTransfer { + /// Shielded transfer builder + pub builder: Builder<(), (), ExtendedFullViewingKey, ()>, + /// MASP transaction + pub masp_tx: Transaction, + /// Metadata + pub metadata: SaplingMetadata, + /// Epoch in which the transaction was created + pub epoch: Epoch, +} + +#[derive(Clone, Copy, Debug)] +enum LoadOrSaveProofs { + Load, + Save, + Neither, +} + fn load_pvks() -> ( PreparedVerifyingKey, PreparedVerifyingKey, @@ -511,7 +541,7 @@ impl From for Amount { /// Represents the amount used of different conversions pub type Conversions = - HashMap, i128)>; + BTreeMap, i128)>; /// Represents the changes that were made to a list of transparent accounts pub type TransferDelta = HashMap; @@ -531,7 +561,7 @@ pub struct ShieldedContext { /// The commitment tree produced by scanning all transactions up to tx_pos pub tree: CommitmentTree, /// Maps viewing keys to applicable note positions - pub pos_map: HashMap>, + pub pos_map: HashMap>, /// Maps a nullifier to the note position to which it applies pub nf_map: HashMap, /// Maps note positions to their corresponding notes @@ -657,7 +687,7 @@ impl ShieldedContext { ..Default::default() }; for vk in unknown_keys { - tx_ctx.pos_map.entry(vk).or_insert_with(HashSet::new); + tx_ctx.pos_map.entry(vk).or_insert_with(BTreeSet::new); } // Update this unknown shielded context until it is level with self while tx_ctx.last_txidx != self.last_txidx { @@ -931,7 +961,9 @@ impl ShieldedContext { asset_type: AssetType, conversions: &'a mut Conversions, ) { - if let Entry::Vacant(conv_entry) = conversions.entry(asset_type) { + if let btree_map::Entry::Vacant(conv_entry) = + conversions.entry(asset_type) + { // Query for the ID of the last accepted transaction if let Some((addr, denom, ep, conv, path)) = query_conversion(client, asset_type).await @@ -962,7 +994,7 @@ impl ShieldedContext { client, balance, target_epoch, - HashMap::new(), + BTreeMap::new(), ) .await .0; @@ -1142,7 +1174,7 @@ impl ShieldedContext { Conversions, ) { // Establish connection with which to do exchange rate queries - let mut conversions = HashMap::new(); + let mut conversions = BTreeMap::new(); let mut val_acc = Amount::zero(); let mut notes = Vec::new(); // Retrieve the notes that can be spent by this key @@ -1286,7 +1318,7 @@ impl ShieldedContext { println!("Decoded pinned balance: {:?}", amount); // Finally, exchange the balance to the transaction's epoch let computed_amount = self - .compute_exchanged_amount(client, amount, ep, HashMap::new()) + .compute_exchanged_amount(client, amount, ep, BTreeMap::new()) .await .0; println!("Exchanged amount: {:?}", computed_amount); @@ -1357,16 +1389,17 @@ impl ShieldedContext { args: &args::TxTransfer, shielded_gas: bool, ) -> Result< - Option<( - Builder<(), (), ExtendedFullViewingKey, ()>, - Transaction, - SaplingMetadata, - Epoch, - )>, + Option, builder::Error, > { // No shielded components are needed when neither source nor destination // are shielded + + use std::str::FromStr; + + use rand::rngs::StdRng; + use rand_core::SeedableRng; + let spending_key = args.source.spending_key(); let payment_address = args.target.payment_address(); // No shielded components are needed when neither source nor @@ -1388,8 +1421,27 @@ impl ShieldedContext { // possesion let memo = MemoBytes::empty(); + // Try to get a seed from env var, if any. + let rng = if let Ok(seed) = + env::var(ENV_VAR_MASP_TEST_SEED).map(|seed| { + let exp_str = + format!("Env var {ENV_VAR_MASP_TEST_SEED} must be a u64."); + let parsed_seed: u64 = + FromStr::from_str(&seed).expect(&exp_str); + parsed_seed + }) { + tracing::warn!( + "UNSAFE: Using a seed from {ENV_VAR_MASP_TEST_SEED} env var \ + to build proofs." + ); + StdRng::seed_from_u64(seed) + } else { + StdRng::from_rng(OsRng).unwrap() + }; + // Now we build up the transaction within this object - let mut builder = Builder::::new(NETWORK, 1.into()); + let mut builder = + Builder::::new_with_rng(NETWORK, 1.into(), rng); // break up a transfer into a number of transfers with suitable // denominations @@ -1403,13 +1455,13 @@ impl ShieldedContext { let tx_fee = // If there are shielded inputs if let Some(sk) = spending_key { - let InputAmount::Validated(fee) = args.tx.fee_amount else { + let InputAmount::Validated(fee) = args.tx.gas_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 (_, shielded_fee) = - convert_amount(epoch, &args.tx.fee_token, fee.amount); + convert_amount(epoch, &args.tx.gas_token, fee.amount); let required_amt = if shielded_gas { amount + shielded_fee.clone() } else { @@ -1548,16 +1600,81 @@ impl ShieldedContext { } } - // Build and return the constructed transaction - builder - .clone() - .build( + // To speed up integration tests, we can save and load proofs + let load_or_save = if let Ok(masp_proofs) = + env::var(ENV_VAR_MASP_TEST_PROOFS) + { + let parsed = match masp_proofs.to_ascii_lowercase().as_str() { + "load" => LoadOrSaveProofs::Load, + "save" => LoadOrSaveProofs::Save, + env_var => panic!( + "Unexpected value for {ENV_VAR_MASP_TEST_PROOFS} env var. \ + Expecting \"save\" or \"load\", but got \"{env_var}\"." + ), + }; + if env::var(ENV_VAR_MASP_TEST_SEED).is_err() { + panic!( + "Ensure to set a seed with {ENV_VAR_MASP_TEST_SEED} env \ + var when using {ENV_VAR_MASP_TEST_PROOFS} for \ + deterministic proofs." + ); + } + parsed + } else { + LoadOrSaveProofs::Neither + }; + + let builder_clone = builder.clone().map_builder(WalletMap); + let builder_bytes = BorshSerialize::try_to_vec(&builder_clone).unwrap(); + let builder_hash = + namada_core::types::hash::Hash::sha256(&builder_bytes); + let saved_filepath = env::current_dir() + .unwrap() + // One up from "tests" dir to the root dir + .parent() + .unwrap() + .join(MASP_TEST_PROOFS_DIR) + .join(format!("{builder_hash}.bin")); + + if let LoadOrSaveProofs::Load = load_or_save { + let recommendation = format!( + "Re-run the tests with {ENV_VAR_MASP_TEST_PROOFS}=save to \ + re-generate proofs." + ); + let exp_str = format!( + "Read saved MASP proofs from {}. {recommendation}", + saved_filepath.to_string_lossy() + ); + let loaded_bytes = + tokio::fs::read(&saved_filepath).await.expect(&exp_str); + let exp_str = format!( + "Valid `ShieldedTransfer` bytes in {}. {recommendation}", + saved_filepath.to_string_lossy() + ); + let loaded: ShieldedTransfer = + BorshDeserialize::try_from_slice(&loaded_bytes) + .expect(&exp_str); + Ok(Some(loaded)) + } else { + // Build and return the constructed transaction + let (masp_tx, metadata) = builder.build( &self.utils.local_tx_prover(), &FeeRule::non_standard(tx_fee), - ) - .map(|(tx, metadata)| { - Some((builder.map_builder(WalletMap), tx, metadata, epoch)) - }) + )?; + let built = ShieldedTransfer { + builder: builder_clone, + masp_tx, + metadata, + epoch, + }; + if let LoadOrSaveProofs::Save = load_or_save { + let built_bytes = BorshSerialize::try_to_vec(&built).unwrap(); + tokio::fs::write(&saved_filepath, built_bytes) + .await + .unwrap(); + } + Ok(Some(built)) + } } /// Obtain the known effects of all accepted shielded and transparent diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 399f75f800..a5b2a06819 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -3,10 +3,12 @@ pub mod args; pub mod eth_bridge; pub mod events; +pub mod governance; pub mod ibc; pub mod inflation; pub mod masp; pub mod native_vp; +pub mod pgf; pub mod pos; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] pub mod protocol; @@ -20,5 +22,5 @@ pub mod vp_host_fns; pub mod wallet; pub use namada_core::ledger::{ - gas, governance, parameters, replay_protection, storage_api, tx_env, vp_env, + gas, parameters, replay_protection, storage_api, tx_env, vp_env, }; diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index a63b252955..323633b563 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -355,7 +355,6 @@ mod test_bridge_pool_vp { use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{Storage, WlStorage}; use crate::ledger::storage_api::StorageWrite; - use crate::proto::Data; use crate::types::address::{nam, wnam}; use crate::types::chain::ChainId; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; @@ -566,7 +565,7 @@ mod test_bridge_pool_vp { { // setup let mut wl_storage = setup_storage(); - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); // the transfer to be added to the pool let transfer = PendingTransfer { @@ -622,8 +621,8 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_data(transfer); let res = vp.validate_tx(&tx, &keys_changed, &verifiers); match expect { @@ -905,7 +904,7 @@ mod test_bridge_pool_vp { fn test_adding_transfer_twice_fails() { // setup let mut wl_storage = setup_storage(); - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); // the transfer to be added to the pool let transfer = initial_pool(); @@ -960,8 +959,8 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_data(transfer); let res = vp.validate_tx(&tx, &keys_changed, &verifiers); assert!(!res.expect("Test failed")); @@ -973,7 +972,7 @@ mod test_bridge_pool_vp { fn test_zero_gas_fees_rejected() { // setup let mut wl_storage = setup_storage(); - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); // the transfer to be added to the pool let transfer = PendingTransfer { @@ -1022,8 +1021,8 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_data(transfer); let res = vp .validate_tx(&tx, &keys_changed, &verifiers) @@ -1039,7 +1038,7 @@ mod test_bridge_pool_vp { let mut wl_storage = setup_storage(); let eb_account_key = balance_key(&nam(), &Address::Internal(InternalAddress::EthBridge)); - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); // the transfer to be added to the pool let transfer = PendingTransfer { @@ -1108,8 +1107,8 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_data(transfer); let res = vp .validate_tx(&tx, &keys_changed, &verifiers) @@ -1124,7 +1123,7 @@ mod test_bridge_pool_vp { fn test_reject_mint_wnam() { // setup let mut wl_storage = setup_storage(); - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); let eb_account_key = balance_key(&nam(), &Address::Internal(InternalAddress::EthBridge)); @@ -1195,8 +1194,8 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_data(transfer); let res = vp .validate_tx(&tx, &keys_changed, &verifiers) @@ -1232,7 +1231,7 @@ mod test_bridge_pool_vp { ) .expect("Test failed"); wl_storage.write_log.commit_tx(); - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); // the transfer to be added to the pool let transfer = PendingTransfer { @@ -1309,8 +1308,8 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let mut tx = Tx::new(wl_storage.storage.chain_id.clone(), None); + tx.add_data(transfer); let res = vp .validate_tx(&tx, &keys_changed, &verifiers) diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index 33a828938a..5fd7aa6cd1 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -588,7 +588,7 @@ mod tests { let verifiers = BTreeSet::from([BRIDGE_POOL_ADDRESS]); // set up the VP - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); let vp = EthBridge { ctx: setup_ctx( &tx, @@ -638,7 +638,7 @@ mod tests { let verifiers = BTreeSet::from([BRIDGE_POOL_ADDRESS]); // set up the VP - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); let vp = EthBridge { ctx: setup_ctx( &tx, @@ -691,7 +691,7 @@ mod tests { let verifiers = BTreeSet::from([]); // set up the VP - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); let vp = EthBridge { ctx: setup_ctx( &tx, diff --git a/shared/src/ledger/native_vp/governance/utils.rs b/shared/src/ledger/native_vp/governance/utils.rs deleted file mode 100644 index c00f57b5d9..0000000000 --- a/shared/src/ledger/native_vp/governance/utils.rs +++ /dev/null @@ -1,484 +0,0 @@ -//! Governance utility functions - -use std::collections::HashMap; - -use borsh::BorshDeserialize; -use namada_core::types::governance::ProposalResult; -use namada_core::types::transaction::governance::ProposalType; -use namada_proof_of_stake::{ - bond_amount, read_all_validator_addresses, read_pos_params, - read_validator_stake, -}; -use thiserror::Error; - -use crate::ledger::governance::storage as gov_storage; -use crate::ledger::pos::BondId; -use crate::ledger::storage_api; -use crate::types::address::Address; -use crate::types::governance::{ - ProposalVote, Tally, TallyResult, VotePower, VoteType, -}; -use crate::types::storage::Epoch; - -/// Proposal structure holding votes information necessary to compute the -/// outcome -pub struct Votes { - /// Map from validators who votes yay to their total stake amount - pub yay_validators: HashMap, - /// Map from delegation votes to their bond amount - pub delegators: - HashMap>, -} - -/// Proposal errors -#[derive(Error, Debug)] -pub enum Error { - /// Invalid validator set deserialization - #[error("Invalid validator set")] - InvalidValidatorSet, - /// Invalid proposal field deserialization - #[error("Invalid proposal {0}")] - InvalidProposal(u64), - /// Error during tally - #[error("Error while tallying proposal: {0}")] - Tally(String), -} - -/// Proposal event definition -pub struct ProposalEvent { - /// Proposal event type - pub event_type: String, - /// Proposal event attributes - pub attributes: HashMap, -} - -impl ProposalEvent { - /// Create a proposal event - pub fn new( - event_type: String, - tally: TallyResult, - id: u64, - has_proposal_code: bool, - proposal_code_exit_status: bool, - ) -> Self { - let attributes = HashMap::from([ - ("tally_result".to_string(), tally.to_string()), - ("proposal_id".to_string(), id.to_string()), - ( - "has_proposal_code".to_string(), - (!has_proposal_code as u64).to_string(), - ), - ( - "proposal_code_exit_status".to_string(), - (!proposal_code_exit_status as u64).to_string(), - ), - ]); - Self { - event_type, - attributes, - } - } -} - -/// Return a proposal result -pub fn compute_tally( - votes: Votes, - total_stake: VotePower, - proposal_type: &ProposalType, -) -> Result { - let Votes { - yay_validators, - delegators, - } = votes; - - match proposal_type { - ProposalType::Default(_) | ProposalType::ETHBridge => { - let mut total_yay_staked_tokens = VotePower::default(); - - 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; - } else { - // Log the error and continue - tracing::error!( - "Unexpected vote type. Expected: {}, Found: {}", - proposal_type, - validator_vote - ); - continue; - } - } else { - // Log the error and continue - tracing::error!( - "Unexpected vote type. Expected: {}, Found: {}", - proposal_type, - validator_vote - ); - continue; - } - } - - // This loop is taken only for Default proposals - for (_, vote_map) in delegators.iter() { - for (validator_address, (vote_power, delegator_vote)) in - vote_map.iter() - { - match delegator_vote { - ProposalVote::Yay(VoteType::Default) => { - if !yay_validators.contains_key(validator_address) { - // YAY: Add delegator amount whose validator - // didn't vote / voted nay - total_yay_staked_tokens += *vote_power; - } - } - ProposalVote::Nay => { - // NAY: Remove delegator amount whose validator - // validator vote yay - - if yay_validators.contains_key(validator_address) { - total_yay_staked_tokens -= *vote_power; - } - } - - _ => { - // Log the error and continue - tracing::error!( - "Unexpected vote type. Expected: {}, Found: {}", - proposal_type, - delegator_vote - ); - continue; - } - } - } - } - - // Proposal passes if 2/3 of total voting power voted Yay - if total_yay_staked_tokens >= (total_stake / 3) * 2 { - let tally_result = match proposal_type { - ProposalType::Default(_) => { - TallyResult::Passed(Tally::Default) - } - ProposalType::ETHBridge => { - TallyResult::Passed(Tally::ETHBridge) - } - _ => { - return Err(Error::Tally(format!( - "Unexpected proposal type: {}", - proposal_type - ))); - } - }; - - Ok(ProposalResult { - result: tally_result, - total_voting_power: total_stake, - total_yay_power: total_yay_staked_tokens, - 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.into(), - }) - } - } - ProposalType::PGFCouncil => { - let mut total_yay_staked_tokens = HashMap::new(); - for (_, (amount, validator_vote)) in yay_validators.iter() { - if let ProposalVote::Yay(VoteType::PGFCouncil(votes)) = - validator_vote - { - for v in votes { - *total_yay_staked_tokens - .entry(v) - .or_insert(VotePower::zero()) += *amount; - } - } else { - // Log the error and continue - tracing::error!( - "Unexpected vote type. Expected: PGFCouncil, Found: {}", - validator_vote - ); - continue; - } - } - - // YAY: Add delegator amount whose validator didn't vote / voted nay - // or adjust voting power if delegator voted yay with a - // different memo - for (_, vote_map) in delegators.iter() { - for (validator_address, (vote_power, delegator_vote)) in - vote_map.iter() - { - match delegator_vote { - ProposalVote::Yay(VoteType::PGFCouncil( - delegator_votes, - )) => { - match yay_validators.get(validator_address) { - Some((_, validator_vote)) => { - if let ProposalVote::Yay( - VoteType::PGFCouncil(validator_votes), - ) = validator_vote - { - for vote in validator_votes - .symmetric_difference( - delegator_votes, - ) - { - if validator_votes.contains(vote) { - // Delegator didn't vote for - // this, reduce voting power - if let Some(power) = - total_yay_staked_tokens - .get_mut(vote) - { - *power -= *vote_power; - } else { - return Err(Error::Tally( - format!( - "Expected PGF \ - vote {:?} was \ - not in tally", - vote - ), - )); - } - } else { - // Validator didn't vote for - // this, add voting power - *total_yay_staked_tokens - .entry(vote) - .or_insert( - VotePower::zero(), - ) += *vote_power; - } - } - } else { - // Log the error and continue - tracing::error!( - "Unexpected vote type. Expected: \ - PGFCouncil, Found: {}", - validator_vote - ); - continue; - } - } - None => { - // Validator didn't vote or voted nay, add - // delegator vote - - for vote in delegator_votes { - *total_yay_staked_tokens - .entry(vote) - .or_insert(VotePower::zero()) += - *vote_power; - } - } - } - } - ProposalVote::Nay => { - for ( - validator_address, - (vote_power, _delegator_vote), - ) in vote_map.iter() - { - if let Some((_, validator_vote)) = - yay_validators.get(validator_address) - { - if let ProposalVote::Yay( - VoteType::PGFCouncil(votes), - ) = validator_vote - { - for vote in votes { - if let Some(power) = - total_yay_staked_tokens - .get_mut(vote) - { - *power -= *vote_power; - } else { - return Err(Error::Tally( - format!( - "Expected PGF vote \ - {:?} was not in tally", - vote - ), - )); - } - } - } else { - // Log the error and continue - tracing::error!( - "Unexpected vote type. Expected: \ - PGFCouncil, Found: {}", - validator_vote - ); - continue; - } - } - } - } - _ => { - // Log the error and continue - tracing::error!( - "Unexpected vote type. Expected: PGFCouncil, \ - Found: {}", - delegator_vote - ); - continue; - } - } - } - } - - // At least 1/3 of the total voting power must vote Yay - let total_yay_voted_power = total_yay_staked_tokens - .iter() - .fold(VotePower::zero(), |acc, (_, vote_power)| { - acc + *vote_power - }); - - 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: VotePower::zero(), - }), - _ => { - // Select the winner council based on approval voting - // (majority) - let council = total_yay_staked_tokens - .into_iter() - .max_by(|a, b| a.1.cmp(&b.1)) - .map(|(vote, _)| vote.to_owned()) - .ok_or_else(|| { - Error::Tally( - "Missing expected elected council".to_string(), - ) - })?; - - Ok(ProposalResult { - result: TallyResult::Passed(Tally::PGFCouncil(council)), - total_voting_power: total_stake, - total_yay_power: total_yay_voted_power, - total_nay_power: VotePower::zero(), - }) - } - } - } - } -} - -/// Prepare Votes structure to compute proposal tally -pub fn get_proposal_votes( - storage: &S, - epoch: Epoch, - proposal_id: u64, -) -> storage_api::Result -where - S: storage_api::StorageRead, -{ - let params = read_pos_params(storage)?; - let validators = read_all_validator_addresses(storage, epoch)?; - - let vote_prefix_key = - gov_storage::get_proposal_vote_prefix_key(proposal_id); - let vote_iter = - storage_api::iter_prefix::(storage, &vote_prefix_key)?; - - let mut yay_validators = HashMap::new(); - let mut delegators: HashMap< - Address, - HashMap, - > = HashMap::new(); - - for next_vote in vote_iter { - let (vote_key, vote) = next_vote?; - let voter_address = gov_storage::get_voter_address(&vote_key); - match voter_address { - Some(voter_address) => { - if vote.is_yay() && validators.contains(voter_address) { - let amount: VotePower = read_validator_stake( - storage, - ¶ms, - voter_address, - epoch, - )? - .unwrap_or_default() - .try_into() - .expect("Amount out of bounds"); - - yay_validators - .insert(voter_address.clone(), (amount, vote)); - } else if !validators.contains(voter_address) { - let validator_address = - gov_storage::get_vote_delegation_address(&vote_key); - match validator_address { - Some(validator) => { - let bond_id = BondId { - source: voter_address.clone(), - validator: validator.clone(), - }; - let amount = - bond_amount(storage, &bond_id, epoch)?.1; - - if !amount.is_zero() { - let entry = delegators - .entry(voter_address.to_owned()) - .or_default(); - entry.insert( - validator.to_owned(), - ( - VotePower::try_from(amount).unwrap(), - vote, - ), - ); - } - } - None => continue, - } - } - } - None => continue, - } - } - - Ok(Votes { - yay_validators, - delegators, - }) -} - -/// Calculate the valid voting window for validator given a proposal epoch -/// details -pub fn is_valid_validator_voting_period( - current_epoch: Epoch, - voting_start_epoch: Epoch, - voting_end_epoch: Epoch, -) -> bool { - voting_start_epoch < voting_end_epoch - && current_epoch * 3 <= voting_start_epoch + voting_end_epoch * 2 -} - -/// Check if an accepted proposal is being executed -pub fn is_proposal_accepted( - storage: &S, - tx_data: &[u8], -) -> storage_api::Result -where - S: storage_api::StorageRead, -{ - let proposal_id = u64::try_from_slice(tx_data).ok(); - match proposal_id { - Some(id) => { - let proposal_execution_key = - gov_storage::get_proposal_execution_key(id); - storage.has_key(&proposal_execution_key) - } - None => Ok(false), - } -} diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index ed34545f16..02a3435397 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -2,11 +2,9 @@ //! as the PoS and IBC modules. pub mod ethereum_bridge; -pub mod governance; pub mod multitoken; pub mod parameters; pub mod replay_protection; -pub mod slash_fund; use std::cell::RefCell; use std::collections::BTreeSet; diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index 5b88c0f152..3a15da97aa 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -164,7 +164,7 @@ mod tests { fn dummy_tx(wl_storage: &TestWlStorage) -> Tx { let tx_code = vec![]; let tx_data = vec![]; - let mut tx = Tx::new(TxType::Raw); + let mut tx = Tx::from_type(TxType::Raw); tx.header.chain_id = wl_storage.storage.chain_id.clone(); tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); diff --git a/shared/src/ledger/native_vp/parameters.rs b/shared/src/ledger/native_vp/parameters.rs index bb1db0ab30..a2559ad0ab 100644 --- a/shared/src/ledger/native_vp/parameters.rs +++ b/shared/src/ledger/native_vp/parameters.rs @@ -8,7 +8,7 @@ use namada_core::types::address::Address; use namada_core::types::storage::Key; use thiserror::Error; -use super::governance; +use crate::core::ledger::storage_api::governance; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::vm::WasmCacheAccess; @@ -55,11 +55,10 @@ where return false; }; match key_type { - KeyType::PARAMETER => governance::utils::is_proposal_accepted( - &self.ctx.pre(), - &data, - ) - .unwrap_or(false), + KeyType::PARAMETER => { + governance::is_proposal_accepted(&self.ctx.pre(), &data) + .unwrap_or(false) + } KeyType::UNKNOWN_PARAMETER => false, KeyType::UNKNOWN => true, } diff --git a/shared/src/ledger/pgf/mod.rs b/shared/src/ledger/pgf/mod.rs new file mode 100644 index 0000000000..f402b0f97b --- /dev/null +++ b/shared/src/ledger/pgf/mod.rs @@ -0,0 +1,126 @@ +//! Pgf VP + +/// Pgf utility functions and structures +pub mod utils; + +use std::collections::BTreeSet; + +use namada_core::ledger::pgf::storage::keys as pgf_storage; +use namada_core::ledger::storage; +use namada_core::ledger::storage_api::governance::is_proposal_accepted; +use namada_core::proto::Tx; +use thiserror::Error; + +use crate::ledger::native_vp; +use crate::ledger::native_vp::{Ctx, NativeVp}; +use crate::types::address::{Address, InternalAddress}; +use crate::types::storage::Key; +use crate::vm::WasmCacheAccess; + +/// for handling Pgf NativeVP errors +pub type Result = std::result::Result; + +/// The PGF internal address +pub const ADDRESS: Address = Address::Internal(InternalAddress::Pgf); + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Native VP error: {0}")] + NativeVpError(#[from] native_vp::Error), +} + +/// Pgf VP +pub struct PgfVp<'a, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, + CA: WasmCacheAccess, +{ + /// Context to interact with the host structures. + pub ctx: Ctx<'a, DB, H, CA>, +} + +impl<'a, DB, H, CA> NativeVp for PgfVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + + fn validate_tx( + &self, + tx_data: &Tx, + keys_changed: &BTreeSet, + _verifiers: &BTreeSet
, + ) -> Result { + let result = keys_changed.iter().all(|key| { + let key_type = KeyType::from(key); + + let result = match key_type { + KeyType::STEWARDS => Ok(false), + KeyType::PAYMENTS => Ok(false), + KeyType::PGF_INFLATION_RATE + | KeyType::STEWARD_INFLATION_RATE => { + self.is_valid_parameter_change(tx_data) + } + KeyType::UNKNOWN_PGF => Ok(false), + KeyType::UNKNOWN => Ok(true), + }; + result.unwrap_or(false) + }); + Ok(result) + } +} + +impl<'a, DB, H, CA> PgfVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Validate a governance parameter + pub fn is_valid_parameter_change(&self, tx: &Tx) -> Result { + match tx.data() { + Some(data) => is_proposal_accepted(&self.ctx.pre(), data.as_ref()) + .map_err(Error::NativeVpError), + None => Ok(true), + } + } +} + +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug)] +enum KeyType { + #[allow(non_camel_case_types)] + STEWARDS, + #[allow(non_camel_case_types)] + PAYMENTS, + #[allow(non_camel_case_types)] + PGF_INFLATION_RATE, + #[allow(non_camel_case_types)] + STEWARD_INFLATION_RATE, + #[allow(non_camel_case_types)] + UNKNOWN_PGF, + #[allow(non_camel_case_types)] + UNKNOWN, +} + +impl From<&Key> for KeyType { + fn from(key: &Key) -> Self { + if pgf_storage::is_stewards_key(key) { + Self::STEWARDS + } else if pgf_storage::is_payments_key(key) { + KeyType::PAYMENTS + } else if pgf_storage::is_pgf_inflation_rate_key(key) { + Self::PGF_INFLATION_RATE + } else if pgf_storage::is_steward_inflation_rate_key(key) { + Self::STEWARD_INFLATION_RATE + } else if pgf_storage::is_pgf_key(key) { + KeyType::UNKNOWN_PGF + } else { + KeyType::UNKNOWN + } + } +} diff --git a/shared/src/ledger/pgf/utils.rs b/shared/src/ledger/pgf/utils.rs new file mode 100644 index 0000000000..e1bec701ba --- /dev/null +++ b/shared/src/ledger/pgf/utils.rs @@ -0,0 +1,66 @@ +use std::collections::HashMap; + +use namada_core::types::address::Address; +use namada_core::types::token; + +use crate::ledger::events::EventType; + +/// Proposal event definition +pub struct ProposalEvent { + /// Proposal event type + pub event_type: String, + /// Proposal event attributes + pub attributes: HashMap, +} + +impl ProposalEvent { + /// Create a proposal event + pub fn new( + event_type: String, + target: Address, + amount: token::Amount, + is_steward: bool, + success: bool, + ) -> Self { + let attributes = HashMap::from([ + ("target".to_string(), target.to_string()), + ("amount".to_string(), amount.to_string_native()), + ("is_steward".to_string(), is_steward.to_string()), + ("successed".to_string(), success.to_string()), + ]); + Self { + event_type, + attributes, + } + } + + /// Create a new proposal event for pgf continous funding + pub fn pgf_funding_payment( + target: Address, + amount: token::Amount, + success: bool, + ) -> Self { + ProposalEvent::new( + EventType::PgfPayment.to_string(), + target, + amount, + false, + success, + ) + } + + /// Create a new proposal event for steward payments + pub fn pgf_steward_payment( + target: Address, + amount: token::Amount, + success: bool, + ) -> Self { + ProposalEvent::new( + EventType::PgfPayment.to_string(), + target, + amount, + true, + success, + ) + } +} diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 18085c5c53..76607c3199 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use std::panic::{RefUnwindSafe, UnwindSafe}; +use namada_core::ledger::storage_api::governance; // use borsh::BorshDeserialize; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; @@ -12,7 +13,7 @@ pub use namada_proof_of_stake::types; use thiserror::Error; use super::is_params_key; -use crate::ledger::native_vp::{self, governance, Ctx, NativeVp}; +use crate::ledger::native_vp::{self, Ctx, NativeVp}; // use crate::ledger::pos::{ // is_validator_address_raw_hash_key, // is_validator_max_commission_rate_change_key, @@ -106,18 +107,14 @@ where tracing::debug!("\nValidating PoS Tx\n"); for key in keys_changed { - // println!("KEY: {}\n", key); if is_params_key(key) { let data = if let Some(data) = tx_data.data() { data } else { return Ok(false); }; - if !governance::utils::is_proposal_accepted( - &self.ctx.pre(), - &data, - ) - .map_err(Error::NativeVpError)? + if !governance::is_proposal_accepted(&self.ctx.pre(), &data) + .map_err(Error::NativeVpError)? { return Ok(false); } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 6cab156d7a..7a0244932a 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -8,15 +8,15 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; +use crate::ledger::governance::GovernanceVp; use crate::ledger::ibc::vp::Ibc; use crate::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use crate::ledger::native_vp::ethereum_bridge::vp::EthBridge; -use crate::ledger::native_vp::governance::GovernanceVp; use crate::ledger::native_vp::multitoken::MultitokenVp; use crate::ledger::native_vp::parameters::{self, ParametersVp}; use crate::ledger::native_vp::replay_protection::ReplayProtectionVp; -use crate::ledger::native_vp::slash_fund::SlashFundVp; use crate::ledger::native_vp::{self, NativeVp}; +use crate::ledger::pgf::PgfVp; use crate::ledger::pos::{self, PosVP}; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{DBIter, Storage, StorageHasher, WlStorage, DB}; @@ -61,9 +61,9 @@ pub enum Error { #[error("IBC Token native VP: {0}")] MultitokenNativeVpError(crate::ledger::native_vp::multitoken::Error), #[error("Governance native VP error: {0}")] - GovernanceNativeVpError(crate::ledger::native_vp::governance::Error), - #[error("SlashFund native VP error: {0}")] - SlashFundNativeVpError(crate::ledger::native_vp::slash_fund::Error), + GovernanceNativeVpError(crate::ledger::governance::Error), + #[error("Pgf native VP error: {0}")] + PgfNativeVpError(crate::ledger::pgf::Error), #[error("Ethereum bridge native VP error: {0}")] EthBridgeNativeVpError(native_vp::ethereum_bridge::vp::Error), #[error("Ethereum bridge pool native VP error: {0}")] @@ -543,14 +543,6 @@ where gas_meter = governance.ctx.gas_meter.into_inner(); result } - InternalAddress::SlashFund => { - let slash_fund = SlashFundVp { ctx }; - let result = slash_fund - .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::SlashFundNativeVpError); - gas_meter = slash_fund.ctx.gas_meter.into_inner(); - result - } InternalAddress::Multitoken => { let multitoken = MultitokenVp { ctx }; let result = multitoken @@ -585,6 +577,14 @@ where replay_protection_vp.ctx.gas_meter.into_inner(); result } + InternalAddress::Pgf => { + let pgf_vp = PgfVp { ctx }; + let result = pgf_vp + .validate_tx(tx, &keys_changed, &verifiers) + .map_err(Error::PgfNativeVpError); + gas_meter = pgf_vp.ctx.gas_meter.into_inner(); + result + } InternalAddress::IbcToken(_) | InternalAddress::Erc20(_) => { // The address should be a part of a multitoken key diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 3eb95c8e8c..94412f1a15 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -5,6 +5,7 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use namada_core::ledger::storage::LastBlock; +use namada_core::types::account::{Account, AccountPublicKeysMap}; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::storage::{BlockHeight, BlockResults, KeySeg}; @@ -75,6 +76,12 @@ router! {SHELL, // was the transaction applied? ( "applied" / [tx_hash: Hash] ) -> Option = applied, + // Query account subspace + ( "account" / [owner: Address] ) -> Option = account, + + // Query public key revealad + ( "revealed" / [owner: Address] ) -> bool = revealed, + // IBC UpdateClient event ( "ibc_client_update" / [client_id: ClientId] / [consensus_height: BlockHeight] ) -> Option = ibc_client_update, @@ -438,6 +445,46 @@ where .cloned()) } +fn account( + ctx: RequestCtx<'_, D, H>, + owner: Address, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let account_exists = storage_api::account::exists(ctx.wl_storage, &owner)?; + + if account_exists { + let public_keys = + storage_api::account::public_keys(ctx.wl_storage, &owner)?; + let threshold = + storage_api::account::threshold(ctx.wl_storage, &owner)?; + + Ok(Some(Account { + public_keys_map: AccountPublicKeysMap::from_iter(public_keys), + address: owner, + threshold: threshold.unwrap_or(1), + })) + } else { + Ok(None) + } +} + +fn revealed( + ctx: RequestCtx<'_, D, H>, + owner: Address, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let public_keys = + storage_api::account::public_keys(ctx.wl_storage, &owner)?; + + Ok(!public_keys.is_empty()) +} + #[cfg(test)] mod test { @@ -492,7 +539,8 @@ mod test { assert_eq!(current_epoch, read_epoch); // Request dry run tx - let mut outer_tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + let mut outer_tx = + Tx::from_type(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] // To be able to dry-run testnet faucet withdrawal, pretend // that we got a valid PoW diff --git a/shared/src/ledger/queries/vp/governance.rs b/shared/src/ledger/queries/vp/governance.rs new file mode 100644 index 0000000000..230612cbb8 --- /dev/null +++ b/shared/src/ledger/queries/vp/governance.rs @@ -0,0 +1,38 @@ +// cd shared && cargo expand ledger::queries::vp::governance + +use namada_core::ledger::governance::storage::proposal::StorageProposal; +use namada_core::ledger::governance::utils::Vote; + +use crate::ledger::queries::types::RequestCtx; +use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage_api; + +// Governance queries +router! {GOV, + ( "proposal" / [id: u64 ] ) -> Option = proposal_id, + ( "proposal" / [id: u64 ] / "votes" ) -> Vec = proposal_id_votes, +} + +/// Find if the given address belongs to a validator account. +fn proposal_id( + ctx: RequestCtx<'_, D, H>, + id: u64, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + storage_api::governance::get_proposal_by_id(ctx.wl_storage, id) +} + +/// Find if the given address belongs to a validator account. +fn proposal_id_votes( + ctx: RequestCtx<'_, D, H>, + id: u64, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + storage_api::governance::get_proposal_votes(ctx.wl_storage, id) +} diff --git a/shared/src/ledger/queries/vp/mod.rs b/shared/src/ledger/queries/vp/mod.rs index ad05a2b88b..c53386b2f8 100644 --- a/shared/src/ledger/queries/vp/mod.rs +++ b/shared/src/ledger/queries/vp/mod.rs @@ -1,10 +1,16 @@ //! Queries router and handlers for validity predicates // Re-export to show in rustdoc! +pub use governance::Gov; +use governance::GOV; pub use pos::Pos; use pos::POS; pub use token::Token; use token::TOKEN; +mod governance; +pub use pgf::Pgf; +use pgf::PGF; +mod pgf; pub mod pos; mod token; @@ -13,6 +19,8 @@ mod token; router! {VP, ( "pos" ) = (sub POS), ( "token" ) = (sub TOKEN), + ( "governance" ) = (sub GOV), + ( "pgf" ) = (sub PGF), } /// Client-only methods for the router type are composed from router functions. diff --git a/shared/src/ledger/queries/vp/pgf.rs b/shared/src/ledger/queries/vp/pgf.rs new file mode 100644 index 0000000000..932d898068 --- /dev/null +++ b/shared/src/ledger/queries/vp/pgf.rs @@ -0,0 +1,36 @@ +use std::collections::BTreeSet; + +use namada_core::ledger::governance::storage::proposal::PGFTarget; +use namada_core::types::address::Address; + +use crate::ledger::queries::types::RequestCtx; +use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage_api; + +// PoS validity predicate queries +router! {PGF, + ( "stewards" ) -> BTreeSet
= stewards, + ( "fundings" ) -> BTreeSet = funding, +} + +/// Query the currect pgf steward set +fn stewards( + ctx: RequestCtx<'_, D, H>, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + storage_api::pgf::get_stewards(ctx.wl_storage) +} + +/// Query the continous pgf fundings +fn funding( + ctx: RequestCtx<'_, D, H>, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + storage_api::pgf::get_payments(ctx.wl_storage) +} diff --git a/shared/src/ledger/queries/vp/pos.rs b/shared/src/ledger/queries/vp/pos.rs index 075b936e25..aff5181a19 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/shared/src/ledger/queries/vp/pos.rs @@ -69,6 +69,9 @@ router! {POS, ( "delegations" / [owner: Address] ) -> HashSet
= delegation_validators, + ( "delegations_at" / [owner: Address] / [epoch: opt Epoch] ) + -> HashMap = delegations, + ( "bond_deltas" / [source: Address] / [validator: Address] ) -> HashMap = bond_deltas, @@ -463,7 +466,6 @@ where /// Find all the validator addresses to whom the given `owner` address has /// some delegation in any epoch -#[allow(dead_code)] fn delegations( ctx: RequestCtx<'_, D, H>, owner: Address, diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 7cd7565f66..d0fad687be 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -8,8 +8,13 @@ use borsh::BorshDeserialize; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; +use namada_core::ledger::governance::parameters::GovernanceParameters; +use namada_core::ledger::governance::storage::proposal::StorageProposal; +use namada_core::ledger::governance::utils::Vote; use namada_core::ledger::storage::LastBlock; +#[cfg(not(feature = "mainnet"))] use namada_core::ledger::testnet_pow; +use namada_core::types::account::Account; use namada_core::types::address::Address; use namada_core::types::storage::Key; use namada_core::types::token::{ @@ -21,11 +26,9 @@ use namada_proof_of_stake::types::{ }; use serde::Serialize; +use crate::core::ledger::governance::storage::keys as gov_storage; use crate::ledger::args::InputAmount; use crate::ledger::events::Event; -use crate::ledger::governance::parameters::GovParams; -use crate::ledger::governance::storage as gov_storage; -use crate::ledger::native_vp::governance::utils::Votes; use crate::ledger::queries::vp::pos::EnrichedBondsAndUnbondsDetails; use crate::ledger::queries::RPC; use crate::proto::Tx; @@ -35,9 +38,8 @@ use crate::tendermint_rpc::error::Error as TError; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; use crate::types::control_flow::{time, Halt, TryHalt}; -use crate::types::governance::{ProposalVote, VotePower}; use crate::types::hash::Hash; -use crate::types::key::*; +use crate::types::key::common; use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; use crate::types::{storage, token}; @@ -122,8 +124,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.to_string()); }) } @@ -145,15 +147,6 @@ pub async fn get_token_balance( ) } -/// Get account's public key stored in its storage sub-space -pub async fn get_public_key( - client: &C, - address: &Address, -) -> Option { - let key = pk_key(address); - query_storage_value(client, &key).await -} - /// Check if the given address is a known validator. pub async fn is_validator( client: &C, @@ -635,69 +628,6 @@ pub async fn query_tx_response( Ok(result) } -/// Get the votes for a given proposal id -pub async fn get_proposal_votes( - client: &C, - epoch: Epoch, - proposal_id: u64, -) -> Votes { - let validators = get_all_validators(client, epoch).await; - - let vote_prefix_key = - gov_storage::get_proposal_vote_prefix_key(proposal_id); - let vote_iter = - query_storage_prefix::(client, &vote_prefix_key).await; - - let mut yay_validators: HashMap = - HashMap::new(); - let mut delegators: HashMap< - Address, - HashMap, - > = HashMap::new(); - - if let Some(vote_iter) = vote_iter { - for (key, vote) in vote_iter { - let voter_address = gov_storage::get_voter_address(&key) - .expect("Vote key should contain the voting address.") - .clone(); - if vote.is_yay() && validators.contains(&voter_address) { - let amount: VotePower = - get_validator_stake(client, epoch, &voter_address) - .await - .try_into() - .expect("Amount of bonds"); - yay_validators.insert(voter_address, (amount, vote)); - } else if !validators.contains(&voter_address) { - let validator_address = - gov_storage::get_vote_delegation_address(&key) - .expect( - "Vote key should contain the delegation address.", - ) - .clone(); - let delegator_token_amount = get_bond_amount_at( - client, - &voter_address, - &validator_address, - epoch, - ) - .await; - if let Some(amount) = delegator_token_amount { - let entry = delegators.entry(voter_address).or_default(); - entry.insert( - validator_address, - (VotePower::from(amount), vote), - ); - } - } - } - } - - Votes { - yay_validators, - delegators, - } -} - /// Get the PoS parameters pub async fn get_pos_params( client: &C, @@ -771,6 +701,34 @@ pub async fn get_delegators_delegation< ) } +/// Get the delegator's delegation at some epoh +pub async fn get_delegators_delegation_at< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + address: &Address, + epoch: Epoch, +) -> HashMap { + unwrap_client_response::( + RPC.vp() + .pos() + .delegations(client, address, &Some(epoch)) + .await, + ) +} + +/// Query proposal by Id +pub async fn query_proposal_by_id( + client: &C, + proposal_id: u64, +) -> Option { + // let a = RPC.vp().gov().proposal_id(client, &proposal_id).await; + // println!("{:?}", a.err().unwrap()); + unwrap_client_response::( + RPC.vp().gov().proposal_id(client, &proposal_id).await, + ) +} + /// Query and return validator's commission rate and max commission rate change /// per epoch pub async fn query_commission_rate( @@ -798,6 +756,42 @@ pub async fn query_bond( ) } +/// Query the accunt substorage space of an address +pub async fn get_account_info( + client: &C, + owner: &Address, +) -> Option { + unwrap_client_response::>( + RPC.shell().account(client, owner).await, + ) +} + +/// Query if the public_key is revealed +pub async fn is_public_key_revealed< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + owner: &Address, +) -> bool { + unwrap_client_response::(RPC.shell().revealed(client, owner).await) +} + +/// Query an account substorage at a specific index +pub async fn get_public_key_at( + client: &C, + owner: &Address, + index: u8, +) -> Option { + let account = unwrap_client_response::>( + RPC.shell().account(client, owner).await, + ); + if let Some(account) = account { + account.get_public_key_from_index(index) + } else { + None + } +} + /// Query a validator's unbonds for a given epoch pub async fn query_and_print_unbonds< C: crate::ledger::queries::Client + Sync, @@ -871,11 +865,11 @@ pub async fn query_unbond_with_slashing< } /// Get the givernance parameters -pub async fn get_governance_parameters< +pub async fn query_governance_parameters< C: crate::ledger::queries::Client + Sync, >( client: &C, -) -> GovParams { +) -> GovernanceParameters { let key = gov_storage::get_max_proposal_code_size_key(); let max_proposal_code_size = query_storage_value::(client, &key) .await @@ -896,27 +890,37 @@ pub async fn get_governance_parameters< .await .expect("Parameter should be definied."); - let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(client, &key) - .await - .expect("Parameter should be definied."); + let key = gov_storage::get_min_proposal_voting_period_key(); + let min_proposal_voting_period = + query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); let key = gov_storage::get_max_proposal_period_key(); let max_proposal_period = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); - GovParams { - min_proposal_fund: u128::try_from(min_proposal_fund) - .expect("Amount out of bounds") as u64, + GovernanceParameters { + min_proposal_fund, max_proposal_code_size, - min_proposal_period, + min_proposal_voting_period, max_proposal_period, max_proposal_content_size, min_proposal_grace_epochs, } } +/// Get the givernance parameters +pub async fn query_proposal_votes( + client: &C, + proposal_id: u64, +) -> Vec { + unwrap_client_response::>( + RPC.vp().gov().proposal_id_votes(client, &proposal_id).await, + ) +} + /// Get the bond amount at the given epoch pub async fn get_bond_amount_at( client: &C, @@ -947,7 +951,6 @@ pub async fn bonds_and_unbonds( .await, ) } - /// Get bonds and unbonds with all details (slashes and rewards, if any) /// grouped by their bond IDs, enriched with extra information calculated from /// the data. diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index a087e66c7c..5c1e6a975c 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -1,12 +1,6 @@ //! Functions to sign transactions use std::collections::HashMap; -#[cfg(feature = "std")] -use std::env; -#[cfg(feature = "std")] -use std::fs::File; use std::io::ErrorKind; -#[cfg(feature = "std")] -use std::io::Write; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER; @@ -15,11 +9,14 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::components::sapling::fees::{ InputView, OutputView, }; +use namada_core::proto::SignatureIndex; +use namada_core::types::account::AccountPublicKeysMap; use namada_core::types::address::{ masp, masp_tx_key, Address, ImplicitAddress, }; +// use namada_core::types::storage::Key; use namada_core::types::token::{self, Amount, DenominatedAmount, MaspDenom}; -use namada_core::types::transaction::{pos, MIN_FEE}; +use namada_core::types::transaction::pos; use prost::Message; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; @@ -28,29 +25,27 @@ use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; 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::{ - format_denominated_amount, query_wasm_code_hash, TxBroadcastData, -}; +use crate::ledger::rpc::{format_denominated_amount, query_wasm_code_hash}; 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, - TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_VP_WASM, + TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, VP_USER_WASM, }; pub use crate::ledger::wallet::store::AddressVpType; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; -use crate::proto::{MaspBuilder, Section, Signature, Tx}; +use crate::proto::{MaspBuilder, Section, Tx}; use crate::types::key::*; use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; use crate::types::storage::Epoch; use crate::types::token::Transfer; +use crate::types::transaction::account::{InitAccount, UpdateAccount}; use crate::types::transaction::governance::{ InitProposalData, VoteProposalData, }; -use crate::types::transaction::{ - Fee, InitAccount, InitValidator, TxType, UpdateVp, WrapperTx, -}; +use crate::types::transaction::pos::InitValidator; +use crate::types::transaction::{Fee, TxType}; #[cfg(feature = "std")] /// Env. var specifying where to store signing test vectors @@ -59,6 +54,19 @@ const ENV_VAR_LEDGER_LOG_PATH: &str = "NAMADA_LEDGER_LOG_PATH"; /// Env. var specifying where to store transaction debug outputs const ENV_VAR_TX_LOG_PATH: &str = "NAMADA_TX_LOG_PATH"; +/// A struture holding the signing data to craft a transaction +#[derive(Clone)] +pub struct SigningTxData { + /// The public keys associated to an account + pub public_keys: Vec, + /// The threshold associated to an account + pub threshold: u8, + /// The public keys to index map associated to an account + pub account_public_keys_map: Option, + /// The public keys of the fee payer + pub gas_payer: common::PublicKey, +} + /// Find the public key for the given address and try to load the keypair /// for it from the wallet. If the keypair is encrypted but a password is not /// supplied, then it is interactively prompted. Errors if the key cannot be @@ -78,12 +86,12 @@ pub async fn find_pk< "Looking-up public key of {} from the ledger...", addr.encode() ); - rpc::get_public_key(client, addr).await.ok_or(Error::Other( - format!( + rpc::get_public_key_at(client, addr, 0) + .await + .ok_or(Error::Other(format!( "No public key found for the address {}", addr.encode() - ), - )) + ))) } Address::Implicit(ImplicitAddress(pkh)) => Ok(wallet .find_key_by_pkh(pkh, password) @@ -109,82 +117,57 @@ pub async fn find_pk< pub fn find_key_by_pk( wallet: &mut Wallet, args: &args::Tx, - keypair: &common::PublicKey, + public_key: &common::PublicKey, ) -> Result { - if *keypair == masp_tx_key().ref_to() { + if *public_key == masp_tx_key().ref_to() { // We already know the secret key corresponding to the MASP sentinal key Ok(masp_tx_key()) - } else if args - .signing_key - .as_ref() - .map(|x| x.ref_to() == *keypair) - .unwrap_or(false) - { - // We can lookup the secret key from the CLI arguments in this case - Ok(args.signing_key.clone().unwrap()) } else { // Otherwise we need to search the wallet for the secret key wallet - .find_key_by_pk(keypair, args.password.clone()) + .find_key_by_pk(public_key, args.password.clone()) .map_err(|err| { Error::Other(format!( "Unable to load the keypair from the wallet for public \ key {}. Failed with: {}", - keypair, err + public_key, err )) }) } } -/// Carries types that can be directly/indirectly used to sign a transaction. -#[allow(clippy::large_enum_variant)] -#[derive(Clone)] -pub enum TxSigningKey { - /// Do not sign any transaction - None, - /// Obtain the keypair corresponding to given address from wallet and sign - WalletAddress(Address), -} - /// Given CLI arguments and some defaults, determine the rightful transaction /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, an `Error` is returned. -pub async fn tx_signer< +pub async fn tx_signers< C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( client: &C, wallet: &mut Wallet, args: &args::Tx, - default: TxSigningKey, -) -> Result<(Option
, common::PublicKey), Error> { - let signer = if args.dry_run { - // We cannot override the signer if we're doing a dry run - default - } else if let Some(signing_key) = &args.signing_key { - // Otherwise use the signing key override provided by user - return Ok((None, signing_key.ref_to())); + default: Option
, +) -> Result, Error> { + let signer = if !&args.signing_keys.is_empty() { + let public_keys = + args.signing_keys.iter().map(|key| key.ref_to()).collect(); + return Ok(public_keys); } else if let Some(verification_key) = &args.verification_key { - return Ok((None, verification_key.clone())); - } else if let Some(signer) = &args.signer { - // Otherwise use the signer address provided by user - TxSigningKey::WalletAddress(signer.clone()) + return Ok(vec![verification_key.clone()]); } else { // Otherwise use the signer determined by the caller default }; + // Now actually fetch the signing key and apply it match signer { - TxSigningKey::WalletAddress(signer) if signer == masp() => { - Ok((None, masp_tx_key().ref_to())) - } - TxSigningKey::WalletAddress(signer) => Ok(( - Some(signer.clone()), + Some(signer) if signer == masp() => Ok(vec![masp_tx_key().ref_to()]), + Some(signer) => Ok(vec![ find_pk::(client, wallet, &signer, args.password.clone()) .await?, - )), - TxSigningKey::None => other_err( + ]), + None => other_err( "All transactions must be signed; please either specify the key \ or the address from which to look up the signing key." .to_string(), @@ -200,26 +183,88 @@ pub async fn tx_signer< /// hashes needed for monitoring the tx on chain. /// /// If it is a dry run, it is not put in a wrapper, but returned as is. -pub async fn sign_tx( +pub fn sign_tx( wallet: &mut Wallet, + args: &args::Tx, tx: &mut Tx, + signing_data: SigningTxData, +) { + if !args.signatures.is_empty() { + let signatures = args + .signatures + .iter() + .map(|bytes| SignatureIndex::deserialize(bytes).unwrap()) + .collect(); + tx.add_signatures(signatures); + } else if let Some(account_public_keys_map) = + signing_data.account_public_keys_map + { + let signing_tx_keypairs = signing_data + .public_keys + .iter() + .filter_map(|public_key| { + match find_key_by_pk(wallet, args, public_key) { + Ok(secret_key) => Some(secret_key), + Err(_) => None, + } + }) + .collect::>(); + tx.sign_raw(signing_tx_keypairs, account_public_keys_map); + } + + let fee_payer_keypair = + find_key_by_pk(wallet, args, &signing_data.gas_payer).expect(""); + tx.sign_wrapper(fee_payer_keypair); +} + +/// Return the necessary data regarding an account to be able to generate a +/// multisignature section +pub async fn aux_signing_data< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( + client: &C, + wallet: &mut Wallet, args: &args::Tx, - keypair: &common::PublicKey, -) -> Result<(), Error> { - let keypair = find_key_by_pk(wallet, args, keypair)?; - // Sign over the transacttion data - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, - ))); - // Remove all the sensitive sections - tx.protocol_filter(); - // Then sign over the bound wrapper - tx.add_section(Section::Signature(Signature::new( - tx.sechashes(), - &keypair, - ))); - Ok(()) + owner: &Option
, + default_signer: Option
, +) -> Result { + let public_keys = if owner.is_some() || args.gas_payer.is_none() { + tx_signers::(client, wallet, args, default_signer.clone()).await? + } else { + vec![] + }; + + let (account_public_keys_map, threshold) = match owner { + Some(owner @ Address::Established(_)) => { + let account = rpc::get_account_info::(client, owner).await; + if let Some(account) = account { + (Some(account.public_keys_map), account.threshold) + } else { + return Err(Error::InvalidAccount(owner.encode())); + } + } + Some(Address::Implicit(_)) => ( + Some(AccountPublicKeysMap::from_iter(public_keys.clone())), + 1u8, + ), + Some(owner @ Address::Internal(_)) => { + return Err(Error::InvalidAccount(owner.encode())); + } + None => (None, 0u8), + }; + + let gas_payer = match &args.gas_payer { + Some(keypair) => keypair.ref_to(), + None => public_keys.get(0).ok_or(Error::InvalidFeePayer)?.clone(), + }; + + Ok(SigningTxData { + public_keys, + threshold, + account_public_keys_map, + gas_payer, + }) } #[cfg(not(feature = "mainnet"))] @@ -232,26 +277,26 @@ pub async fn solve_pow_challenge( requires_pow: bool, ) -> (Option, Fee) { let wrapper_tx_fees_key = parameter_storage::get_wrapper_tx_fees_key(); - let fee_amount = rpc::query_storage_value::( + let gas_amount = rpc::query_storage_value::( client, &wrapper_tx_fees_key, ) .await .unwrap_or_default(); - let fee_token = &args.fee_token; + let gas_token = &args.gas_token; let source = Address::from(keypair); - let balance_key = token::balance_key(fee_token, &source); + let balance_key = token::balance_key(gas_token, &source); let balance = rpc::query_storage_value::(client, &balance_key) .await .unwrap_or_default(); - let is_bal_sufficient = fee_amount <= balance; + let is_bal_sufficient = gas_amount <= balance; if !is_bal_sufficient { - let token_addr = args.fee_token.clone(); + let token_addr = args.gas_token.clone(); let err_msg = format!( "The wrapper transaction source doesn't have enough balance to \ pay fee {}, got {}.", - format_denominated_amount(client, &token_addr, fee_amount).await, + format_denominated_amount(client, &token_addr, gas_amount).await, format_denominated_amount(client, &token_addr, balance).await, ); if !args.force && cfg!(feature = "mainnet") { @@ -259,13 +304,13 @@ pub async fn solve_pow_challenge( } } let fee = Fee { - amount: fee_amount, - token: fee_token.clone(), + amount: gas_amount, + token: gas_token.clone(), }; // A PoW solution can be used to allow zero-fee testnet transactions // If the address derived from the keypair doesn't have enough balance // to pay for the fee, allow to find a PoW solution instead. - if requires_pow || !is_bal_sufficient { + if (requires_pow || !is_bal_sufficient) && !args.dump_tx { println!("The transaction requires the completion of a PoW challenge."); // Obtain a PoW challenge for faucet withdrawal let challenge = rpc::get_testnet_pow_challenge(client, source).await; @@ -298,210 +343,26 @@ pub async fn update_pow_challenge( /// Create a wrapper tx from a normal tx. Get the hash of the /// wrapper and its payload which is needed for monitoring its /// progress on chain. -pub async fn wrap_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn wrap_tx( client: &C, - #[allow(unused_variables)] wallet: &mut Wallet, + tx: &mut Tx, args: &args::Tx, epoch: Epoch, - mut tx: Tx, - keypair: &common::PublicKey, + gas_payer: common::PublicKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Tx { +) { #[cfg(not(feature = "mainnet"))] let (pow_solution, fee) = - solve_pow_challenge(client, args, keypair, requires_pow).await; - // This object governs how the payload will be processed - tx.update_header(TxType::Wrapper(Box::new(WrapperTx::new( - fee, - keypair.clone(), - epoch, - args.gas_limit.clone(), - #[cfg(not(feature = "mainnet"))] - pow_solution, - )))); - tx.header.chain_id = args.chain_id.clone().unwrap(); - tx.header.expiration = args.expiration; - - #[cfg(feature = "std")] - // Attempt to decode the construction - if let Ok(path) = env::var(ENV_VAR_LEDGER_LOG_PATH) { - let mut tx = tx.clone(); - // Contract the large data blobs in the transaction - tx.wallet_filter(); - // Convert the transaction to Ledger format - let decoding = to_ledger_vector(client, wallet, &tx) - .await - .expect("unable to decode transaction"); - let output = serde_json::to_string(&decoding) - .expect("failed to serialize decoding"); - // Record the transaction at the identified path - let mut f = File::options() - .append(true) - .create(true) - .open(path) - .expect("failed to open test vector file"); - writeln!(f, "{},", output) - .expect("unable to write test vector to file"); - } - #[cfg(feature = "std")] - // Attempt to decode the construction - if let Ok(path) = env::var(ENV_VAR_TX_LOG_PATH) { - let mut tx = tx.clone(); - // Contract the large data blobs in the transaction - tx.wallet_filter(); - // Record the transaction at the identified path - let mut f = File::options() - .append(true) - .create(true) - .open(path) - .expect("failed to open test vector file"); - writeln!(f, "{:x?},", tx).expect("unable to write test vector to file"); - } - - tx -} - -/// Create a wrapper tx from a normal tx. Get the hash of the -/// wrapper and its payload which is needed for monitoring its -/// progress on chain. -pub async fn sign_wrapper< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( - client: &C, - #[allow(unused_variables)] wallet: &mut Wallet, - args: &args::Tx, - epoch: Epoch, - mut tx: Tx, - keypair: &common::SecretKey, - #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> TxBroadcastData { - let fee_amount = if cfg!(feature = "mainnet") { - Amount::native_whole(MIN_FEE) - } else { - let wrapper_tx_fees_key = parameter_storage::get_wrapper_tx_fees_key(); - rpc::query_storage_value::( - client, - &wrapper_tx_fees_key, - ) - .await - .unwrap_or_default() - }; - let fee_token = &args.fee_token; - let source = Address::from(&keypair.ref_to()); - let balance_key = token::balance_key(fee_token, &source); - let balance = - rpc::query_storage_value::(client, &balance_key) - .await - .unwrap_or_default(); - let is_bal_sufficient = fee_amount <= balance; - if !is_bal_sufficient { - let token_addr = args.fee_token.clone(); - let err_msg = format!( - "The wrapper transaction source doesn't have enough balance to \ - 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!("{}", err_msg); - } - } + solve_pow_challenge(client, args, &gas_payer, requires_pow).await; - #[cfg(not(feature = "mainnet"))] - // A PoW solution can be used to allow zero-fee testnet transactions - let pow_solution: Option = { - // If the address derived from the keypair doesn't have enough balance - // to pay for the fee, allow to find a PoW solution instead. - if requires_pow || !is_bal_sufficient { - println!( - "The transaction requires the completion of a PoW challenge." - ); - // Obtain a PoW challenge for faucet withdrawal - let challenge = - rpc::get_testnet_pow_challenge(client, source).await; - - // Solve the solution, this blocks until a solution is found - let solution = challenge.solve(); - Some(solution) - } else { - None - } - }; - - // This object governs how the payload will be processed - tx.update_header(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: fee_amount, - token: fee_token.clone(), - }, - keypair.ref_to(), + tx.add_wrapper( + fee, + gas_payer, epoch, args.gas_limit.clone(), #[cfg(not(feature = "mainnet"))] pow_solution, - )))); - tx.header.chain_id = args.chain_id.clone().unwrap(); - tx.header.expiration = args.expiration; - - #[cfg(feature = "std")] - // Attempt to decode the construction - if let Ok(path) = env::var(ENV_VAR_LEDGER_LOG_PATH) { - let mut tx = tx.clone(); - // Contract the large data blobs in the transaction - tx.wallet_filter(); - // Convert the transaction to Ledger format - let decoding = to_ledger_vector(client, wallet, &tx) - .await - .expect("unable to decode transaction"); - let output = serde_json::to_string(&decoding) - .expect("failed to serialize decoding"); - // Record the transaction at the identified path - let mut f = File::options() - .append(true) - .create(true) - .open(path) - .expect("failed to open test vector file"); - writeln!(f, "{},", output) - .expect("unable to write test vector to file"); - } - #[cfg(feature = "std")] - // Attempt to decode the construction - if let Ok(path) = env::var(ENV_VAR_TX_LOG_PATH) { - let mut tx = tx.clone(); - // Contract the large data blobs in the transaction - tx.wallet_filter(); - // Record the transaction at the identified path - let mut f = File::options() - .append(true) - .create(true) - .open(path) - .expect("failed to open test vector file"); - writeln!(f, "{:x?},", tx).expect("unable to write test vector to file"); - } - - // Remove all the sensitive sections - tx.protocol_filter(); - // 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 - // on-chain - let decrypted_hash = tx - .clone() - .update_header(TxType::Raw) - .header_hash() - .to_string(); - TxBroadcastData::Wrapper { - tx, - wrapper_hash, - decrypted_hash, - } + ); } #[allow(clippy::result_large_err)] @@ -711,6 +572,55 @@ pub async fn make_ledger_masp_endpoints< } } +/// Internal method used to generate transaction test vectors +#[cfg(feature = "std")] +pub async fn generate_test_vector< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( + client: &C, + wallet: &mut Wallet, + tx: &Tx, +) { + use std::env; + use std::fs::File; + use std::io::Write; + + if let Ok(path) = env::var(ENV_VAR_LEDGER_LOG_PATH) { + let mut tx = tx.clone(); + // Contract the large data blobs in the transaction + tx.wallet_filter(); + // Convert the transaction to Ledger format + let decoding = to_ledger_vector(client, wallet, &tx) + .await + .expect("unable to decode transaction"); + let output = serde_json::to_string(&decoding) + .expect("failed to serialize decoding"); + // Record the transaction at the identified path + let mut f = File::options() + .append(true) + .create(true) + .open(path) + .expect("failed to open test vector file"); + writeln!(f, "{},", output) + .expect("unable to write test vector to file"); + } + + // Attempt to decode the construction + if let Ok(path) = env::var(ENV_VAR_TX_LOG_PATH) { + let mut tx = tx.clone(); + // Contract the large data blobs in the transaction + tx.wallet_filter(); + // Record the transaction at the identified path + let mut f = File::options() + .append(true) + .create(true) + .open(path) + .expect("failed to open test vector file"); + writeln!(f, "{:x?},", tx).expect("unable to write test vector to file"); + } +} + /// Converts the given transaction to the form that is displayed on the Ledger /// device pub async fn to_ledger_vector< @@ -736,9 +646,10 @@ pub async fn to_ledger_vector< .unwrap(); let reveal_pk_hash = query_wasm_code_hash(client, TX_REVEAL_PK).await.unwrap(); - let update_vp_hash = query_wasm_code_hash(client, TX_UPDATE_VP_WASM) - .await - .unwrap(); + let update_account_hash = + query_wasm_code_hash(client, TX_UPDATE_ACCOUNT_WASM) + .await + .unwrap(); let transfer_hash = query_wasm_code_hash(client, TX_TRANSFER_WASM) .await .unwrap(); @@ -809,12 +720,12 @@ pub async fn to_ledger_vector< tv.output.extend(vec![ format!("Type : Init Account"), - format!("Public key : {}", init_account.public_key), + format!("Public key : {:?}", init_account.public_keys), format!("VP type : {}", vp_code), ]); tv.output_expert.extend(vec![ - format!("Public key : {}", init_account.public_key), + format!("Public key : {:?}", init_account.public_keys), format!("VP type : {}", HEXLOWER.encode(&extra.0)), ]); } else if code_hash == init_validator_hash { @@ -839,7 +750,7 @@ pub async fn to_ledger_vector< tv.output.extend(vec![ format!("Type : Init Validator"), - format!("Account key : {}", init_validator.account_key), + format!("Account key : {:?}", init_validator.account_keys), format!("Consensus key : {}", init_validator.consensus_key), format!("Protocol key : {}", init_validator.protocol_key), format!("DKG key : {}", init_validator.dkg_key), @@ -852,7 +763,7 @@ pub async fn to_ledger_vector< ]); tv.output_expert.extend(vec![ - format!("Account key : {}", init_validator.account_key), + format!("Account key : {:?}", init_validator.account_keys), format!("Consensus key : {}", init_validator.consensus_key), format!("Protocol key : {}", init_validator.protocol_key), format!("DKG key : {}", init_validator.dkg_key), @@ -950,36 +861,40 @@ pub async fn to_ledger_vector< tv.output_expert .extend(vec![format!("Public key : {}", public_key)]); - } else if code_hash == update_vp_hash { + } else if code_hash == update_account_hash { let transfer = - UpdateVp::try_from_slice(&tx.data().ok_or_else(|| { + UpdateAccount::try_from_slice(&tx.data().ok_or_else(|| { std::io::Error::from(ErrorKind::InvalidData) })?)?; tv.name = "Update VP 0".to_string(); - let extra = tx - .get_section(&transfer.vp_code_hash) - .and_then(|x| Section::extra_data_sec(x.as_ref())) - .expect("unable to load vp code") - .code - .hash(); - let vp_code = if extra == user_hash { - "User".to_string() - } else { - HEXLOWER.encode(&extra.0) - }; - - tv.output.extend(vec![ - format!("Type : Update VP"), - format!("Address : {}", transfer.addr), - format!("VP type : {}", vp_code), - ]); + match &transfer.vp_code_hash { + Some(hash) => { + let extra = tx + .get_section(hash) + .and_then(|x| Section::extra_data_sec(x.as_ref())) + .expect("unable to load vp code") + .code + .hash(); + let vp_code = if extra == user_hash { + "User".to_string() + } else { + HEXLOWER.encode(&extra.0) + }; + tv.output.extend(vec![ + format!("Type : Update VP"), + format!("Address : {}", transfer.addr), + format!("VP type : {}", vp_code), + ]); - tv.output_expert.extend(vec![ - format!("Address : {}", transfer.addr), - format!("VP type : {}", HEXLOWER.encode(&extra.0)), - ]); + tv.output_expert.extend(vec![ + format!("Address : {}", transfer.addr), + format!("VP type : {}", HEXLOWER.encode(&extra.0)), + ]); + } + None => (), + }; } else if code_hash == transfer_hash { let transfer = Transfer::try_from_slice(&tx.data().ok_or_else(|| { diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 8fae45cb4e..aa9050519e 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -1,6 +1,7 @@ //! SDK functions to construct different types of transactions use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, HashSet}; +use std::fs::File; use std::time::Duration; use borsh::BorshSerialize; @@ -14,17 +15,24 @@ use masp_primitives::transaction::components::transparent::fees::{ InputView as TransparentInputView, OutputView as TransparentOutputView, }; use masp_primitives::transaction::components::Amount; -use namada_core::types::address::{ - masp, masp_tx_key, Address, InternalAddress, +use namada_core::ledger::governance::cli::onchain::{ + DefaultProposal, PgfFundingProposal, PgfStewardProposal, ProposalVote, }; +use namada_core::ledger::governance::storage::proposal::ProposalType; +use namada_core::ledger::governance::storage::vote::StorageProposalVote; +use namada_core::types::address::{masp, Address, InternalAddress}; use namada_core::types::dec::Dec; use namada_core::types::token::MaspDenom; +use namada_core::types::transaction::governance::{ + InitProposalData, VoteProposalData, +}; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; use prost::EncodeError; use thiserror::Error; use super::rpc::query_wasm_code_hash; +use super::signing; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc::applications::transfer::packet::PacketData; use crate::ibc::applications::transfer::PrefixedCoin; @@ -33,16 +41,14 @@ use crate::ibc::core::timestamp::Timestamp as IbcTimestamp; use crate::ibc::core::Msg; use crate::ibc::Height as IbcHeight; use crate::ledger::args::{self, InputAmount}; -use crate::ledger::governance::storage as gov_storage; use crate::ledger::ibc::storage::ibc_denom_key; -use crate::ledger::masp::{ShieldedContext, ShieldedUtils}; +use crate::ledger::masp::{ShieldedContext, ShieldedTransfer, ShieldedUtils}; use crate::ledger::rpc::{ self, format_denominated_amount, validate_amount, TxBroadcastData, TxResponse, }; -use crate::ledger::signing::{tx_signer, wrap_tx, TxSigningKey}; use crate::ledger::wallet::{Wallet, WalletUtils}; -use crate::proto::{Code, Data, MaspBuilder, Section, Tx}; +use crate::proto::{MaspBuilder, Tx}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; use crate::types::control_flow::{time, ProceedOrElse}; @@ -50,7 +56,8 @@ use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; -use crate::types::transaction::{pos, InitAccount, TxType, UpdateVp}; +use crate::types::transaction::account::{InitAccount, UpdateAccount}; +use crate::types::transaction::{pos, TxType}; use crate::types::{storage, token}; use crate::vm; use crate::vm::WasmValidationError; @@ -66,7 +73,7 @@ pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; /// Reveal public key transaction WASM path pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; /// Update validity predicate WASM path -pub const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; +pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; /// Transfer transaction WASM path pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; /// IBC transaction WASM path @@ -199,6 +206,21 @@ pub enum Error { /// Like EncodeTxFailure but for the encode error type #[error("Encoding tx data, {0}, shouldn't fail")] EncodeFailure(EncodeError), + /// Failed to deserialize the proposal data from json + #[error("Failed to deserialize the proposal data: {0}")] + FailedGovernaneProposalDeserialize(String), + /// The proposal data are invalid + #[error("Proposal data are invalid: {0}")] + InvalidProposal(String), + /// The proposal vote is not valid + #[error("Proposal vote is invalid")] + InvalidProposalVote, + /// The proposal can't be voted + #[error("Proposal {0} can't be voted")] + InvalidProposalVotingPeriod(u64), + /// The proposal can't be found + #[error("Proposal {0} can't be found")] + ProposalDoesNotExist(u64), /// Encoding public key failure #[error("Encoding a public key, {0}, shouldn't fail")] EncodeKeyFailure(std::io::Error), @@ -222,6 +244,18 @@ pub enum Error { /// Epoch not in storage #[error("Proposal end epoch is not in the storage.")] EpochNotInStorage, + /// Couldn't understand who the fee payer is + #[error("Either --signing-keys or --gas-payer must be available.")] + InvalidFeePayer, + /// Account threshold is not set + #[error("Account threshold must be set.")] + MissingAccountThreshold, + /// Not enough signature + #[error("Account threshold is {0} but the valid signatures are {1}.")] + MissingSigningKeys(u8, u8), + /// Invalid owner account + #[error("The source account {0} is not valid or doesn't exist.")] + InvalidAccount(String), /// Other Errors that may show up when using the interface #[error("{0}")] Other(String), @@ -235,6 +269,8 @@ pub enum ProcessTxResponse { Broadcast(Response), /// Result of dry running transaction DryRun, + /// Dump transaction to disk + Dump, } impl ProcessTxResponse { @@ -247,40 +283,42 @@ impl ProcessTxResponse { } } +/// Build and dump a transaction either to file or to screen +pub fn dump_tx(args: &args::Tx, tx: Tx) { + let tx_id = tx.header_hash(); + let serialized_tx = tx.serialize(); + match args.output_folder.to_owned() { + Some(path) => { + let tx_filename = format!("{}.tx", tx_id); + let out = File::create(path.join(tx_filename)).unwrap(); + serde_json::to_writer_pretty(out, &serialized_tx) + .expect("Should be able to write to file.") + } + None => println!("{}", serialized_tx), + } +} + /// Prepare a transaction for signing and submission by adding a wrapper header /// to it. -pub async fn prepare_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn prepare_tx( client: &C, - wallet: &mut Wallet, args: &args::Tx, - tx: Tx, - default_signer: TxSigningKey, + tx: &mut Tx, + gas_payer: common::PublicKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let (signer_addr, signer_pk) = - tx_signer::(client, wallet, args, default_signer.clone()).await?; - if args.dry_run { - Ok((tx, signer_addr, signer_pk)) - } else { +) { + if !args.dry_run { let epoch = rpc::query_epoch(client).await; - Ok(( - wrap_tx( - client, - wallet, - args, - epoch, - tx.clone(), - &signer_pk, - #[cfg(not(feature = "mainnet"))] - requires_pow, - ) - .await, - signer_addr, - signer_pk, - )) + signing::wrap_tx( + client, + tx, + args, + epoch, + gas_payer, + #[cfg(not(feature = "mainnet"))] + requires_pow, + ) + .await } } @@ -293,10 +331,8 @@ pub async fn process_tx< client: &C, wallet: &mut Wallet, args: &args::Tx, - mut tx: Tx, + tx: Tx, ) -> Result { - // Remove all the sensitive sections - tx.protocol_filter(); // NOTE: use this to print the request JSON body: // let request = @@ -347,72 +383,38 @@ pub async fn process_tx< } } -/// Submit transaction to reveal public key -pub async fn build_reveal_pk< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( - client: &C, - wallet: &mut Wallet, - args: args::RevealPk, -) -> Result, common::PublicKey)>, Error> { - let args::RevealPk { - tx: args, - public_key, - } = args; - let public_key = public_key; - if !is_reveal_pk_needed::(client, &public_key, &args).await? { - let addr: Address = (&public_key).into(); - println!("PK for {addr} is already revealed, nothing to do."); - Ok(None) - } else { - // If not, submit it - Ok(Some( - build_reveal_pk_aux::(client, wallet, &public_key, &args) - .await?, - )) - } -} - -/// Submit transaction to rveeal public key if needed -pub async fn is_reveal_pk_needed< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +/// Check if a reveal public key transaction is needed +pub async fn is_reveal_pk_needed( client: &C, - public_key: &common::PublicKey, - args: &args::Tx, + address: &Address, + force: bool, ) -> Result where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, { - let addr: Address = public_key.into(); // Check if PK revealed - Ok(args.force || !has_revealed_pk(client, &addr).await) + Ok(force || !has_revealed_pk(client, address).await) } /// Check if the public key for the given address has been revealed pub async fn has_revealed_pk( client: &C, - addr: &Address, + address: &Address, ) -> bool { - rpc::get_public_key(client, addr).await.is_some() + rpc::is_public_key_revealed(client, address).await } /// Submit transaction to reveal the given public key -pub async fn build_reveal_pk_aux< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_reveal_pk( client: &C, - wallet: &mut Wallet, - public_key: &common::PublicKey, args: &args::Tx, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let addr: Address = public_key.into(); - println!("Submitting a tx to reveal the public key for address {addr}..."); - let tx_data = public_key.try_to_vec().map_err(Error::EncodeKeyFailure)?; + address: &Address, + public_key: &common::PublicKey, + gas_payer: &common::PublicKey, +) -> Result { + println!( + "Submitting a tx to reveal the public key for address {address}..." + ); let tx_code_hash = query_wasm_code_hash( client, @@ -421,22 +423,21 @@ pub async fn build_reveal_pk_aux< .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.chain_id.clone().expect("value should be there"); - tx.header.expiration = args.expiration; - tx.set_data(Data::new(tx_data)); - tx.set_code(Code::from_hash(tx_code_hash)); + let chain_id = args.chain_id.clone().unwrap(); + + let mut tx = Tx::new(chain_id, args.expiration); + tx.add_code_from_hash(tx_code_hash).add_data(public_key); - prepare_tx::( + prepare_tx::( client, - wallet, args, - tx, - TxSigningKey::WalletAddress(addr), + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + Ok(tx) } /// Broadcast a transaction to be included in the blockchain and checks that @@ -634,26 +635,30 @@ pub async fn save_initialized_accounts( /// Submit validator comission rate change pub async fn build_validator_commission_change< C: crate::ledger::queries::Client + Sync, - U: WalletUtils, >( client: &C, - wallet: &mut Wallet, - args: args::CommissionRateChange, -) -> Result<(Tx, Option
, common::PublicKey), Error> { + args::CommissionRateChange { + tx: tx_args, + validator, + rate, + tx_code_path, + }: args::CommissionRateChange, + gas_payer: &common::PublicKey, +) -> Result { let epoch = rpc::query_epoch(client).await; let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); let params: PosParams = rpc::get_pos_params(client).await; - let validator = args.validator.clone(); + let validator = validator.clone(); if rpc::is_validator(client, &validator).await { - if args.rate < Dec::zero() || args.rate > Dec::one() { - eprintln!("Invalid new commission rate, received {}", args.rate); - return Err(Error::InvalidCommissionRate(args.rate)); + if rate < Dec::zero() || rate > Dec::one() { + eprintln!("Invalid new commission rate, received {}", rate); + return Err(Error::InvalidCommissionRate(rate)); } let pipeline_epoch_minus_one = epoch + params.pipeline_len - 1; @@ -669,7 +674,7 @@ pub async fn build_validator_commission_change< commission_rate, max_commission_change_per_epoch, }) => { - if args.rate.abs_diff(&commission_rate) + if rate.abs_diff(&commission_rate) > max_commission_change_per_epoch { eprintln!( @@ -677,109 +682,62 @@ pub async fn build_validator_commission_change< the predecessor epoch in which the rate will take \ effect." ); - if !args.tx.force { - return Err(Error::InvalidCommissionRate(args.rate)); + if !tx_args.force { + return Err(Error::InvalidCommissionRate(rate)); } } } None => { eprintln!("Error retrieving from storage"); - if !args.tx.force { + if !tx_args.force { return Err(Error::Retrieval); } } } } else { eprintln!("The given address {validator} is not a validator."); - if !args.tx.force { + if !tx_args.force { return Err(Error::InvalidValidatorAddress(validator)); } } let data = pos::CommissionChange { - validator: args.validator.clone(), - new_rate: args.rate, + validator: validator.clone(), + new_rate: rate, }; - let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + tx.add_code_from_hash(tx_code_hash).add_data(data); - let default_signer = args.validator.clone(); - prepare_tx::( + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + Ok(tx) } /// Submit transaction to unjail a jailed validator pub async fn build_unjail_validator< C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( - client: &C, - wallet: &mut Wallet, - args: args::TxUnjailValidator, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - if !rpc::is_validator(client, &args.validator).await { - eprintln!("The given address {} is not a validator.", &args.validator); - if !args.tx.force { - return Err(Error::InvalidValidatorAddress(args.validator.clone())); - } - } - - let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) - .await - .unwrap(); - - let data = args - .validator - .clone() - .try_to_vec() - .map_err(Error::EncodeTxFailure)?; - - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); - - let default_signer = args.validator; - prepare_tx( - client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), - #[cfg(not(feature = "mainnet"))] - false, - ) - .await -} - -/// Submit transaction to unjail a jailed validator -pub async fn submit_unjail_validator< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, >( client: &C, - wallet: &mut Wallet, - args: args::TxUnjailValidator, -) -> Result<(), Error> { - if !rpc::is_validator(client, &args.validator).await { - eprintln!("The given address {} is not a validator.", &args.validator); - if !args.tx.force { - return Err(Error::InvalidValidatorAddress(args.validator.clone())); + args::TxUnjailValidator { + tx: tx_args, + validator, + tx_code_path, + }: args::TxUnjailValidator, + gas_payer: &common::PublicKey, +) -> Result { + if !rpc::is_validator(client, &validator).await { + eprintln!("The given address {} is not a validator.", &validator); + if !tx_args.force { + return Err(Error::InvalidValidatorAddress(validator.clone())); } } @@ -788,24 +746,22 @@ pub async fn submit_unjail_validator< let pipeline_epoch = current_epoch + params.pipeline_len; let validator_state_at_pipeline = - rpc::get_validator_state(client, &args.validator, Some(pipeline_epoch)) + rpc::get_validator_state(client, &validator, Some(pipeline_epoch)) .await .expect("Validator state should be defined."); if validator_state_at_pipeline != ValidatorState::Jailed { eprintln!( "The given validator address {} is not jailed at the pipeline \ epoch when it would be restored to one of the validator sets.", - &args.validator + &validator ); - if !args.tx.force { - return Err(Error::ValidatorNotCurrentlyJailed( - args.validator.clone(), - )); + if !tx_args.force { + return Err(Error::ValidatorNotCurrentlyJailed(validator.clone())); } } let last_slash_epoch_key = - crate::ledger::pos::validator_last_slash_key(&args.validator); + crate::ledger::pos::validator_last_slash_key(&validator); let last_slash_epoch = rpc::query_storage_value::(client, &last_slash_epoch_key) .await; @@ -816,66 +772,64 @@ pub async fn submit_unjail_validator< eprintln!( "The given validator address {} is currently frozen and not \ yet eligible to be unjailed.", - &args.validator + &validator ); - if !args.tx.force { + if !tx_args.force { return Err(Error::ValidatorNotCurrentlyJailed( - args.validator.clone(), + validator.clone(), )); } } } let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let data = args - .validator + let _data = validator .clone() .try_to_vec() .map_err(Error::EncodeTxFailure)?; - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + tx.add_code_from_hash(tx_code_hash) + .add_data(validator.clone()); - let default_signer = args.validator; prepare_tx( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await?; - Ok(()) + .await; + Ok(tx) } /// Submit transaction to withdraw an unbond -pub async fn build_withdraw< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_withdraw( client: &C, - wallet: &mut Wallet, - args: args::Withdraw, -) -> Result<(Tx, Option
, common::PublicKey), Error> { + args::Withdraw { + tx: tx_args, + validator, + source, + tx_code_path, + }: args::Withdraw, + gas_payer: &common::PublicKey, +) -> Result { let epoch = rpc::query_epoch(client).await; let validator = - known_validator_or_err(args.validator.clone(), args.tx.force, client) + known_validator_or_err(validator.clone(), tx_args.force, client) .await?; - let source = args.source.clone(); + let source = source.clone(); let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); @@ -888,6 +842,7 @@ pub async fn build_withdraw< Some(epoch), ) .await; + if tokens.is_zero() { eprintln!( "There are no unbonded bonds ready to withdraw in the current \ @@ -895,7 +850,7 @@ pub async fn build_withdraw< epoch ); rpc::query_and_print_unbonds(client, &bond_source, &validator).await; - if !args.tx.force { + if !tx_args.force { return Err(Error::NoUnbondReady(epoch)); } } else { @@ -907,25 +862,21 @@ pub async fn build_withdraw< } let data = pos::Withdraw { validator, source }; - let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + tx.add_code_from_hash(tx_code_hash).add_data(data); - let default_signer = args.source.unwrap_or(args.validator); - prepare_tx::( + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + Ok(tx) } /// Submit a transaction to unbond @@ -934,50 +885,49 @@ pub async fn build_unbond< U: WalletUtils, >( client: &C, - wallet: &mut Wallet, - args: args::Unbond, -) -> Result< - ( - Tx, - Option
, - common::PublicKey, - Option<(Epoch, token::Amount)>, - ), - Error, -> { - let source = args.source.clone(); + _wallet: &mut Wallet, + args::Unbond { + tx: tx_args, + validator, + amount, + source, + tx_code_path, + }: args::Unbond, + gas_payer: &common::PublicKey, +) -> Result<(Tx, Option<(Epoch, token::Amount)>), Error> { + let source = source.clone(); // Check the source's current bond amount - let bond_source = source.clone().unwrap_or_else(|| args.validator.clone()); + let bond_source = source.clone().unwrap_or_else(|| validator.clone()); let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - if !args.tx.force { - known_validator_or_err(args.validator.clone(), args.tx.force, client) + if !tx_args.force { + known_validator_or_err(validator.clone(), tx_args.force, client) .await?; let bond_amount = - rpc::query_bond(client, &bond_source, &args.validator, None).await; + rpc::query_bond(client, &bond_source, &validator, None).await; println!( "Bond amount available for unbonding: {} NAM", bond_amount.to_string_native() ); - if args.amount > bond_amount { + if 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.to_string_native(), + amount.to_string_native(), bond_amount.to_string_native() ); - if !args.tx.force { + if !tx_args.force { return Err(Error::LowerBondThanUnbond( bond_source, - args.amount.to_string_native(), + amount.to_string_native(), bond_amount.to_string_native(), )); } @@ -986,8 +936,7 @@ pub async fn build_unbond< // Query the unbonds before submitting the tx let unbonds = - rpc::query_unbond_with_slashing(client, &bond_source, &args.validator) - .await; + rpc::query_unbond_with_slashing(client, &bond_source, &validator).await; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); @@ -996,31 +945,26 @@ pub async fn build_unbond< let latest_withdrawal_pre = withdrawable.into_iter().last(); let data = pos::Unbond { - validator: args.validator.clone(), - amount: args.amount, - source, + validator: validator.clone(), + amount, + source: source.clone(), }; - let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + tx.add_code_from_hash(tx_code_hash).add_data(data); - let default_signer = args.source.unwrap_or_else(|| args.validator.clone()); - let (tx, signer_addr, default_signer) = prepare_tx::( + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await?; + .await; - Ok((tx, signer_addr, default_signer, latest_withdrawal_pre)) + Ok((tx, latest_withdrawal_pre)) } /// Query the unbonds post-tx @@ -1086,22 +1030,26 @@ pub async fn query_unbonds( } /// Submit a transaction to bond -pub async fn build_bond< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_bond( client: &C, - wallet: &mut Wallet, - args: args::Bond, -) -> Result<(Tx, Option
, common::PublicKey), Error> { + args::Bond { + tx: tx_args, + validator, + amount, + source, + native_token, + tx_code_path, + }: args::Bond, + gas_payer: &common::PublicKey, +) -> Result { let validator = - known_validator_or_err(args.validator.clone(), args.tx.force, client) + known_validator_or_err(validator.clone(), tx_args.force, client) .await?; // Check that the source address exists on chain - let source = args.source.clone(); - let source = match args.source.clone() { - Some(source) => source_exists_or_err(source, args.tx.force, client) + let source = source.clone(); + let source = match source.clone() { + Some(source) => source_exists_or_err(source, tx_args.force, client) .await .map(Some), None => Ok(source), @@ -1109,124 +1057,334 @@ pub async fn build_bond< // Check bond's source (source for delegation or validator for self-bonds) // balance let bond_source = source.as_ref().unwrap_or(&validator); - let balance_key = token::balance_key(&args.native_token, bond_source); + let balance_key = token::balance_key(&native_token, bond_source); // TODO Should we state the same error message for the native token? check_balance_too_low_err( - &args.native_token, + &native_token, bond_source, - args.amount, + amount, balance_key, - args.tx.force, + tx_args.force, client, ) .await?; let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let bond = pos::Bond { + let data = pos::Bond { validator, - amount: args.amount, + amount, source, }; - let data = bond.try_to_vec().map_err(Error::EncodeTxFailure)?; - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + tx.add_code_from_hash(tx_code_hash).add_data(data); - let default_signer = args.source.unwrap_or(args.validator); - prepare_tx::( + prepare_tx::( client, - wallet, - &args.tx, + &tx_args, + &mut tx, + gas_payer.clone(), + #[cfg(not(feature = "mainnet"))] + false, + ) + .await; + Ok(tx) +} + +/// Build a default proposal governance +pub async fn build_default_proposal< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + args::InitProposal { tx, - TxSigningKey::WalletAddress(default_signer), + proposal_data: _, + native_token: _, + is_offline: _, + is_pgf_stewards: _, + is_pgf_funding: _, + tx_code_path, + }: args::InitProposal, + proposal: DefaultProposal, + gas_payer: &common::PublicKey, +) -> Result { + let mut init_proposal_data = + InitProposalData::try_from(proposal.clone()) + .map_err(|e| Error::InvalidProposal(e.to_string()))?; + + let tx_code_hash = + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) + .await + .unwrap(); + + let chain_id = tx.chain_id.clone().unwrap(); + + let mut tx_builder = Tx::new(chain_id, tx.expiration); + + let (_, extra_section_hash) = tx_builder + .add_extra_section(proposal.proposal.content.try_to_vec().unwrap()); + init_proposal_data.content = extra_section_hash; + + if let Some(init_proposal_code) = proposal.data { + let (_, extra_section_hash) = + tx_builder.add_extra_section(init_proposal_code); + init_proposal_data.r#type = + ProposalType::Default(Some(extra_section_hash)); + }; + + tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(init_proposal_data); + + prepare_tx::( + client, + &tx, + &mut tx_builder, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + + Ok(tx_builder) } -/// Check if current epoch is in the last third of the voting period of the -/// proposal. This ensures that it is safe to optimize the vote writing to -/// storage. -pub async fn is_safe_voting_window( +/// Build a proposal vote +pub async fn build_vote_proposal( client: &C, - proposal_id: u64, - proposal_start_epoch: Epoch, -) -> Result { - let current_epoch = rpc::query_epoch(client).await; + args::VoteProposal { + tx, + proposal_id, + vote, + voter, + is_offline: _, + proposal_data: _, + tx_code_path, + }: args::VoteProposal, + epoch: Epoch, + gas_payer: &common::PublicKey, +) -> Result { + let proposal_vote = + ProposalVote::try_from(vote).map_err(|_| Error::InvalidProposalVote)?; + + let proposal_id = proposal_id.expect("Proposal id must be defined."); + let proposal = if let Some(proposal) = + rpc::query_proposal_by_id(client, proposal_id).await + { + proposal + } else { + return Err(Error::ProposalDoesNotExist(proposal_id)); + }; - let proposal_end_epoch_key = - gov_storage::get_voting_end_epoch_key(proposal_id); - let proposal_end_epoch = - rpc::query_storage_value::(client, &proposal_end_epoch_key) - .await; + let storage_vote = + StorageProposalVote::build(&proposal_vote, &proposal.r#type) + .expect("Should be able to build the proposal vote"); - match proposal_end_epoch { - Some(proposal_end_epoch) => { - Ok(!crate::ledger::native_vp::governance::utils::is_valid_validator_voting_period( - current_epoch, - proposal_start_epoch, - proposal_end_epoch, - )) - } - None => { - Err(Error::EpochNotInStorage) - } + let is_validator = rpc::is_validator(client, &voter).await; + + if !proposal.can_be_voted(epoch, is_validator) { + return Err(Error::InvalidProposalVotingPeriod(proposal_id)); } + + let delegations = rpc::get_delegators_delegation_at( + client, + &voter, + proposal.voting_start_epoch, + ) + .await + .keys() + .cloned() + .collect::>(); + + let data = VoteProposalData { + id: proposal_id, + vote: storage_vote, + voter: voter.clone(), + delegations, + }; + + let tx_code_hash = + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) + .await + .unwrap(); + + let chain_id = tx.chain_id.clone().unwrap(); + + let mut tx_builder = Tx::new(chain_id, tx.expiration); + tx_builder.add_code_from_hash(tx_code_hash).add_data(data); + + prepare_tx::( + client, + &tx, + &mut tx_builder, + gas_payer.clone(), + #[cfg(not(feature = "mainnet"))] + false, + ) + .await; + + Ok(tx_builder) } -/// Submit an IBC transfer -pub async fn build_ibc_transfer< +/// Build a pgf funding proposal governance +pub async fn build_pgf_funding_proposal< C: crate::ledger::queries::Client + Sync, - U: WalletUtils, >( client: &C, - wallet: &mut Wallet, - args: args::TxIbcTransfer, -) -> Result<(Tx, Option
, common::PublicKey), Error> { + args::InitProposal { + tx, + proposal_data: _, + native_token: _, + is_offline: _, + is_pgf_stewards: _, + is_pgf_funding: _, + tx_code_path, + }: args::InitProposal, + proposal: PgfFundingProposal, + gas_payer: &common::PublicKey, +) -> Result { + let mut init_proposal_data = + InitProposalData::try_from(proposal.clone()) + .map_err(|e| Error::InvalidProposal(e.to_string()))?; + + let tx_code_hash = + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) + .await + .unwrap(); + + let chain_id = tx.chain_id.clone().unwrap(); + + let mut tx_builder = Tx::new(chain_id, tx.expiration); + + let (_, extra_section_hash) = tx_builder + .add_extra_section(proposal.proposal.content.try_to_vec().unwrap()); + init_proposal_data.content = extra_section_hash; + + tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(init_proposal_data); + + prepare_tx::( + client, + &tx, + &mut tx_builder, + gas_payer.clone(), + #[cfg(not(feature = "mainnet"))] + false, + ) + .await; + + Ok(tx_builder) +} + +/// Build a pgf funding proposal governance +pub async fn build_pgf_stewards_proposal< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + args::InitProposal { + tx, + proposal_data: _, + native_token: _, + is_offline: _, + is_pgf_stewards: _, + is_pgf_funding: _, + tx_code_path, + }: args::InitProposal, + proposal: PgfStewardProposal, + gas_payer: &common::PublicKey, +) -> Result { + let mut init_proposal_data = + InitProposalData::try_from(proposal.clone()) + .map_err(|e| Error::InvalidProposal(e.to_string()))?; + + let tx_code_hash = + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) + .await + .unwrap(); + + let chain_id = tx.chain_id.clone().unwrap(); + + let mut tx_builder = Tx::new(chain_id, tx.expiration); + + let (_, extra_section_hash) = tx_builder + .add_extra_section(proposal.proposal.content.try_to_vec().unwrap()); + init_proposal_data.content = extra_section_hash; + + tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(init_proposal_data); + + prepare_tx::( + client, + &tx, + &mut tx_builder, + gas_payer.clone(), + #[cfg(not(feature = "mainnet"))] + false, + ) + .await; + + Ok(tx_builder) +} + +/// Submit an IBC transfer +pub async fn build_ibc_transfer( + client: &C, + args::TxIbcTransfer { + tx: tx_args, + source, + receiver, + token, + amount, + port_id, + channel_id, + timeout_height, + timeout_sec_offset, + memo, + tx_code_path, + }: args::TxIbcTransfer, + gas_payer: &common::PublicKey, +) -> Result { // Check that the source address exists on chain let source = - source_exists_or_err(args.source.clone(), args.tx.force, client) - .await?; + source_exists_or_err(source.clone(), tx_args.force, client).await?; // We cannot check the receiver // Check source balance - let balance_key = token::balance_key(&args.token, &source); + let balance_key = token::balance_key(&token, &source); check_balance_too_low_err( - &args.token, + &token, &source, - args.amount, + amount, balance_key, - args.tx.force, + tx_args.force, client, ) .await?; let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let ibc_denom = match &args.token { + let ibc_denom = match &token { Address::Internal(InternalAddress::IbcToken(hash)) => { let ibc_denom_key = ibc_denom_key(hash); rpc::query_storage_value::(client, &ibc_denom_key) .await - .ok_or_else(|| Error::TokenDoesNotExist(args.token.clone()))? + .ok_or_else(|| Error::TokenDoesNotExist(token.clone()))? } - _ => args.token.to_string(), + _ => token.to_string(), }; - let amount = args - .amount + let amount = amount .to_string_native() .split('.') .next() @@ -1239,12 +1397,12 @@ pub async fn build_ibc_transfer< let packet_data = PacketData { token, sender: source.to_string().into(), - receiver: args.receiver.into(), - memo: args.memo.unwrap_or_default().into(), + receiver: receiver.into(), + memo: memo.unwrap_or_default().into(), }; // this height should be that of the destination chain, not this chain - let timeout_height = match args.timeout_height { + let timeout_height = match timeout_height { Some(h) => { TimeoutHeight::At(IbcHeight::new(0, h).expect("invalid height")) } @@ -1253,7 +1411,7 @@ pub async fn build_ibc_transfer< let now: crate::tendermint::Time = DateTimeUtc::now().try_into().unwrap(); let now: IbcTimestamp = now.into(); - let timeout_timestamp = if let Some(offset) = args.timeout_sec_offset { + let timeout_timestamp = if let Some(offset) = timeout_sec_offset { (now + Duration::new(offset, 0)).unwrap() } else if timeout_height == TimeoutHeight::Never { // we cannot set 0 to both the height and the timestamp @@ -1263,34 +1421,33 @@ pub async fn build_ibc_transfer< }; let msg = MsgTransfer { - port_id_on_a: args.port_id, - chan_id_on_a: args.channel_id, + port_id_on_a: port_id, + chan_id_on_a: channel_id, packet_data, timeout_height_on_b: timeout_height, timeout_timestamp_on_b: timeout_timestamp, }; - tracing::debug!("IBC transfer message {:?}", msg); + let any_msg = msg.to_any(); let mut data = vec![]; prost::Message::encode(&any_msg, &mut data) .map_err(Error::EncodeFailure)?; - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + tx.add_code_from_hash(tx_code_hash) + .add_serialized_data(data); - prepare_tx::( + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(args.source), + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + Ok(tx) } /// Try to decode the given asset type and add its decoding to the supplied set. @@ -1369,15 +1526,13 @@ async fn used_asset_types< /// Submit an ordinary transfer pub async fn build_transfer< C: crate::ledger::queries::Client + Sync, - V: WalletUtils, U: ShieldedUtils, >( client: &C, - wallet: &mut Wallet, shielded: &mut ShieldedContext, mut args: args::TxTransfer, -) -> Result<(Tx, Option
, common::PublicKey, Option, bool), Error> -{ + gas_payer: &common::PublicKey, +) -> Result<(Tx, Option), Error> { let source = args.source.effective_address(); let target = args.target.effective_address(); let token = args.token.clone(); @@ -1396,15 +1551,15 @@ pub async fn build_transfer< .expect("expected to validate amount"); let validate_fee = validate_amount( client, - args.tx.fee_amount, - &args.tx.fee_token, + args.tx.gas_amount, + &args.tx.gas_token, 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); + args.tx.gas_amount = InputAmount::Validated(validate_fee); check_balance_too_low_err::( &token, &source, @@ -1420,21 +1575,14 @@ pub async fn build_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 (_amount, token) = if source == masp_addr && target == masp_addr { - // TODO Refactor me, we shouldn't rely on any specific token here. - (token::Amount::default(), args.native_token.clone()) - } else { - (validated_amount.amount, token) - }; - let default_signer = - TxSigningKey::WalletAddress(args.source.effective_address()); - // If our chosen signer is the MASP sentinel key, then our shielded inputs - // will need to cover the gas fees. - let chosen_signer = - tx_signer::(client, wallet, &args.tx, default_signer.clone()) - .await? - .1; - let shielded_gas = masp_tx_key().ref_to() == chosen_signer; + let (_amount, token, shielded_gas) = + if source == masp_addr && target == masp_addr { + // TODO Refactor me, we shouldn't rely on any specific token here. + (token::Amount::default(), args.native_token.clone(), true) + } else { + (validated_amount.amount, token, false) + }; + // Determine whether to pin this transaction to a storage key let key = match &args.target { TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), @@ -1443,6 +1591,8 @@ pub async fn build_transfer< #[cfg(not(feature = "mainnet"))] let is_source_faucet = rpc::is_faucet_account(client, &source).await; + #[cfg(feature = "mainnet")] + let is_source_faucet = false; let tx_code_hash = query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) @@ -1462,40 +1612,44 @@ pub async fn build_transfer< validated_amount.amount.to_string_native(), Box::new(token.clone()), validate_fee.amount.to_string_native(), - Box::new(args.tx.fee_token.clone()), + Box::new(args.tx.gas_token.clone()), )) } Err(err) => Err(Error::MaspError(err)), }?; - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; + let chain_id = args.tx.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, 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 + let (masp_hash, shielded_tx_epoch) = if let Some(ShieldedTransfer { + builder, + masp_tx, + metadata, + epoch, + }) = 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(); + // Add a MASP Transaction section to the Tx and get the tx hash + let masp_tx_hash = tx.add_masp_tx_section(masp_tx).1; + // 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) + let asset_types = used_asset_types(shielded, client, &builder) .await .unwrap_or_default(); - // Add the MASP Transaction's Builder to the Tx - tx.add_section(Section::MaspBuilder(MaspBuilder { + + tx.add_masp_builder(MaspBuilder { asset_types, // Store how the Info objects map to Descriptors/Outputs - metadata: shielded_parts.2, + metadata, // Store the data that was used to construct the Transaction - builder: shielded_parts.0, + builder, // Link the Builder to the Transaction by hash code - target: masp_hash, - })); + target: masp_tx_hash, + }); + // The MASP Transaction section hash will be used in Transfer - (Some(masp_hash), Some(shielded_parts.3)) + (Some(masp_tx_hash), Some(epoch)) } else { (None, None) }; @@ -1509,192 +1663,182 @@ pub async fn build_transfer< // Link the Transfer to the MASP Transaction by hash code shielded: masp_hash, }; + tracing::debug!("Transfer data {:?}", transfer); - // Encode the Transfer and store it beside the MASP transaction - let data = transfer - .try_to_vec() - .expect("Encoding tx data shouldn't fail"); - tx.set_data(Data::new(data)); - // Finally store the Traansfer WASM code in the Tx - tx.set_code(Code::from_hash(tx_code_hash)); + + tx.add_code_from_hash(tx_code_hash).add_data(transfer); // Dry-run/broadcast/submit the transaction - let (tx, signer_addr, def_key) = prepare_tx::( + prepare_tx::( client, - wallet, &args.tx, - tx, - default_signer.clone(), + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] is_source_faucet, ) - .await?; - Ok(( - tx, - signer_addr, - def_key, - shielded_tx_epoch, - is_source_faucet, - )) + .await; + + Ok((tx, shielded_tx_epoch)) } /// Submit a transaction to initialize an account -pub async fn build_init_account< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_init_account( client: &C, - wallet: &mut Wallet, - args: args::TxInitAccount, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let public_key = args.public_key; - + args::TxInitAccount { + tx: tx_args, + vp_code_path, + tx_code_path, + public_keys, + threshold, + }: args::TxInitAccount, + gas_payer: &common::PublicKey, +) -> Result { let vp_code_hash = - query_wasm_code_hash(client, args.vp_code_path.to_str().unwrap()) + query_wasm_code_hash(client, vp_code_path.to_str().unwrap()) .await .unwrap(); let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - let extra = - tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); + let threshold = match threshold { + Some(threshold) => threshold, + None => { + if public_keys.len() == 1 { + 1u8 + } else { + return Err(Error::MissingAccountThreshold); + } + } + }; + + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + let extra_section_hash = tx.add_extra_section_from_hash(vp_code_hash); let data = InitAccount { - public_key, - vp_code_hash: extra.get_hash(), + public_keys, + vp_code_hash: extra_section_hash, + threshold, }; - let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); + tx.add_code_from_hash(tx_code_hash).add_data(data); - prepare_tx::( + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(args.source), + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + Ok(tx) } /// Submit a transaction to update a VP -pub async fn build_update_vp< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_update_account( client: &C, - wallet: &mut Wallet, - args: args::TxUpdateVp, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let addr = args.addr.clone(); - - // Check that the address is established and exists on chain - match &addr { - Address::Established(_) => { - let exists = rpc::known_address::(client, &addr).await; - if !exists { - if args.tx.force { - eprintln!("The address {} doesn't exist on chain.", addr); - Ok(()) - } else { - Err(Error::LocationDoesNotExist(addr.clone())) - } - } else { - Ok(()) - } - } - Address::Implicit(_) => { - if args.tx.force { - eprintln!( - "A validity predicate of an implicit address cannot be \ - directly updated. You can use an established address for \ - this purpose." - ); - Ok(()) - } else { - Err(Error::ImplicitUpdate) - } - } - Address::Internal(_) => { - if args.tx.force { - eprintln!( - "A validity predicate of an internal address cannot be \ - directly updated." - ); - Ok(()) - } else { - Err(Error::ImplicitInternalError) - } - } - }?; + args::TxUpdateAccount { + tx: tx_args, + vp_code_path, + tx_code_path, + addr, + public_keys, + threshold, + }: args::TxUpdateAccount, + gas_payer: &common::PublicKey, +) -> Result { + let addr = if let Some(account) = rpc::get_account_info(client, &addr).await + { + account.address + } else if tx_args.force { + addr + } else { + return Err(Error::LocationDoesNotExist(addr)); + }; - let vp_code_hash = - query_wasm_code_hash(client, args.vp_code_path.to_str().unwrap()) - .await - .unwrap(); + let vp_code_hash = match vp_code_path { + Some(code_path) => { + let vp_hash = + query_wasm_code_hash(client, code_path.to_str().unwrap()) + .await + .unwrap(); + Some(vp_hash) + } + None => None, + }; let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - let extra = - tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); - let data = UpdateVp { + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + let extra_section_hash = vp_code_hash + .map(|vp_code_hash| tx.add_extra_section_from_hash(vp_code_hash)); + + let data = UpdateAccount { addr, - vp_code_hash: extra.get_hash(), + vp_code_hash: extra_section_hash, + public_keys, + threshold, }; - let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); - prepare_tx::( + tx.add_code_from_hash(tx_code_hash).add_data(data); + + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(args.addr), + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + Ok(tx) } /// Submit a custom transaction -pub async fn build_custom< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_custom( client: &C, - wallet: &mut Wallet, - args: args::TxCustom, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - args.data_path.map(|data| tx.set_data(Data::new(data))); - tx.set_code(Code::new(args.code_path)); - - prepare_tx::( + args::TxCustom { + tx: tx_args, + code_path, + data_path, + serialized_tx, + owner: _, + }: args::TxCustom, + gas_payer: &common::PublicKey, +) -> Result { + let mut tx = if let Some(serialized_tx) = serialized_tx { + Tx::deserialize(serialized_tx.as_ref()).map_err(|_| { + Error::Other("Invalid tx deserialization.".to_string()) + })? + } else { + let tx_code_hash = + query_wasm_code_hash(client, code_path.unwrap().to_str().unwrap()) + .await + .unwrap(); + let chain_id = tx_args.chain_id.clone().unwrap(); + let mut tx = Tx::new(chain_id, tx_args.expiration); + tx.add_code_from_hash(tx_code_hash); + data_path.map(|data| tx.add_serialized_data(data)); + tx + }; + + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::None, + &tx_args, + &mut tx, + gas_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await + .await; + Ok(tx) } async fn expect_dry_broadcast( diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4f1a795d40..25ffab2054 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -5,7 +5,7 @@ pub mod ibc; pub mod key; pub use namada_core::types::{ - address, chain, dec, eth_abi, eth_bridge_pool, ethereum_events, governance, - hash, internal, keccak, masp, storage, time, token, transaction, uint, + address, chain, dec, eth_abi, eth_bridge_pool, ethereum_events, hash, + internal, keccak, masp, storage, time, token, transaction, uint, validity_predicate, vote_extensions, voting_power, }; diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 9ac4852eed..95cf2afc38 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -551,7 +551,7 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = wasm::compilation_cache::common::testing::cache(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.set_code(Code::new(tx_code.clone())); outer_tx.set_data(Data::new(tx_data)); let result = tx( @@ -568,7 +568,7 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); let error = tx( @@ -617,18 +617,18 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let input = 2_usize.pow(23).try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input)); + + let mut tx = Tx::new(storage.chain_id.clone(), None); + tx.add_code(vec![]).add_serialized_data(input); + let eval_vp = EvalVp { vp_code_hash: limit_code_hash, input: tx, }; - let tx_data = eval_vp.try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); - outer_tx.header.chain_id = storage.chain_id.clone(); - outer_tx.set_code(Code::new(vec![])); - outer_tx.set_data(Data::new(tx_data)); + + let mut outer_tx = Tx::new(storage.chain_id.clone(), None); + outer_tx.add_code(vec![]).add_data(eval_vp); + let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); // When the `eval`ed VP doesn't run out of memory, it should return // `true` @@ -652,18 +652,17 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let input = 2_usize.pow(24).try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input)); + let mut tx = Tx::new(storage.chain_id.clone(), None); + tx.add_code(vec![]).add_data(input); + let eval_vp = EvalVp { vp_code_hash: limit_code_hash, input: tx, }; - let tx_data = eval_vp.try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); - outer_tx.header.chain_id = storage.chain_id.clone(); - outer_tx.set_data(Data::new(tx_data)); - outer_tx.set_code(Code::new(vec![])); + + let mut outer_tx = Tx::new(storage.chain_id.clone(), None); + outer_tx.add_code(vec![]).add_data(eval_vp); + // When the `eval`ed VP runs out of memory, its result should be // `false`, hence we should also get back `false` from the VP that // called `eval`. @@ -711,7 +710,7 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let tx_data = 2_usize.pow(23).try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = storage.chain_id.clone(); outer_tx.set_data(Data::new(tx_data)); outer_tx.set_code(Code::new(vec![])); @@ -735,7 +734,7 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = storage.chain_id.clone(); outer_tx.set_data(Data::new(tx_data)); let error = vp( @@ -783,7 +782,7 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = wasm::compilation_cache::common::testing::cache(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.set_code(Code::new(tx_no_op)); outer_tx.set_data(Data::new(tx_data)); let result = tx( @@ -839,7 +838,7 @@ mod tests { // limit and should fail let len = 2_usize.pow(24); let tx_data: Vec = vec![6_u8; len]; - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = storage.chain_id.clone(); outer_tx.set_data(Data::new(tx_data)); outer_tx.set_code(Code::new(vec![])); @@ -911,7 +910,7 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = wasm::compilation_cache::common::testing::cache(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.set_code(Code::new(tx_read_key)); outer_tx.set_data(Data::new(tx_data)); let error = tx( @@ -959,7 +958,7 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let tx_data = key.try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.header.chain_id = storage.chain_id.clone(); outer_tx.set_data(Data::new(tx_data)); outer_tx.set_code(Code::new(vec![])); @@ -1022,18 +1021,18 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let input = 2_usize.pow(23).try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(input)); - tx.set_code(Code::new(vec![])); + + let mut tx = Tx::new(storage.chain_id.clone(), None); + tx.add_code(vec![]).add_serialized_data(input); + let eval_vp = EvalVp { vp_code_hash: read_code_hash, input: tx, }; - let tx_data = eval_vp.try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); - outer_tx.header.chain_id = storage.chain_id.clone(); - outer_tx.set_data(Data::new(tx_data)); - outer_tx.set_code(Code::new(vec![])); + + let mut outer_tx = Tx::new(storage.chain_id.clone(), None); + outer_tx.add_code(vec![]).add_data(eval_vp); + let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let passed = vp( &code_hash, @@ -1103,7 +1102,7 @@ mod tests { let key = Key::wasm_code(&code_hash); write_log.write(&key, tx_code).unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); + let mut outer_tx = Tx::from_type(TxType::Raw); outer_tx.set_code(Code::from_hash(code_hash)); outer_tx.set_data(Data::new(tx_data)); @@ -1147,7 +1146,7 @@ mod tests { ) .expect("unexpected error converting wat2wasm").into_owned(); - let outer_tx = Tx::new(TxType::Raw); + let outer_tx = Tx::from_type(TxType::Raw); let tx_index = TxIndex::default(); let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); diff --git a/test_fixtures/masp_proofs/0DAF8BDF2318129AC828A7149AC83E76506147445D4DC22D57CBC9869BCDDA80.bin b/test_fixtures/masp_proofs/0DAF8BDF2318129AC828A7149AC83E76506147445D4DC22D57CBC9869BCDDA80.bin new file mode 100644 index 0000000000..f20cf90f1a Binary files /dev/null and b/test_fixtures/masp_proofs/0DAF8BDF2318129AC828A7149AC83E76506147445D4DC22D57CBC9869BCDDA80.bin differ diff --git a/test_fixtures/masp_proofs/12C933751C24BDC39C9108F5AF5D4C1BF345378A4FB6BB0B179BA8BDB0D2A3C0.bin b/test_fixtures/masp_proofs/12C933751C24BDC39C9108F5AF5D4C1BF345378A4FB6BB0B179BA8BDB0D2A3C0.bin new file mode 100644 index 0000000000..e24feb560a Binary files /dev/null and b/test_fixtures/masp_proofs/12C933751C24BDC39C9108F5AF5D4C1BF345378A4FB6BB0B179BA8BDB0D2A3C0.bin differ diff --git a/test_fixtures/masp_proofs/1362F1CF9B836CF8B05D8189EA9CB1712CCA85B0E96A3330A63BE7CD9E5ECD22.bin b/test_fixtures/masp_proofs/1362F1CF9B836CF8B05D8189EA9CB1712CCA85B0E96A3330A63BE7CD9E5ECD22.bin new file mode 100644 index 0000000000..db2cb751aa Binary files /dev/null and b/test_fixtures/masp_proofs/1362F1CF9B836CF8B05D8189EA9CB1712CCA85B0E96A3330A63BE7CD9E5ECD22.bin differ diff --git a/test_fixtures/masp_proofs/5B99F3D7E0CE75AB1F4B737EC88B269A5436CD72AA758686960F409B04841707.bin b/test_fixtures/masp_proofs/5B99F3D7E0CE75AB1F4B737EC88B269A5436CD72AA758686960F409B04841707.bin new file mode 100644 index 0000000000..30b0e399a3 Binary files /dev/null and b/test_fixtures/masp_proofs/5B99F3D7E0CE75AB1F4B737EC88B269A5436CD72AA758686960F409B04841707.bin differ diff --git a/test_fixtures/masp_proofs/889C046FA76727BC97433503BB79BAC90BA1F01653EBCFDCF7CC8AAA1BBEE462.bin b/test_fixtures/masp_proofs/889C046FA76727BC97433503BB79BAC90BA1F01653EBCFDCF7CC8AAA1BBEE462.bin new file mode 100644 index 0000000000..525e1e63ee Binary files /dev/null and b/test_fixtures/masp_proofs/889C046FA76727BC97433503BB79BAC90BA1F01653EBCFDCF7CC8AAA1BBEE462.bin differ diff --git a/test_fixtures/masp_proofs/8B29BC2E1A96DF331C7C3A2B227C98D1E5AAAA9988F26B1A47090ACCE693572F.bin b/test_fixtures/masp_proofs/8B29BC2E1A96DF331C7C3A2B227C98D1E5AAAA9988F26B1A47090ACCE693572F.bin new file mode 100644 index 0000000000..77f5568490 Binary files /dev/null and b/test_fixtures/masp_proofs/8B29BC2E1A96DF331C7C3A2B227C98D1E5AAAA9988F26B1A47090ACCE693572F.bin differ diff --git a/test_fixtures/masp_proofs/A9FA2730222946FA51E9D587544FDED28D5E7D3C6B52DCF38A5978CEA70D6FD3.bin b/test_fixtures/masp_proofs/A9FA2730222946FA51E9D587544FDED28D5E7D3C6B52DCF38A5978CEA70D6FD3.bin new file mode 100644 index 0000000000..634d326dcd Binary files /dev/null and b/test_fixtures/masp_proofs/A9FA2730222946FA51E9D587544FDED28D5E7D3C6B52DCF38A5978CEA70D6FD3.bin differ diff --git a/test_fixtures/masp_proofs/AC308C08512AF5DAA364B845D146763B3CE0BACFB7799C6744E50B9E7F43E961.bin b/test_fixtures/masp_proofs/AC308C08512AF5DAA364B845D146763B3CE0BACFB7799C6744E50B9E7F43E961.bin new file mode 100644 index 0000000000..b67b3c8cd0 Binary files /dev/null and b/test_fixtures/masp_proofs/AC308C08512AF5DAA364B845D146763B3CE0BACFB7799C6744E50B9E7F43E961.bin differ diff --git a/test_fixtures/masp_proofs/BE57BA4D8FB068F5A933E78DEF2989556FD771D368849D034E22923FD350EEEC.bin b/test_fixtures/masp_proofs/BE57BA4D8FB068F5A933E78DEF2989556FD771D368849D034E22923FD350EEEC.bin new file mode 100644 index 0000000000..c05883cbab Binary files /dev/null and b/test_fixtures/masp_proofs/BE57BA4D8FB068F5A933E78DEF2989556FD771D368849D034E22923FD350EEEC.bin differ diff --git a/test_fixtures/masp_proofs/E76E54B7526CD2B5423322FB711C0CA6AA6520A2AC8BC34A84358EA137F138D0.bin b/test_fixtures/masp_proofs/E76E54B7526CD2B5423322FB711C0CA6AA6520A2AC8BC34A84358EA137F138D0.bin new file mode 100644 index 0000000000..4df2e0be9f Binary files /dev/null and b/test_fixtures/masp_proofs/E76E54B7526CD2B5423322FB711C0CA6AA6520A2AC8BC34A84358EA137F138D0.bin differ diff --git a/test_fixtures/masp_proofs/EE7C912B7E21F07494D58AA6668DC6BBB31619C7E93A1A5A2E64B694DBE1BD6E.bin b/test_fixtures/masp_proofs/EE7C912B7E21F07494D58AA6668DC6BBB31619C7E93A1A5A2E64B694DBE1BD6E.bin new file mode 100644 index 0000000000..56b269667b Binary files /dev/null and b/test_fixtures/masp_proofs/EE7C912B7E21F07494D58AA6668DC6BBB31619C7E93A1A5A2E64B694DBE1BD6E.bin differ diff --git a/test_fixtures/masp_proofs/F068FDF05B8F25DD923E667215344FFFAA6CA273027CD480AEA68DDED57D88CA.bin b/test_fixtures/masp_proofs/F068FDF05B8F25DD923E667215344FFFAA6CA273027CD480AEA68DDED57D88CA.bin new file mode 100644 index 0000000000..5388cd80a3 Binary files /dev/null and b/test_fixtures/masp_proofs/F068FDF05B8F25DD923E667215344FFFAA6CA273027CD480AEA68DDED57D88CA.bin differ diff --git a/test_fixtures/masp_proofs/README.md b/test_fixtures/masp_proofs/README.md new file mode 100644 index 0000000000..1351183cf3 --- /dev/null +++ b/test_fixtures/masp_proofs/README.md @@ -0,0 +1,11 @@ +# MASP proofs for tests + +This directory contains pre-built MASP transaction proofs used to speed-up integration tests. + +```shell +# Run the tests with the saved proofs from here. +make test-integration + +# Delete old proofs, run the tests and save the new proofs. +make test-integration-save-proofs +``` diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 6b0352bcc7..a12fcbff17 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -18,7 +18,6 @@ pub const WASM_FOR_TESTS_DIR: &str = "wasm_for_tests"; #[derive(Debug, Clone, Copy, EnumIter)] pub enum TestWasms { TxMemoryLimit, - TxMintTokens, TxNoOp, TxProposalCode, TxReadStorageKey, @@ -36,7 +35,6 @@ impl TestWasms { pub fn path(&self) -> PathBuf { let filename = match self { TestWasms::TxMemoryLimit => "tx_memory_limit.wasm", - TestWasms::TxMintTokens => "tx_mint_tokens.wasm", TestWasms::TxNoOp => "tx_no_op.wasm", TestWasms::TxProposalCode => "tx_proposal_code.wasm", TestWasms::TxReadStorageKey => "tx_read_storage_key.wasm", diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 6d05a52f4a..674bce9538 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -45,6 +45,7 @@ num-traits.workspace = true prost.workspace = true regex.workspace = true serde_json.workspace = true +serde.workspace = true sha2.workspace = true tempfile.workspace = true tendermint-light-client.workspace = true diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 1d15c5323a..d85b344f45 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -144,17 +144,17 @@ async fn test_roundtrip_eth_transfer() -> Result<()> { "add-erc20-transfer", "--address", BERTHA, - "--signer", - BERTHA, + "--signing-keys", + BERTHA_KEY, "--amount", &amount, "--erc20", &dai_addr, "--ethereum-address", RECEIVER, - "--fee-amount", + "--gas-amount", "10", - "--fee-payer", + "--gas-payer", BERTHA, "--gas-amount", "0", @@ -335,17 +335,17 @@ async fn test_bridge_pool_e2e() { "add-erc20-transfer", "--address", BERTHA, - "--signer", - BERTHA, + "--signing-keys", + BERTHA_KEY, "--amount", "100", "--erc20", &wnam_address, "--ethereum-address", RECEIVER, - "--fee-amount", + "--gas-amount", "10", - "--fee-payer", + "--gas-payer", BERTHA, "--gas-amount", "0", diff --git a/tests/src/e2e/eth_bridge_tests/helpers.rs b/tests/src/e2e/eth_bridge_tests/helpers.rs index 09df129e2b..a52273366c 100644 --- a/tests/src/e2e/eth_bridge_tests/helpers.rs +++ b/tests/src/e2e/eth_bridge_tests/helpers.rs @@ -183,7 +183,7 @@ pub fn attempt_wrapped_erc20_transfer( from, "--target", to, - "--signer", + "--signing-keys", signer, "--amount", &amount, diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 1fde997ef6..e2d887b5a2 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -458,3 +458,9 @@ pub fn wait_for_wasm_pre_compile(ledger: &mut NamadaCmd) -> Result<()> { ledger.exp_string("Finished compiling all")?; Ok(()) } + +/// Convert epoch `min_duration` in seconds to `epochs_per_year` genesis +/// parameter. +pub fn epochs_per_year_from_min_duration(min_duration: u64) -> u64 { + 60 * 60 * 24 * 365 / min_duration +} diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 54b94fa429..6fbe6986d0 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -118,6 +118,16 @@ fn run_ledger_ibc() -> Result<()> { &["ledger", "run"], Some(40) )?; + ledger_b.exp_string("Namada ledger node started")?; + ledger_a.exp_string("This node is a validator")?; + ledger_b.exp_string("This node is a validator")?; + + wait_for_wasm_pre_compile(&mut ledger_a)?; + wait_for_wasm_pre_compile(&mut ledger_b)?; + + // Wait for a first block + ledger_a.exp_string("Committed block hash")?; + ledger_b.exp_string("Committed block hash")?; wait_for_wasm_pre_compile(&mut ledger_a)?; wait_for_wasm_pre_compile(&mut ledger_b)?; @@ -210,7 +220,7 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { consensus_state: make_consensus_state(test_b, height)?.into(), signer: signer(), }; - let height_a = submit_ibc_tx(test_a, message, ALBERT)?; + let height_a = submit_ibc_tx(test_a, message, ALBERT, ALBERT_KEY, false)?; let height = query_height(test_a)?; let client_state = make_client_state(test_a, height); @@ -220,7 +230,7 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { consensus_state: make_consensus_state(test_a, height)?.into(), signer: signer(), }; - let height_b = submit_ibc_tx(test_b, message, ALBERT)?; + let height_b = submit_ibc_tx(test_b, message, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_a, height_a)?; let client_id_a = get_client_id_from_events(&events) @@ -349,7 +359,7 @@ fn update_client( client_id: client_id.clone(), signer: signer(), }; - submit_ibc_tx(target_test, message, ALBERT)?; + submit_ibc_tx(target_test, message, ALBERT, ALBERT_KEY, false)?; check_ibc_update_query( target_test, @@ -389,7 +399,7 @@ fn connection_handshake( signer: signer(), }; // OpenInitConnection on Chain A - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_a, height)?; let conn_id_a = get_connection_id_from_events(&events) .ok_or(eyre!("No connection ID is set"))?; @@ -416,7 +426,7 @@ fn connection_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenTryConnection on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_b, height)?; let conn_id_b = get_connection_id_from_events(&events) .ok_or(eyre!("No connection ID is set"))?; @@ -442,7 +452,7 @@ fn connection_handshake( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // OpenAckConnection on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; // get the proofs on Chain A let height_a = query_height(test_a)?; @@ -456,7 +466,7 @@ fn connection_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenConfirmConnection on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok((conn_id_a, conn_id_b)) } @@ -494,7 +504,7 @@ fn channel_handshake( signer: signer(), version_proposal: channel_version.clone(), }; - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_a, height)?; let channel_id_a = get_channel_id_from_events(&events) .ok_or(eyre!("Transaction failed"))?; @@ -521,7 +531,7 @@ fn channel_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenTryChannel on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_b, height)?; let channel_id_b = get_channel_id_from_events(&events) .ok_or(eyre!("Transaction failed"))?; @@ -542,7 +552,7 @@ fn channel_handshake( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // OpenAckChannel on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; // get the proofs on Chain A let height_a = query_height(test_a)?; @@ -558,7 +568,7 @@ fn channel_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenConfirmChannel on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok(((port_id.clone(), channel_id_a), (port_id, channel_id_b))) } @@ -627,9 +637,11 @@ fn transfer_token( &receiver, NAM, &Amount::native_whole(100000), + ALBERT_KEY, port_id_a, channel_id_a, None, + false, )?; let events = get_events(test_a, height)?; let packet = @@ -648,7 +660,7 @@ fn transfer_token( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // Receive the token on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_b, height)?; let packet = get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; @@ -673,7 +685,7 @@ fn transfer_token( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Acknowledge on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -738,9 +750,11 @@ fn transfer_back( &receiver, ibc_token, &Amount::native_whole(50000), + BERTHA_KEY, port_id_b, channel_id_b, None, + false, )?; let events = get_events(test_b, height)?; let packet = @@ -757,7 +771,7 @@ fn transfer_back( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Receive the token on Chain A - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let events = get_events(test_a, height)?; let packet = get_packet_from_events(&events).ok_or(eyre!("Transaction failed"))?; @@ -777,7 +791,7 @@ fn transfer_back( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // Acknowledge on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -798,9 +812,11 @@ fn transfer_timeout( &receiver, NAM, &Amount::native_whole(100000), + ALBERT_KEY, port_id_a, channel_id_a, Some(Duration::new(5, 0)), + false, )?; let events = get_events(test_a, height)?; let packet = @@ -822,7 +838,7 @@ fn transfer_timeout( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Timeout on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -880,7 +896,9 @@ fn commitment_prefix() -> CommitmentPrefix { fn submit_ibc_tx( test: &Test, message: impl Msg + std::fmt::Debug, + owner: &str, signer: &str, + wait_reveal_pk: bool, ) -> Result { let data_path = test.test_dir.path().join("tx.data"); let data = make_ibc_data(message); @@ -897,7 +915,9 @@ fn submit_ibc_tx( TX_IBC_WASM, "--data-path", &data_path, - "--signer", + "--owner", + owner, + "--signing-keys", signer, "--gas-amount", "0", @@ -911,6 +931,9 @@ fn submit_ibc_tx( Some(40) )?; client.exp_string("Transaction applied")?; + if wait_reveal_pk { + client.exp_string("Transaction applied")?; + } check_tx_height(test, &mut client) } @@ -921,9 +944,11 @@ fn transfer( receiver: &Address, token: impl AsRef, amount: &Amount, + signer: impl AsRef, port_id: &PortId, channel_id: &ChannelId, timeout_sec: Option, + wait_reveal_pk: bool, ) -> Result { let rpc = get_actor_rpc(test, &Who::Validator(0)); @@ -937,8 +962,8 @@ fn transfer( sender.as_ref(), "--receiver", &receiver, - "--signer", - sender.as_ref(), + "--signing-keys", + signer.as_ref(), "--token", token.as_ref(), "--amount", @@ -959,6 +984,9 @@ fn transfer( let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction applied")?; + if wait_reveal_pk { + client.exp_string("Transaction applied")?; + } check_tx_height(test, &mut client) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 8dc8face08..2ea7d901f0 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -20,7 +20,6 @@ use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; use namada::types::address::Address; -use namada::types::governance::ProposalType; use namada::types::storage::Epoch; use namada::types::token; use namada_apps::client::tx::CLIShieldedUtils; @@ -30,18 +29,21 @@ 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::ledger::governance::cli::onchain::{PgfAction, PgfSteward}; use namada_test_utils::TestWasms; +use namada_vp_prelude::testnet_pow; use serde_json::json; use setup::constants::*; use setup::Test; use super::helpers::{ - get_height, wait_for_block_height, wait_for_wasm_pre_compile, + epochs_per_year_from_min_duration, get_height, wait_for_block_height, + wait_for_wasm_pre_compile, }; use super::setup::{get_all_wasms_hashes, set_ethereum_bridge_mode, NamadaCmd}; use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, - parse_reached_epoch, + is_debug_mode, parse_reached_epoch, }; use crate::e2e::setup::{self, default_port_offset, sleep, Bin, Who}; use crate::{run, run_as}; @@ -171,6 +173,8 @@ fn test_node_connectivity_and_consensus() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -407,7 +411,22 @@ fn stop_ledger_at_height() -> Result<()> { /// 8. Query the raw bytes of a storage key #[test] fn ledger_txs_and_queries() -> Result<()> { - let test = setup::network(|genesis| genesis, None)?; + let test = setup::network( + |genesis| { + #[cfg(not(feature = "mainnet"))] + { + GenesisConfig { + faucet_pow_difficulty: testnet_pow::Difficulty::try_new(1), + ..genesis + } + } + #[cfg(feature = "mainnet")] + { + genesis + } + }, + None, + )?; set_ethereum_bridge_mode( &test, @@ -442,6 +461,9 @@ fn ledger_txs_and_queries() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let multisig_account = + format!("{},{},{}", BERTHA_KEY, ALBERT_KEY, CHRISTEL_KEY); + let txs_args = vec![ // 2. Submit a token transfer tx (from an established account) vec![ @@ -460,6 +482,8 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], @@ -480,6 +504,8 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + DAEWON, "--node", &validator_one_rpc, ], @@ -506,46 +532,50 @@ fn ledger_txs_and_queries() -> Result<()> { // 3. Submit a transaction to update an account's validity // predicate vec![ - "update", - "--address", - BERTHA, - "--code-path", - VP_USER_WASM, - "--gas-amount", - "0", - "--gas-limit", - "0", - "--gas-token", - NAM, + "update-account", + "--address", + BERTHA, + "--code-path", + VP_USER_WASM, + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], // 4. Submit a custom tx vec![ "tx", - "--signer", - BERTHA, "--code-path", TX_TRANSFER_WASM, "--data-path", &tx_data_path, + "--owner", + BERTHA, "--gas-amount", "0", "--gas-limit", "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc ], // 5. Submit a tx to initialize a new account vec![ "init-account", - "--source", - BERTHA, - "--public-key", + "--public-keys", // Value obtained from `namada::types::key::ed25519::tests::gen_keypair` "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", + "--threshold", + "1", "--code-path", VP_USER_WASM, "--alias", @@ -556,11 +586,35 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], - // 6. Submit a tx to withdraw from faucet account (requires PoW challenge - // solution) + // 5. Submit a tx to initialize a new multisig account + vec![ + "init-account", + "--public-keys", + &multisig_account, + "--threshold", + "2", + "--code-path", + VP_USER_WASM, + "--alias", + "Test-Account-2", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ], + // 6. Submit a tx to withdraw from faucet account (requires PoW challenge + // solution) vec![ "transfer", "--source", @@ -572,8 +626,8 @@ fn ledger_txs_and_queries() -> Result<()> { "--amount", "10.1", // Faucet withdrawal requires an explicit signer - "--signer", - ALBERT, + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, ], @@ -613,6 +667,8 @@ fn ledger_txs_and_queries() -> Result<()> { ], // expect a decimal vec![r"nam: \d+(\.\d+)?"], + // check also as validator node + true, ), // Unspecified token expect all tokens from wallet derived from genesis ( @@ -626,9 +682,25 @@ fn ledger_txs_and_queries() -> Result<()> { r"kartoffel: \d+(\.\d+)?", r"schnitzel: \d+(\.\d+)?", ], + // check also as validator node + true, + ), + ( + vec![ + "query-account", + "--owner", + "Test-Account-2", + "--node", + &validator_one_rpc, + ], + vec!["Threshold: 2"], + // check also as validator node + false, ), ]; - for (query_args, expected) in &query_args_and_expected_response { + for (query_args, expected, check_as_validator) in + &query_args_and_expected_response + { // Run as a non-validator let mut client = run!(test, Bin::Client, query_args, Some(40))?; for pattern in expected { @@ -636,6 +708,10 @@ fn ledger_txs_and_queries() -> Result<()> { } client.assert_success(); + if !check_as_validator { + continue; + } + // Run as a validator let mut client = run_as!( test, @@ -853,9 +929,7 @@ fn invalid_transactions() -> Result<()> { let tx_args = vec![ "transfer", "--source", - DAEWON, - "--signing-key", - ALBERT_KEY, + BERTHA, "--target", ALBERT, "--token", @@ -868,8 +942,11 @@ fn invalid_transactions() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, + "--force", ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -902,13 +979,16 @@ fn invalid_transactions() -> Result<()> { ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); + // we need to wait for the rpc endpoint to start + sleep(10); + // 5. Submit an invalid transactions (invalid token address) let daewon_lower = DAEWON.to_lowercase(); let tx_args = vec![ "transfer", "--source", DAEWON, - "--signing-key", + "--signing-keys", &daewon_lower, "--target", ALBERT, @@ -1005,6 +1085,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -1029,6 +1111,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -1050,6 +1134,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -1074,6 +1160,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -1093,7 +1181,7 @@ fn pos_bonds() -> Result<()> { epoch, delegation_withdrawable_epoch ); let start = Instant::now(); - let loop_timeout = Duration::new(60, 0); + let loop_timeout = Duration::new(120, 0); loop { if Instant::now().duration_since(start) > loop_timeout { panic!( @@ -1118,6 +1206,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -1140,6 +1230,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -1216,6 +1308,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_zero_rpc, ]; @@ -1252,6 +1346,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-1-account-key", "--ledger-address", &validator_one_rpc, ]; @@ -1274,6 +1370,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-2-account-key", "--ledger-address", &validator_two_rpc, ]; @@ -1362,6 +1460,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -1398,6 +1498,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -1421,6 +1523,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -1514,14 +1618,13 @@ fn pos_init_validator() -> Result<()> { // 2. Initialize a new validator account with the non-validator node let new_validator = "new-validator"; - let new_validator_key = format!("{}-key", new_validator); + let _new_validator_key = format!("{}-key", new_validator); let tx_args = vec![ "init-validator", "--alias", new_validator, - "--source", - BERTHA, - "--unsafe-dont-encrypt", + "--account-keys", + "bertha-key", "--gas-amount", "0", "--gas-limit", @@ -1532,8 +1635,11 @@ fn pos_init_validator() -> Result<()> { "0.05", "--max-commission-rate-change", "0.01", + "--signing-keys", + "bertha-key", "--node", &non_validator_rpc, + "--unsafe-dont-encrypt", ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; @@ -1546,7 +1652,7 @@ fn pos_init_validator() -> Result<()> { "--source", BERTHA, "--target", - &new_validator_key, + new_validator, "--token", NAM, "--amount", @@ -1557,6 +1663,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -1580,6 +1688,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -1605,6 +1715,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -1637,6 +1749,13 @@ fn pos_init_validator() -> Result<()> { non_validator.interrupt()?; non_validator.exp_eof()?; + // it takes a bit before the node is shutdown. We dont want flasky test. + if is_debug_mode() { + sleep(10); + } else { + sleep(5); + } + let loc = format!("{}:{}", std::file!(), std::line!()); let validator_1_base_dir = test.get_base_dir(&Who::NonValidator); let mut validator_1 = setup::run_cmd( @@ -1728,6 +1847,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", ]); @@ -1848,12 +1969,8 @@ fn proposal_submission() -> Result<()> { let valid_proposal_json_path = prepare_proposal_data( &test, albert, - ProposalType::Default(Some( - TestWasms::TxProposalCode - .path() - .to_string_lossy() - .to_string(), - )), + TestWasms::TxProposalCode.read_bytes(), + 12, ); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -1883,7 +2000,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("Proposal: 0")?; + client.exp_string("Proposal Id: 0")?; client.assert_success(); // 4. Query token balance proposal author (submitted funds) @@ -1919,66 +2036,28 @@ fn proposal_submission() -> Result<()> { // 6. Submit an invalid proposal // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 let albert = find_address(&test, ALBERT)?; - let invalid_proposal_json = json!( - { - "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": albert, - "voting_start_epoch": 9999_u64, - "voting_end_epoch": 10000_u64, - "grace_epoch": 10009_u64, - "type": { - "Default":null - } - } - ); - let invalid_proposal_json_path = - test.test_dir.path().join("invalid_proposal.json"); - generate_proposal_json_file( - invalid_proposal_json_path.as_path(), - &invalid_proposal_json, + let invalid_proposal_json = prepare_proposal_data( + &test, + albert, + TestWasms::TxProposalCode.read_bytes(), + 1, ); let submit_proposal_args = vec![ "init-proposal", "--data-path", - invalid_proposal_json_path.to_str().unwrap(), + invalid_proposal_json.to_str().unwrap(), "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string( - "Invalid proposal end epoch: difference between proposal start and \ - end epoch must be at least 3 and at max 27 and end epoch must be a \ - multiple of 3", + client.exp_regex( + "Proposal data are invalid: Invalid proposal start epoch: 1 must be \ + greater than current epoch .* and a multiple of 3", )?; client.assert_failure(); - // 7. Check invalid proposal was not accepted + // 7. Check invalid proposal was not submitted let proposal_query_args = vec![ "query-proposal", "--proposal-id", @@ -1988,7 +2067,7 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("No valid proposal was found with id 1")?; + client.exp_string("No proposal found with id: 1")?; client.assert_success(); // 8. Query token balance (funds shall not be submitted) @@ -2019,7 +2098,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "yay", - "--signer", + "--address", "validator-0", "--node", &validator_one_rpc, @@ -2041,7 +2120,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "nay", - "--signer", + "--address", BERTHA, "--node", &validator_one_rpc, @@ -2059,7 +2138,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "yay", - "--signer", + "--address", ALBERT, "--node", &validator_one_rpc, @@ -2087,7 +2166,10 @@ fn proposal_submission() -> Result<()> { ]; let mut client = run!(test, Bin::Client, query_proposal, Some(15))?; - client.exp_string("Result: passed")?; + client.exp_string("Proposal Id: 0")?; + client.exp_string( + "passed with 200900.000000 yay votes and 0.000000 nay votes (0.%)", + )?; client.assert_success(); // 12. Wait proposal grace and check proposal author funds @@ -2138,228 +2220,6 @@ fn proposal_submission() -> Result<()> { Ok(()) } -/// Test submission and vote of an ETH proposal. -/// -/// 1 - Submit proposal -/// 2 - Vote with delegator and check failure -/// 3 - Vote with validator and check success -/// 4 - Check that proposal passed and funds -#[test] -fn eth_governance_proposal() -> Result<()> { - let test = setup::network( - |genesis| { - let parameters = ParametersConfig { - epochs_per_year: epochs_per_year_from_min_duration(1), - max_proposal_bytes: Default::default(), - min_num_of_blocks: 1, - max_expected_time_per_block: 1, - ..genesis.parameters - }; - - GenesisConfig { - parameters, - ..genesis - } - }, - None, - )?; - - let namadac_help = vec!["--help"]; - - let mut client = run!(test, Bin::Client, namadac_help, Some(40))?; - client.exp_string("Namada client command line interface.")?; - client.assert_success(); - - // Run the ledger node - let _bg_ledger = - start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))? - .background(); - - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - - // Delegate some token - let tx_args = vec![ - "bond", - "--validator", - "validator-0", - "--source", - BERTHA, - "--amount", - "900", - "--gas-amount", - "0", - "--gas-limit", - "0", - "--gas-token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - client = run!(test, Bin::Client, tx_args, Some(40))?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); - - // 1 - Submit proposal - let albert = find_address(&test, ALBERT)?; - let valid_proposal_json_path = - prepare_proposal_data(&test, albert, ProposalType::ETHBridge); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - - let submit_proposal_args = vec![ - "init-proposal", - "--data-path", - valid_proposal_json_path.to_str().unwrap(), - "--ledger-address", - &validator_one_rpc, - ]; - client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); - - // Query the proposal - let proposal_query_args = vec![ - "query-proposal", - "--proposal-id", - "0", - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("Proposal: 0")?; - client.assert_success(); - - // Query token balance proposal author (submitted funds) - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 999500")?; - client.assert_success(); - - // Query token balance governance - let query_balance_args = vec![ - "balance", - "--owner", - GOVERNANCE_ADDRESS, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 500")?; - client.assert_success(); - - // 2 - Vote with delegator and check failure - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 13 { - sleep(1); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - use namada::types::key::{self, secp256k1, SigScheme}; - use rand::prelude::ThreadRng; - use rand::thread_rng; - - // Generate a signing key to sign the eth message to sign the eth message to - // sign the eth message - let mut rng: ThreadRng = thread_rng(); - let node_sk = secp256k1::SigScheme::generate(&mut rng); - let signing_key = key::common::SecretKey::Secp256k1(node_sk); - let msg = "fd34672ab5"; - let vote_arg = format!("{} {}", signing_key, msg); - let submit_proposal_vote_delagator = vec![ - "vote-proposal", - "--proposal-id", - "0", - "--vote", - "yay", - "--eth", - &vote_arg, - "--signer", - BERTHA, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, submit_proposal_vote_delagator, Some(40))?; - client.exp_string("Transaction is invalid.")?; - client.assert_success(); - - // 3 - Send a yay vote from a validator - let vote_arg = format!("{} {}", signing_key, msg); - - let submit_proposal_vote = vec![ - "vote-proposal", - "--proposal-id", - "0", - "--vote", - "yay", - "--eth", - &vote_arg, - "--signer", - "validator-0", - "--ledger-address", - &validator_one_rpc, - ]; - - client = run_as!( - test, - Who::Validator(0), - Bin::Client, - submit_proposal_vote, - Some(15) - )?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); - - // 4 - Wait proposals grace and check proposal author funds - while epoch.0 < 31 { - sleep(1); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - let query_balance_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 1000000")?; - client.assert_success(); - - // Check if governance funds are 0 - let query_balance_args = vec![ - "balance", - "--owner", - GOVERNANCE_ADDRESS, - "--token", - NAM, - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 0")?; - client.assert_success(); - - Ok(()) -} - /// Test submission and vote of a PGF proposal /// /// 1 - Sumbit two proposals @@ -2433,12 +2293,18 @@ fn pgf_governance_proposal() -> Result<()> { // 1 - Submit proposal let albert = find_address(&test, ALBERT)?; + let pgf_stewards = PgfSteward { + action: PgfAction::Add, + address: albert.clone(), + }; + let valid_proposal_json_path = - prepare_proposal_data(&test, albert.clone(), ProposalType::PGFCouncil); + prepare_proposal_data(&test, albert, vec![pgf_stewards], 12); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", + "--pgf-stewards", "--data-path", valid_proposal_json_path.to_str().unwrap(), "--ledger-address", @@ -2449,22 +2315,6 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // Sumbit another proposal - let valid_proposal_json_path = - prepare_proposal_data(&test, albert, ProposalType::PGFCouncil); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - - let submit_proposal_args = vec![ - "init-proposal", - "--data-path", - valid_proposal_json_path.to_str().unwrap(), - "--ledger-address", - &validator_one_rpc, - ]; - client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); - // 2 - Query the proposal let proposal_query_args = vec![ "query-proposal", @@ -2475,19 +2325,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("Proposal: 0")?; - client.assert_success(); - - let proposal_query_args = vec![ - "query-proposal", - "--proposal-id", - "1", - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, proposal_query_args, Some(40))?; - client.exp_string("Proposal: 1")?; + client.exp_string("Proposal Id: 0")?; client.assert_success(); // Query token balance proposal author (submitted funds) @@ -2502,7 +2340,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 999000")?; + client.exp_string("nam: 999500")?; client.assert_success(); // Query token balance governance @@ -2517,7 +2355,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, query_balance_args, Some(40))?; - client.exp_string("nam: 1000")?; + client.exp_string("nam: 500")?; client.assert_success(); // 3 - Send a yay vote from a validator @@ -2528,17 +2366,13 @@ fn pgf_governance_proposal() -> Result<()> { } let albert_address = find_address(&test, ALBERT)?; - let arg_vote = format!("{} 1000", albert_address); - let submit_proposal_vote = vec![ "vote-proposal", "--proposal-id", "0", "--vote", "yay", - "--pgf", - &arg_vote, - "--signer", + "--address", "validator-0", "--ledger-address", &validator_one_rpc, @@ -2556,16 +2390,13 @@ fn pgf_governance_proposal() -> Result<()> { client.assert_success(); // Send different yay vote from delegator to check majority on 1/3 - let different_vote = format!("{} 900", albert_address); let submit_proposal_vote_delagator = vec![ "vote-proposal", "--proposal-id", "0", "--vote", "yay", - "--pgf", - &different_vote, - "--signer", + "--address", BERTHA, "--ledger-address", &validator_one_rpc, @@ -2577,29 +2408,6 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // Send vote to the second proposal from delegator - let submit_proposal_vote_delagator = vec![ - "vote-proposal", - "--proposal-id", - "1", - "--vote", - "yay", - "--pgf", - &different_vote, - "--signer", - BERTHA, - "--ledger-address", - &validator_one_rpc, - ]; - - // this is valid because the client filter ALBERT delegation and there are - // none - let mut client = - run!(test, Bin::Client, submit_proposal_vote_delagator, Some(15))?; - client.exp_string("Transaction applied with result:")?; - client.exp_string("Transaction is valid.")?; - client.assert_success(); - // 4 - Query the proposal and check the result is the one voted by the // validator (majority) epoch = get_epoch(&test, &validator_one_rpc).unwrap(); @@ -2617,23 +2425,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, query_proposal, Some(15))?; - client.exp_string(&format!( - "Result: passed with PGF council address: {}, spending cap: 0.001", - albert_address - ))?; - client.assert_success(); - - // Query the second proposal and check the it didn't pass - let query_proposal = vec![ - "query-proposal-result", - "--proposal-id", - "1", - "--ledger-address", - &validator_one_rpc, - ]; - - client = run!(test, Bin::Client, query_proposal, Some(15))?; - client.exp_string("Result: rejected")?; + client.exp_string("passed")?; client.assert_success(); // 12. Wait proposals grace and check proposal author funds @@ -2653,7 +2445,7 @@ fn pgf_governance_proposal() -> Result<()> { ]; client = run!(test, Bin::Client, query_balance_args, Some(30))?; - client.exp_string("nam: 999500")?; + client.exp_string("nam: 1000000")?; client.assert_success(); // Check if governance funds are 0 @@ -2671,6 +2463,15 @@ fn pgf_governance_proposal() -> Result<()> { client.exp_string("nam: 0")?; client.assert_success(); + // 14. Query pgf stewards + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + + let mut client = run!(test, Bin::Client, query_pgf, Some(30))?; + client.exp_string("Pgf stewards:")?; + client.exp_string(&format!("- {}", albert_address))?; + client.exp_string("Pgf fundings: no fundings are currently set.")?; + client.assert_success(); + Ok(()) } @@ -2748,7 +2549,7 @@ fn proposal_offline() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 2. Create an offline + // 2. Create an offline proposal let albert = find_address(&test, ALBERT)?; let valid_proposal_json = json!( { @@ -2764,12 +2565,7 @@ fn proposal_offline() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 3_u64, - "voting_end_epoch": 9_u64, - "grace_epoch": 18_u64, - "type": { - "Default": null - } + "tally_epoch": 3_u64, } ); let valid_proposal_json_path = @@ -2779,6 +2575,12 @@ fn proposal_offline() -> Result<()> { &valid_proposal_json, ); + let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + while epoch.0 <= 3 { + sleep(1); + epoch = get_epoch(&test, &validator_one_rpc).unwrap(); + } + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let offline_proposal_args = vec![ @@ -2786,44 +2588,48 @@ fn proposal_offline() -> Result<()> { "--data-path", valid_proposal_json_path.to_str().unwrap(), "--offline", + "--signing-keys", + ALBERT_KEY, + "--output-folder-path", + test.test_dir.path().to_str().unwrap(), "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, offline_proposal_args, Some(15))?; - client.exp_string("Proposal created: ")?; + let (_, matched) = client.exp_regex("Proposal serialized to: .*")?; client.assert_success(); - // 3. Generate an offline yay vote - let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 2 { - sleep(1); - epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - } - - let proposal_path = test.test_dir.path().join("proposal"); + let proposal_path = matched + .split(':') + .collect::>() + .get(1) + .unwrap() + .trim() + .to_string(); + // 3. Generate an offline yay vote let submit_proposal_vote = vec![ "vote-proposal", "--data-path", - proposal_path.to_str().unwrap(), + &proposal_path, "--vote", "yay", - "--signer", + "--address", ALBERT, "--offline", + "--signing-keys", + ALBERT_KEY, + "--output-folder-path", + test.test_dir.path().to_str().unwrap(), "--node", &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_vote, Some(15))?; - client.exp_string("Proposal vote created: ")?; + client.exp_string("Proposal vote serialized to: ")?; client.assert_success(); - let expected_file_name = format!("proposal-vote-{}", albert); - let expected_path_vote = test.test_dir.path().join(expected_file_name); - assert!(expected_path_vote.exists()); - // 4. Compute offline tally let tally_offline = vec![ "query-proposal-result", @@ -2835,7 +2641,8 @@ fn proposal_offline() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tally_offline, Some(15))?; - client.exp_string("Result: rejected")?; + client.exp_string("Parsed 1 votes")?; + client.exp_string("rejected with 900.000000 yay votes")?; client.assert_success(); Ok(()) @@ -3618,6 +3425,8 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "10.1", + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -3635,6 +3444,8 @@ fn implicit_account_reveal_pk() -> Result<()> { source, "--amount", "10.1", + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -3645,16 +3456,19 @@ fn implicit_account_reveal_pk() -> Result<()> { // Submit proposal Box::new(|source| { // Gen data for proposal tx - let source = find_address(&test, source).unwrap(); + let author = find_address(&test, source).unwrap(); let valid_proposal_json_path = prepare_proposal_data( &test, - source, - ProposalType::Default(None), + author, + TestWasms::TxProposalCode.read_bytes(), + 12, ); vec![ "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -3690,6 +3504,8 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "1000", + "--signing-keys", + BERTHA_KEY, "--node", &validator_0_rpc, ]; @@ -3763,10 +3579,11 @@ fn test_epoch_sleep() -> Result<()> { fn prepare_proposal_data( test: &setup::Test, source: Address, - proposal_type: ProposalType, + data: impl serde::Serialize, + start_epoch: u64, ) -> PathBuf { - let valid_proposal_json = json!( - { + let valid_proposal_json = json!({ + "proposal": { "content": { "title": "TheTitle", "authors": "test@test.com", @@ -3779,12 +3596,13 @@ fn prepare_proposal_data( "requires": "2" }, "author": source, - "voting_start_epoch": 12_u64, + "voting_start_epoch": start_epoch, "voting_end_epoch": 24_u64, "grace_epoch": 30_u64, - "type": proposal_type - } - ); + }, + "data": data + }); + let valid_proposal_json_path = test.test_dir.path().join("valid_proposal.json"); generate_proposal_json_file( @@ -3793,9 +3611,3 @@ fn prepare_proposal_data( ); valid_proposal_json_path } - -/// Convert epoch `min_duration` in seconds to `epochs_per_year` genesis -/// parameter. -fn epochs_per_year_from_min_duration(min_duration: u64) -> u64 { - 60 * 60 * 24 * 365 / min_duration -} diff --git a/tests/src/e2e/multitoken_tests.rs b/tests/src/e2e/multitoken_tests.rs new file mode 100644 index 0000000000..5d713b2aed --- /dev/null +++ b/tests/src/e2e/multitoken_tests.rs @@ -0,0 +1,372 @@ +//! Tests for multitoken functionality +use color_eyre::eyre::Result; +use namada_core::types::token; + +use super::helpers::get_actor_rpc; +use super::setup::constants::{ALBERT, BERTHA}; +use super::setup::{self, Who}; +use crate::e2e; +use crate::e2e::setup::constants::{ALBERT_KEY, BERTHA_KEY, CHRISTEL_KEY}; + +mod helpers; + +#[test] +fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + // establish a multitoken VP with the following balances + // - #atest5blah/tokens/red/balance/$albert_established = 100 + // - #atest5blah/tokens/red/balance/$bertha = 0 + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + 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::native_whole(100_000_000); + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &albert_addr, + &albert_starting_red_balance, + )?; + + let transfer_amount = token::Amount::native_whole(10_000_000); + + // make a transfer from Albert to Bertha, signed by Christel - this should + // be rejected + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + BERTHA, + CHRISTEL_KEY, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer.exp_string(&format!("Rejected: {albert_addr}"))?; + unauthorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!(albert_balance, albert_starting_red_balance); + + // make a transfer from Albert to Bertha, signed by Albert - this should + // be accepted + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + BERTHA, + ALBERT_KEY, + &token::Amount::native_whole(10_000_000), + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!( + albert_balance, + albert_starting_red_balance - transfer_amount + ); + Ok(()) +} + +#[test] +fn test_multitoken_transfer_established_to_implicit() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account that Albert controls + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + ALBERT, + ALBERT_KEY, + established_alias, + )?; + + 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)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + 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, + &rpc_addr, + &multitoken_alias, + established_alias, + BERTHA, + CHRISTEL_KEY, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer + .exp_string(&format!("Rejected: {established_addr}"))?; + unauthorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!(established_balance, established_starting_red_balance); + + // attempt an authorized transfer to Albert from the established account + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + BERTHA, + ALBERT_KEY, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!( + established_balance, + established_starting_red_balance - transfer_amount + ); + + Ok(()) +} + +#[test] +fn test_multitoken_transfer_implicit_to_established() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account controlled by Bertha + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + BERTHA, + BERTHA_KEY, + established_alias, + )?; + + let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; + let albert_starting_red_balance = token::Amount::native_whole(100_000_000); + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &albert_addr, + &albert_starting_red_balance, + )?; + + 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( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + established_alias, + CHRISTEL_KEY, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer.exp_string(&format!("Rejected: {albert_addr}"))?; + unauthorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!(albert_balance, albert_starting_red_balance); + + // attempt an authorized transfer to Albert from the established account + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + established_alias, + ALBERT_KEY, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!( + albert_balance, + albert_starting_red_balance - transfer_amount + ); + + Ok(()) +} + +#[test] +fn test_multitoken_transfer_established_to_established() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account that Albert controls + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + ALBERT, + ALBERT_KEY, + established_alias, + )?; + + 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)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + // create another established account to receive transfers + let receiver_alias = "receiver"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + BERTHA, + BERTHA_KEY, + receiver_alias, + )?; + + 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)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + let transfer_amount = token::Amount::native_whole(10_000_000); + + // attempt an unauthorized transfer + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + receiver_alias, + CHRISTEL_KEY, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer + .exp_string(&format!("Rejected: {established_addr}"))?; + unauthorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!(established_balance, established_starting_red_balance); + + // attempt an authorized transfer which should succeed + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + receiver_alias, + ALBERT_KEY, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!( + established_balance, + established_starting_red_balance - transfer_amount + ); + + Ok(()) +} diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs new file mode 100644 index 0000000000..176692c508 --- /dev/null +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -0,0 +1,190 @@ +//! Helpers for use in multitoken tests. +use std::path::PathBuf; + +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; +use namada_tx_prelude::storage::KeySeg; +use rand::Rng; +use regex::Regex; + +use super::setup::constants::NAM; +use super::setup::{Bin, NamadaCmd, Test}; +use crate::e2e::setup::constants::{ALBERT, ALBERT_KEY}; +use crate::run; + +const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; +const BALANCE_KEY_SEGMENT: &str = "balance"; +const RED_TOKEN_KEY_SEGMENT: &str = "red"; +const MULTITOKEN_RED_TOKEN_SUB_PREFIX: &str = "tokens/red"; + +const ARBITRARY_SIGNER: &str = ALBERT; +const ARBITRARY_SIGNER_KEY: &str = ALBERT_KEY; + +/// Initializes a VP to represent a multitoken account. +pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { + // we use a VP that always returns true for the multitoken VP here, as we + // are testing out the VPs of the sender and receiver of multitoken + // transactions here - not any multitoken VP itself + let multitoken_vp_wasm_path = + TestWasms::VpAlwaysTrue.path().to_string_lossy().to_string(); + let multitoken_alias = "multitoken"; + + let init_account_args = vec![ + "init-account", + "--source", + ARBITRARY_SIGNER, + "--public-key", + // Value obtained from + // `namada::types::key::ed25519::tests::gen_keypair` + "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", + "--code-path", + &multitoken_vp_wasm_path, + "--alias", + multitoken_alias, + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + rpc_addr, + ]; + let mut client_init_account = + run!(test, Bin::Client, init_account_args, Some(40))?; + client_init_account.exp_string("Transaction is valid.")?; + client_init_account.exp_string("Transaction applied")?; + client_init_account.assert_success(); + Ok(multitoken_alias.to_string()) +} + +/// Generates a random path within the `test` directory. +fn generate_random_test_dir_path(test: &Test) -> PathBuf { + let rng = rand::thread_rng(); + let random_string: String = rng + .sample_iter(&rand::distributions::Alphanumeric) + .take(24) + .map(char::from) + .collect(); + test.test_dir.path().join(random_string) +} + +/// Writes `contents` to a random path within the `test` directory, and return +/// the path. +pub fn write_test_file( + test: &Test, + contents: impl AsRef<[u8]>, +) -> Result { + let path = generate_random_test_dir_path(test); + std::fs::write(&path, contents)?; + Ok(path) +} + +/// Mint red tokens to the given address. +pub fn mint_red_tokens( + test: &Test, + rpc_addr: &str, + multitoken: &Address, + owner: &Address, + amount: &token::Amount, +) -> Result<()> { + let red_balance_key = storage::Key::from(multitoken.to_db_key()) + .push(&MULTITOKEN_KEY_SEGMENT.to_owned())? + .push(&RED_TOKEN_KEY_SEGMENT.to_owned())? + .push(&BALANCE_KEY_SEGMENT.to_owned())? + .push(owner)?; + + let tx_code_path = TestWasms::TxWriteStorageKey.path(); + let tx_data_path = write_test_file( + test, + TxWriteData { + key: red_balance_key, + value: amount.try_to_vec()?, + } + .try_to_vec()?, + )?; + + let tx_data_path = tx_data_path.to_string_lossy().to_string(); + let tx_code_path = tx_code_path.to_string_lossy().to_string(); + let tx_args = vec![ + "tx", + "--signing-keys", + ARBITRARY_SIGNER_KEY, + "--code-path", + &tx_code_path, + "--data-path", + &tx_data_path, + "--ledger-address", + rpc_addr, + ]; + let mut client_tx = run!(test, Bin::Client, tx_args, Some(40))?; + client_tx.exp_string("Transaction is valid.")?; + client_tx.exp_string("Transaction applied")?; + client_tx.assert_success(); + Ok(()) +} + +pub fn attempt_red_tokens_transfer( + test: &Test, + rpc_addr: &str, + multitoken: &str, + from: &str, + to: &str, + signing_keys: &str, + amount: &token::Amount, +) -> Result { + let amount = amount.to_string_native(); + let transfer_args = vec![ + "transfer", + "--token", + multitoken, + "--sub-prefix", + MULTITOKEN_RED_TOKEN_SUB_PREFIX, + "--source", + from, + "--target", + to, + "--signing-keys", + signing_keys, + "--amount", + &amount, + "--ledger-address", + rpc_addr, + ]; + run!(test, Bin::Client, transfer_args, Some(40)) +} + +pub fn fetch_red_token_balance( + test: &Test, + rpc_addr: &str, + multitoken_alias: &str, + owner_alias: &str, +) -> Result { + let balance_args = vec![ + "balance", + "--owner", + owner_alias, + "--token", + multitoken_alias, + "--sub-prefix", + MULTITOKEN_RED_TOKEN_SUB_PREFIX, + "--ledger-address", + rpc_addr, + ]; + let mut client_balance = run!(test, Bin::Client, balance_args, Some(40))?; + let (_, matched) = client_balance.exp_regex(&format!( + r"{MULTITOKEN_RED_TOKEN_SUB_PREFIX}: (\d*\.?\d+)" + ))?; + let decimal_regex = Regex::new(r"(\d*\.?\d+)").unwrap(); + 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, 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 5f148b4fc2..eecc60588f 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -839,6 +839,7 @@ pub mod constants { pub const CHRISTEL: &str = "Christel"; pub const CHRISTEL_KEY: &str = "Christel-key"; pub const DAEWON: &str = "Daewon"; + pub const DAEWON_KEY: &str = "Daewon-key"; pub const ESTER: &str = "Ester"; pub const MATCHMAKER_KEY: &str = "matchmaker-key"; pub const MASP: &str = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5"; diff --git a/tests/src/integration/masp.rs b/tests/src/integration/masp.rs index 6716769c83..a8c94f3b15 100644 --- a/tests/src/integration/masp.rs +++ b/tests/src/integration/masp.rs @@ -390,7 +390,7 @@ fn masp_incentives() -> Result<()> { ETH, "--amount", "10", - "--signer", + "--signing-keys", BERTHA, "--node", validator_one_rpc, @@ -487,7 +487,7 @@ fn masp_incentives() -> Result<()> { BTC, "--amount", "20", - "--signer", + "--signing-keys", ALBERT, "--node", validator_one_rpc, @@ -660,7 +660,7 @@ fn masp_incentives() -> Result<()> { "--amount", &((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) .to_string_native(), - "--signer", + "--signing-keys", BERTHA, "--node", validator_one_rpc, @@ -686,7 +686,7 @@ fn masp_incentives() -> Result<()> { "--amount", &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) .to_string_native(), - "--signer", + "--signing-keys", ALBERT, "--node", validator_one_rpc, diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index 46478acf48..544889b2d1 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -8,7 +8,7 @@ mod test_bridge_pool_vp { wrapped_erc20s, Contracts, EthereumBridgeConfig, UpgradeableContract, }; use namada::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; - use namada::proto::{Code, Data, Section, Signature, Tx}; + use namada::proto::Tx; use namada::types::address::{nam, wnam}; use namada::types::chain::ChainId; use namada::types::eth_bridge_pool::{ @@ -17,7 +17,6 @@ mod test_bridge_pool_vp { use namada::types::ethereum_events::EthAddress; use namada::types::key::{common, ed25519, SecretKey}; use namada::types::token::Amount; - use namada::types::transaction::TxType; use namada_apps::wallet::defaults::{albert_address, bertha_address}; use namada_apps::wasm_loader; @@ -108,14 +107,11 @@ mod test_bridge_pool_vp { let data = transfer.try_to_vec().expect("Test failed"); let wasm_code = wasm_loader::read_wasm_or_exit(wasm_dir(), ADD_TRANSFER_WASM); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = ChainId::default(); - tx.set_data(Data::new(data)); - tx.set_code(Code::new(wasm_code)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - keypair, - ))); + + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(wasm_code) + .add_serialized_data(data) + .sign_wrapper(keypair.clone()); tx } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index b76b4dd041..077385dc61 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -118,6 +118,11 @@ pub fn init_pos( tx_env.spawn_accounts([&native_token]); for validator in genesis_validators { tx_env.spawn_accounts([&validator.address]); + tx_env.init_account_storage( + &validator.address, + vec![validator.consensus_key.clone()], + 1, + ) } tx_env.wl_storage.storage.block.epoch = start_epoch; // Initialize PoS storage diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index dbecb01bd5..878d93aeef 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -27,19 +27,22 @@ mod tests { get_dummy_header as tm_dummy_header, Error as IbcError, }; use namada::ledger::tx_env::TxEnv; - use namada::proto::{Code, Data, Section, Signature, Tx}; - use namada::types::address::{Address, InternalAddress}; + use namada::proto::Tx; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; - use namada::types::transaction::TxType; use namada::types::{address, key}; use namada_core::ledger::ibc::context::transfer_mod::testing::DummyTransferModule; use namada_core::ledger::ibc::Error as IbcActionError; use namada_test_utils::TestWasms; - use namada_tx_prelude::{BorshSerialize, StorageRead, StorageWrite}; + use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::chain::ChainId; + use namada_tx_prelude::{ + Address, BorshSerialize, StorageRead, StorageWrite, + }; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::VpEnv; use prost::Message; use test_log::test; @@ -437,12 +440,11 @@ mod tests { let addr = address::testing::established_address_1(); // Write the public key to storage - let pk_key = key::pk_key(&addr); let keypair = key::testing::keypair_1(); let pk = keypair.ref_to(); - env.wl_storage - .write(&pk_key, pk.try_to_vec().unwrap()) - .unwrap(); + + let _ = pks_handle(&addr).insert(&mut env.wl_storage, 0_u8, pk.clone()); + // Initialize the environment vp_host_env::set(env); @@ -455,28 +457,29 @@ mod tests { // Tx without any data vec![], ] { + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter(vec![pk.clone()]); let signed_tx_data = vp_host_env::with(|env| { - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = env.wl_storage.storage.chain_id.clone(); - tx.header.expiration = expiration; - tx.set_code(Code::new(code.clone())); - tx.set_data(Data::new(data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair, - ))); + let chain_id = env.wl_storage.storage.chain_id.clone(); + let mut tx = Tx::new(chain_id, expiration); + tx.add_code(code.clone()) + .add_serialized_data(data.to_vec()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); env.tx = tx; env.tx.clone() }); assert_eq!(signed_tx_data.data().as_ref(), Some(data)); assert!( signed_tx_data - .verify_signature( - &pk, + .verify_section_signatures( &[ *signed_tx_data.data_sechash(), *signed_tx_data.code_sechash(), ], + pks_map, + 1, + None ) .is_ok() ); @@ -484,12 +487,16 @@ mod tests { let other_keypair = key::testing::keypair_2(); assert!( signed_tx_data - .verify_signature( - &other_keypair.ref_to(), + .verify_section_signatures( &[ *signed_tx_data.data_sechash(), *signed_tx_data.code_sechash(), ], + AccountPublicKeysMap::from_iter([ + other_keypair.ref_to() + ]), + 1, + None ) .is_err() ); @@ -543,13 +550,17 @@ mod tests { // evaluating without any code should fail let empty_code = Hash::zero(); let input_data = vec![]; - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(input_data.clone()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); let result = vp::CTX.eval(empty_code, tx).unwrap(); assert!(!result); @@ -561,14 +572,11 @@ mod tests { let key = Key::wasm_code(&code_hash); env.wl_storage.storage.write(&key, code.clone()).unwrap(); }); - let input_data = vec![]; - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code_from_hash(code_hash) + .add_serialized_data(input_data.clone()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); let result = vp::CTX.eval(code_hash, tx).unwrap(); assert!(result); @@ -581,14 +589,11 @@ mod tests { let key = Key::wasm_code(&code_hash); env.wl_storage.storage.write(&key, code.clone()).unwrap(); }); - let input_data = vec![]; - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code_from_hash(code_hash) + .add_serialized_data(input_data) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); let result = vp::CTX.eval(code_hash, tx).unwrap(); assert!(!result); } @@ -599,18 +604,21 @@ mod tests { tx_host_env::init(); ibc::init_storage(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); // Start a transaction to create a new client let msg = ibc::msg_create_client(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); // create a client with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -640,13 +648,11 @@ mod tests { let msg = ibc::msg_update_client(client_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // update the client with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -663,6 +669,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions ibc::init_storage(); let (client_id, client_state, writes) = ibc::prepare_client(); @@ -679,13 +691,11 @@ mod tests { let msg = ibc::msg_connection_open_init(client_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); // init a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -714,13 +724,11 @@ mod tests { let msg = ibc::msg_connection_open_ack(conn_id, client_state); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // open the connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -740,6 +748,12 @@ mod tests { // Set the initial state before starting transactions ibc::init_storage(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + let (client_id, client_state, writes) = ibc::prepare_client(); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { @@ -754,13 +768,11 @@ mod tests { let msg = ibc::msg_connection_open_try(client_id, client_state); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); // open try a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -789,13 +801,11 @@ mod tests { let msg = ibc::msg_connection_open_confirm(conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // open the connection with the mssage tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -812,6 +822,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -831,13 +847,11 @@ mod tests { let msg = ibc::msg_channel_open_init(port_id.clone(), conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); // init a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -866,13 +880,11 @@ mod tests { let msg = ibc::msg_channel_open_ack(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // open the channle with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -903,18 +915,22 @@ mod tests { }); }); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Start a transaction for ChannelOpenTry let port_id = ibc::PortId::transfer(); let msg = ibc::msg_channel_open_try(port_id.clone(), conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); // try open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -944,13 +960,11 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -984,18 +998,22 @@ mod tests { }); }); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Start a transaction to close the channel let msg = ibc::msg_channel_close_init(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // close the channel with the message let mut actions = tx_host_env::ibc::ibc_actions(tx::ctx()); // the dummy module closes the channel @@ -1037,18 +1055,22 @@ mod tests { }); }); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Start a transaction to close the channel let msg = ibc::msg_channel_close_confirm(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // close the channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1083,6 +1105,12 @@ mod tests { }); }); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Start a transaction to send a packet let msg = ibc::msg_transfer(port_id, channel_id, token.to_string(), &sender); @@ -1092,13 +1120,11 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs.clone(), pks_map.clone()) + .sign_wrapper(keypair.clone()); // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1141,13 +1167,11 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // ack the packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1179,6 +1203,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, sender) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1221,13 +1251,11 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1258,6 +1286,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, receiver) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1290,13 +1324,11 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1331,6 +1363,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, receiver) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1381,13 +1419,11 @@ mod tests { let msg = ibc::msg_packet_recv(packet); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1419,6 +1455,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, receiver) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1476,13 +1518,11 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1517,6 +1557,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, sender) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1572,13 +1618,11 @@ mod tests { let msg = ibc::msg_timeout(packet, ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // timeout the packet tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1603,6 +1647,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, sender) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1657,13 +1707,11 @@ mod tests { let msg = ibc::msg_timeout_on_close(packet, ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .sign_raw(keypairs, pks_map) + .sign_wrapper(keypair); // timeout the packet tx_host_env::ibc::ibc_actions(tx::ctx()) diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index d3da806ca5..a0e88ab802 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -18,7 +18,8 @@ use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::run::Error; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; -use namada_tx_prelude::{BorshSerialize, Ctx}; +use namada_tx_prelude::{storage_api, BorshSerialize, Ctx}; +use namada_vp_prelude::key::common; use tempfile::TempDir; use crate::vp::TestVpEnv; @@ -67,7 +68,7 @@ impl Default for TestTxEnv { storage: TestStorage::default(), write_log: WriteLog::default(), }; - let mut tx = Tx::new(TxType::Raw); + let mut tx = Tx::from_type(TxType::Raw); tx.header.chain_id = wl_storage.storage.chain_id.clone(); Self { wl_storage, @@ -102,6 +103,7 @@ impl TestTxEnv { epoch_duration: Option, vp_whitelist: Option>, tx_whitelist: Option>, + max_signatures_per_transaction: Option, ) { parameters::update_epoch_parameter( &mut self.wl_storage, @@ -121,6 +123,11 @@ impl TestTxEnv { vp_whitelist.unwrap_or_default(), ) .unwrap(); + parameters::update_max_signature_per_tx( + &mut self.wl_storage, + max_signatures_per_transaction.unwrap_or(15), + ) + .unwrap(); } pub fn store_wasm_code(&mut self, code: Vec) { @@ -155,6 +162,34 @@ impl TestTxEnv { } } + pub fn init_account_storage( + &mut self, + owner: &Address, + public_keys: Vec, + threshold: u8, + ) { + storage_api::account::init_account_storage( + &mut self.wl_storage, + owner, + &public_keys, + threshold, + ) + .expect("Unable to write Account substorage."); + } + + /// Set public key for the address. + pub fn write_account_threshold( + &mut self, + address: &Address, + threshold: u8, + ) { + let storage_key = key::threshold_key(address); + self.wl_storage + .storage + .write(&storage_key, threshold.try_to_vec().unwrap()) + .unwrap(); + } + /// Commit the genesis state. Typically, you'll want to call this after /// setting up the initial state, before running a transaction. pub fn commit_genesis(&mut self) { @@ -186,19 +221,6 @@ impl TestTxEnv { .unwrap(); } - /// Set public key for the address. - pub fn write_public_key( - &mut self, - address: &Address, - public_key: &key::common::PublicKey, - ) { - let storage_key = key::pk_key(address); - self.wl_storage - .storage - .write(&storage_key, public_key.try_to_vec().unwrap()) - .unwrap(); - } - /// Apply the tx changes to the write log. pub fn execute_tx(&mut self) -> Result<(), Error> { wasm::run::tx( diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 02561dbc9a..890d0681d8 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -69,7 +69,7 @@ impl Default for TestVpEnv { storage: TestStorage::default(), write_log: WriteLog::default(), }; - let mut tx = Tx::new(TxType::Raw); + let mut tx = Tx::from_type(TxType::Raw); tx.header.chain_id = wl_storage.storage.chain_id.clone(); Self { addr: address::testing::established_address_1(), diff --git a/tx_prelude/src/account.rs b/tx_prelude/src/account.rs new file mode 100644 index 0000000000..da6c213601 --- /dev/null +++ b/tx_prelude/src/account.rs @@ -0,0 +1,18 @@ +use namada_core::types::transaction::account::InitAccount; + +use super::*; + +pub fn init_account( + ctx: &mut Ctx, + owner: &Address, + data: InitAccount, +) -> EnvResult
{ + storage_api::account::init_account_storage( + ctx, + owner, + &data.public_keys, + data.threshold, + )?; + + Ok(owner.to_owned()) +} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 6e5a0192c8..cd2f5d5f92 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -6,6 +6,7 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] +pub mod account; pub mod ibc; pub mod key; pub mod proof_of_stake; @@ -18,7 +19,6 @@ pub use borsh::{BorshDeserialize, BorshSerialize}; pub use namada_core::ledger::eth_bridge; pub use namada_core::ledger::governance::storage as gov_storage; pub use namada_core::ledger::parameters::storage as parameters_storage; -pub use namada_core::ledger::slash_fund::storage as slash_fund_storage; pub use namada_core::ledger::storage::types::encode; pub use namada_core::ledger::storage_api::{ self, governance, iter_prefix, iter_prefix_bytes, Error, OptionExt, diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 48b0cb665f..cc8bcb7b63 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -2,7 +2,7 @@ use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::transaction::InitValidator; +use namada_core::types::transaction::pos::InitValidator; use namada_core::types::{key, token}; pub use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::{ @@ -74,7 +74,8 @@ impl Ctx { pub fn init_validator( &mut self, InitValidator { - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -89,8 +90,12 @@ impl Ctx { let current_epoch = self.get_block_epoch()?; // Init validator account let validator_address = self.init_account(validator_vp_code_hash)?; - let pk_key = key::pk_key(&validator_address); - self.write(&pk_key, &account_key)?; + storage_api::account::init_account_storage( + self, + &validator_address, + &account_keys, + threshold, + )?; let protocol_pk_key = key::protocol_pk_key(&validator_address); self.write(&protocol_pk_key, &protocol_key)?; let dkg_pk_key = key::dkg_session_keys::dkg_pk_key(&validator_address); diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 8ec55ef7cd..c8e555a87b 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -6,8 +6,6 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -pub mod key; - // used in the VP input use core::convert::AsRef; use core::slice; @@ -74,11 +72,32 @@ pub fn log_string>(msg: T) { /// Checks if a proposal id is being executed pub fn is_proposal_accepted(ctx: &Ctx, proposal_id: u64) -> VpResult { let proposal_execution_key = - gov_storage::get_proposal_execution_key(proposal_id); + gov_storage::keys::get_proposal_execution_key(proposal_id); ctx.has_key_pre(&proposal_execution_key) } +/// Verify section signatures +pub fn verify_signatures(ctx: &Ctx, tx: &Tx, owner: &Address) -> VpResult { + let max_signatures_per_transaction = + parameters::max_signatures_per_transaction(&ctx.pre())?; + + let public_keys_index_map = + storage_api::account::public_keys_index_map(&ctx.pre(), owner)?; + let threshold = + storage_api::account::threshold(&ctx.pre(), owner)?.unwrap_or(1); + + let targets = &[*tx.data_sechash(), *tx.code_sechash()]; + tx.verify_section_signatures( + targets, + public_keys_index_map, + threshold, + max_signatures_per_transaction, + ) + .map_err(|_e| Error::SimpleMessage("Invalid signatures")) + .map(|_| true) +} + /// Checks whether a transaction is valid, which happens in two cases: /// - tx is whitelisted, or /// - tx is executed by an approved governance proposal (no need to be diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 31f5d23169..d04b6b73b8 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3332,7 +3332,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.20.1" +version = "0.21.0" dependencies = [ "async-trait", "bimap", @@ -3392,7 +3392,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.20.1" +version = "0.21.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3444,7 +3444,7 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "ethers", @@ -3464,7 +3464,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.20.1" +version = "0.21.0" dependencies = [ "proc-macro2", "quote", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "data-encoding", @@ -3487,7 +3487,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "namada_core", @@ -3496,7 +3496,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.20.1" +version = "0.21.0" dependencies = [ "async-trait", "chrono", @@ -3513,6 +3513,7 @@ dependencies = [ "num-traits", "prost", "regex", + "serde", "serde_json", "sha2 0.9.9", "tempfile", @@ -3525,7 +3526,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -3539,7 +3540,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -3548,7 +3549,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "namada_core", @@ -3561,7 +3562,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "getrandom 0.2.9", @@ -5785,7 +5786,7 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tx_template" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "getrandom 0.2.9", @@ -5908,7 +5909,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "getrandom 0.2.9", diff --git a/wasm/checksums.json b/wasm/checksums.json index 0076a46c69..77319002b4 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,21 +1,21 @@ { - "tx_bond.wasm": "tx_bond.cf028fa100dd40dce3bb6b7b7772fac3027c59e40fd80d2b3417ef5c6827a9d9.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.3545bf61d615a5843394372ca0db09b9641bae26f2a359d78360dcf16b02c7f5.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.33d15386e96d17745100a03f7cc8b6d51190d29e2193a3edde6905b9278e3cea.wasm", - "tx_ibc.wasm": "tx_ibc.3b058adf100b8ca43f605fa39a6d6d6d2693c396f2464565bdf14b3d56200f61.wasm", - "tx_init_account.wasm": "tx_init_account.f27884da10ed975e34a33926932e3237522367f811051245979eb8600e0d089a.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.605a9e3675ce1956320d1b0601ac6b6e717a97629971477447abab0d2cdb8514.wasm", - "tx_init_validator.wasm": "tx_init_validator.4fc70e31ccf24a60af7089f2e24d25c7b92bd20dd98b2c3c54e066832edb02ac.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.89e6eaef07c8591b1c1197d90520cadb1b63f885802a9c3988215595cbf3ddf9.wasm", - "tx_transfer.wasm": "tx_transfer.37315dda033dd05f0ee5dc61fa0e1ded73c33764211243ed06a61be070b297f6.wasm", - "tx_unbond.wasm": "tx_unbond.0d6745807572f8e17b9be450d26330ae76779158401f013bc8bcdc13ee4602ad.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.015c9abd7c32abd5ee19783e8553c507705bfad554c7489aaae89f066d78a335.wasm", - "tx_update_vp.wasm": "tx_update_vp.60c8ecf16d90fd4ce40e9eef09b129e6785a68d2389fcfb7248c8a253fb68d47.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1556db8849f7889bf392e1e94f99864a38372f804040fa39352f0d118da5ac26.wasm", - "tx_withdraw.wasm": "tx_withdraw.a7c679c73b8b08b937e76033b0d05dd87f57c38ce27f9b6488c19150addf5c45.wasm", - "vp_implicit.wasm": "vp_implicit.f99f17dc584bd29d6f37c56a032496d3348771bce254d68568ac9587dab25f12.wasm", - "vp_masp.wasm": "vp_masp.82fd0d5a82e1ce57ed7bd9d154774cea2be5ed7d92dcc80babb4dcec9473d504.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.dcc531bd09a3f5c7dc20dec68346638f82ddb049b16835d2f49b0870f8cfc4a1.wasm", - "vp_user.wasm": "vp_user.01d995ecad8a8c6eb743c94f28307b9008b70f95ccbc2c276c49684b05e03ebf.wasm", - "vp_validator.wasm": "vp_validator.fb4088d2a342448c418d200d02f17cd0ff0ff28ee9fc797f5ecf393bdeaf3770.wasm" + "tx_bond.wasm": "tx_bond.d80494422e1080b6d74848ab9c59e5c2cbed9a36cc3f14b0880dfabb464b5a2f.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.3c356b9dd2e66c5a21a057e2a9475234888bd49f654f1f26572acc94d711ec2b.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.59b68fe14e5b8120a38b443e5ae8462824d2804b524bbaf19407891dfbf5e7f3.wasm", + "tx_ibc.wasm": "tx_ibc.36a975c3bb8a40d10146e2464667a8bc6e7c241340a6c0469fb81f45eb71df86.wasm", + "tx_init_account.wasm": "tx_init_account.70bdce651438949ba5b22bd15cfdad13a658d69e8297f84574208db4d5f6e2c8.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.85bbdfd529d58f38319e254ba495240c33ed249df74b75833b44ff46be9740a9.wasm", + "tx_init_validator.wasm": "tx_init_validator.afdfc83ca51f3dd1a5899483e0b60157386f5d096e8c5ddf15544545a8966e0a.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.76b2d71a3e46f994cae02c56f03a63acfa629ef203ccd15e5bf2f1e56088a944.wasm", + "tx_transfer.wasm": "tx_transfer.ca4bfa6d2ca698ad90f397fc24eb85d9ba97a06bfbe6575396019453acb0741e.wasm", + "tx_unbond.wasm": "tx_unbond.6f46fcc02da5cc4cec6bb15051b31f0ae1395fa1d43deab106826c6dedb7a5f8.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.1b89353ee8974fb30ad7c0273dd388ba64bdd83048d8700f4f176c0e479cf168.wasm", + "tx_update_account.wasm": "tx_update_account.3e570147734b0c5e5d432f8814ddfe202365f0639f3a014dde5cc07dfb266fc1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.07178ab11ee7a9806c204f655038819849fd7012b6522326217391198c3a7ceb.wasm", + "tx_withdraw.wasm": "tx_withdraw.6981abc141462a5b135e33fc225cf47cc29044b56b7e2d64296a83da02fa224a.wasm", + "vp_implicit.wasm": "vp_implicit.20eef3316b1e2ca7e80f1ed5eb3a2b02df89c398fd47a4e2c039b609a53313b1.wasm", + "vp_masp.wasm": "vp_masp.3fbb62ce883f122bb9a3db6764d4380c59bce461f37a21679ce8d0a3988fc4aa.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.49ef6568b0c9a1d6eaca149b1fe42cf65d6f02e7b6a3f541ee6ce00d7a7bf45b.wasm", + "vp_user.wasm": "vp_user.56d6010f5fed4af9d6e3ac4e660998ff4ad5444c9efb6b3bdea7f106247cd348.wasm", + "vp_validator.wasm": "vp_validator.595bfb11327c21f26c6648fcea590d801f10d0f6c82f9ef988f19141c1b4ea3d.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index ab661039f9..d17d02d880 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.20.1" +version = "0.21.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/tx_template/src/lib.rs b/wasm/tx_template/src/lib.rs index 4a14783325..5b0f8fb501 100644 --- a/wasm/tx_template/src/lib.rs +++ b/wasm/tx_template/src/lib.rs @@ -19,7 +19,7 @@ mod tests { // The environment must be initialized first tx_host_env::init(); - let tx = Tx::new(TxType::Raw); + let tx = Tx::from_type(TxType::Raw); apply_tx(ctx(), tx).unwrap(); let env = tx_host_env::take(); diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 5d2ecf553e..e8d15a2cf0 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.20.1" +version = "0.21.0" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index d25f6d8b9a..a2f8775d22 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.20.1" +version = "0.21.0" [lib] crate-type = ["cdylib"] @@ -24,7 +24,7 @@ tx_reveal_pk = ["namada_tx_prelude"] tx_transfer = ["namada_tx_prelude"] tx_unbond = ["namada_tx_prelude"] tx_unjail_validator = ["namada_tx_prelude"] -tx_update_vp = ["namada_tx_prelude"] +tx_update_account = ["namada_tx_prelude"] tx_vote_proposal = ["namada_tx_prelude"] tx_withdraw = ["namada_tx_prelude"] vp_implicit = ["namada_vp_prelude", "once_cell"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index f394179dc7..a88065355f 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -16,7 +16,7 @@ wasms += tx_reveal_pk wasms += tx_transfer wasms += tx_unbond wasms += tx_unjail_validator -wasms += tx_update_vp +wasms += tx_update_account wasms += tx_vote_proposal wasms += tx_withdraw wasms += vp_implicit diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 2fc69f65c9..ce5b1b1561 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -20,8 +20,8 @@ pub mod tx_transfer; pub mod tx_unbond; #[cfg(feature = "tx_unjail_validator")] pub mod tx_unjail_validator; -#[cfg(feature = "tx_update_vp")] -pub mod tx_update_vp; +#[cfg(feature = "tx_update_account")] +pub mod tx_update_account; #[cfg(feature = "tx_vote_proposal")] pub mod tx_vote_proposal; #[cfg(feature = "tx_withdraw")] diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 33d004591b..f9a6ab9c60 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -22,10 +22,8 @@ mod tests { bond_handle, read_consensus_validator_set_addresses_with_stake, 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; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -34,6 +32,7 @@ mod tests { arb_established_address, arb_non_internal_address, }; use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::chain::ChainId; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -108,14 +107,12 @@ mod tests { let tx_code = vec![]; let tx_data = bond.try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &key, - ))); - let signed_tx = tx.clone(); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(key); + + let signed_tx = tx; // Ensure that the initial stake of the sole validator is equal to the // PoS account balance diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index edbad6efaa..ee3685b191 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -21,15 +21,14 @@ 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; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; use namada_tests::tx::*; use namada_tx_prelude::address::testing::arb_established_address; + use namada_tx_prelude::chain::ChainId; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -87,14 +86,12 @@ mod tests { let tx_code = vec![]; let tx_data = commission_change.try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(tx_data)); - tx.set_code(Code::new(tx_code)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &key, - ))); - let signed_tx = tx.clone(); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(key); + + let signed_tx = tx; // Read the data before the tx is executed let commission_rate_handle = diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index 2e85b70ae9..346afb2bec 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -7,7 +7,7 @@ use namada_tx_prelude::*; fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data")?; - let tx_data = transaction::InitAccount::try_from_slice(&data[..]) + let tx_data = transaction::account::InitAccount::try_from_slice(&data[..]) .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); @@ -18,8 +18,17 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { .ok_or_err_msg("vp code section must be tagged as extra")? .code .hash(); + let address = ctx.init_account(vp_code)?; - let pk_key = key::pk_key(&address); - ctx.write(&pk_key, &tx_data.public_key)?; + + match account::init_account(ctx, &address, tx_data) { + Ok(address) => { + debug_log!("Created account {}", address.encode(),) + } + Err(err) => { + debug_log!("Account creation failed with: {}", err); + panic!() + } + } Ok(()) } diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index c0da1d9316..e11ebb7a61 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -8,22 +8,26 @@ fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { 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( + let code_hash = tx_data.get_section_code_hash(); + let code = match code_hash { + 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, + None => None, }; + log_string("apply_tx called to create a new governance proposal"); governance::init_proposal(ctx, tx_data, content, code) diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 0cd37da111..eb80ec444e 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -1,7 +1,7 @@ //! A tx to initialize a new validator account with a given public keys and a //! validity predicates. -use namada_tx_prelude::transaction::InitValidator; +use namada_tx_prelude::transaction::pos::InitValidator; use namada_tx_prelude::*; #[transaction] diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index e453d48b14..90b22734a0 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -23,15 +23,14 @@ mod tests { bond_handle, read_consensus_validator_set_addresses_with_stake, 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; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; use namada_tests::tx::*; use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::chain::ChainId; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -127,14 +126,11 @@ mod tests { let tx_code = vec![]; let tx_data = unbond.try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(tx_data)); - tx.set_code(Code::new(tx_code)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &key, - ))); - let signed_tx = tx.clone(); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(key); + let signed_tx = tx; let unbond_src = unbond .source diff --git a/wasm/wasm_source/src/tx_update_account.rs b/wasm/wasm_source/src/tx_update_account.rs new file mode 100644 index 0000000000..c2553759e5 --- /dev/null +++ b/wasm/wasm_source/src/tx_update_account.rs @@ -0,0 +1,45 @@ +//! A tx for updating an account's validity predicate. +//! This tx wraps the validity predicate inside `SignedTxData` as +//! its input as declared in `shared` crate. + +use namada_tx_prelude::key::pks_handle; +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { + let signed = tx; + let data = signed.data().ok_or_err_msg("Missing data")?; + let tx_data = + transaction::account::UpdateAccount::try_from_slice(&data[..]) + .wrap_err("failed to decode UpdateAccount")?; + + let owner = &tx_data.addr; + debug_log!("update VP for: {:#?}", tx_data.addr); + + if let Some(hash) = tx_data.vp_code_hash { + let vp_code_hash = signed + .get_section(&hash) + .ok_or_err_msg("vp code section not found")? + .extra_data_sec() + .ok_or_err_msg("vp code section must be tagged as extra")? + .code + .hash(); + + ctx.update_validity_predicate(owner, vp_code_hash)?; + } + + if let Some(threshold) = tx_data.threshold { + let threshold_key = key::threshold_key(owner); + ctx.write(&threshold_key, threshold)?; + } + + if !tx_data.public_keys.is_empty() { + storage_api::account::clear_public_keys(ctx, owner)?; + for (index, public_key) in tx_data.public_keys.iter().enumerate() { + let index = index as u8; + pks_handle(owner).insert(ctx, index, public_key.clone())?; + } + } + + Ok(()) +} diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 20b202bdb6..dd40e044b3 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -22,10 +22,8 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { 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; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -34,6 +32,7 @@ mod tests { arb_established_address, arb_non_internal_address, }; use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::chain::ChainId; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -171,14 +170,11 @@ mod tests { let tx_code = vec![]; let tx_data = withdraw.try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &key, - ))); - let signed_tx = tx.clone(); + let mut tx = Tx::new(ChainId::default(), None); + tx.add_code(tx_code) + .add_serialized_data(tx_data) + .sign_wrapper(key); + let signed_tx = tx; // Read data before we apply tx: let pos_balance_key = token::balance_key( diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index f82c573bc1..9da84d1776 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -28,14 +28,14 @@ enum KeyType<'a> { 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) { + if let Some(address) = key::is_pks_key(key) { Self::Pk(address) } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS - } else if gov_storage::is_vote_key(key) { - let voter_address = gov_storage::get_voter_address(key); + } else if gov_storage::keys::is_vote_key(key) { + let voter_address = gov_storage::keys::get_voter_address(key); if let Some(address) = voter_address { Self::GovernanceVote(address) } else { @@ -62,18 +62,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -196,7 +186,7 @@ fn validate_tx( mod tests { // Use this as `#[test]` annotation to enable logging use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -207,6 +197,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{storage_api, StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -216,7 +207,7 @@ mod tests { /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let addr: Address = address::testing::established_address_1(); let keys_changed: BTreeSet = BTreeSet::default(); @@ -240,7 +231,8 @@ mod tests { let addr: Address = (&public_key).into(); // Initialize a tx environment - let tx_env = TestTxEnv::default(); + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, None, None, None); // Initialize VP environment from a transaction vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { @@ -249,7 +241,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -276,7 +268,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -308,12 +300,16 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { // Do the same as reveal_pk, but with the wrong key - let key = namada_tx_prelude::key::pk_key(&addr); - tx_host_env::ctx().write(&key, &mismatched_pk).unwrap(); + let _ = storage_api::account::set_public_key_at( + tx_host_env::ctx(), + &addr, + &mismatched_pk, + 0, + ); }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -373,7 +369,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -414,6 +410,8 @@ mod tests { // Initialize a tx environment let mut tx_env = tx_host_env::take(); + tx_env.init_parameters(None, Some(vec![]), Some(vec![]), None); + let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); @@ -425,6 +423,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -449,7 +448,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -461,8 +460,8 @@ mod tests { ); } - /// Test that a PoS action that must be authorized is accepted with a valid - /// signature. + /// Test that a PoS action that must be authorized is accepted with a + /// valid signature. #[test] fn test_signed_pos_action_accepted() { // Init PoS genesis @@ -505,6 +504,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -517,8 +517,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - // 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 @@ -530,14 +528,18 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); + let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -565,6 +567,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -599,7 +602,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -624,8 +627,11 @@ mod tests { let token = address::nam(); let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + tx_env.init_parameters(None, None, None, None); + // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -638,8 +644,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -660,20 +664,25 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); + let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); + assert!( validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() @@ -696,6 +705,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -731,7 +741,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -760,8 +770,8 @@ mod tests { } proptest! { - /// Test that an unsigned tx that performs arbitrary storage writes or - /// deletes to the account is rejected. + /// Test that an unsigned tx that performs arbitrary storage writes + /// or deletes to the account is rejected. #[test] fn test_unsigned_arb_storage_write_rejected( (_sk, vp_owner, storage_key) in arb_account_storage_subspace_key(), @@ -787,7 +797,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -795,17 +805,12 @@ mod tests { vp_host_env::set(vp_env); assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } - } - proptest! { - /// Test that a signed tx that performs arbitrary storage writes or - /// deletes to the account is accepted. - #[test] - fn test_signed_arb_storage_write( - (secret_key, vp_owner, storage_key) in arb_account_storage_subspace_key(), - // Generate bytes to write. If `None`, delete from the key instead - storage_value in any::>>(), - ) { + fn test_signed_arb_storage_write( + (secret_key, vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); @@ -815,7 +820,7 @@ mod tests { tx_env.spawn_accounts(storage_key_addresses); let public_key = secret_key.ref_to(); - tx_env.write_public_key(&vp_owner, &public_key); + let _ = storage_api::account::set_public_key_at(tx_host_env::ctx(), &vp_owner, &public_key, 0); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -827,11 +832,17 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key))); + tx.add_section(Section::SectionSignature(MultiSignature::new( + vec![*tx.data_sechash(), *tx.code_sechash()], + &[secret_key], + &pks_map, + ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -869,7 +880,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -899,12 +910,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -914,13 +925,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -947,13 +961,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -963,13 +980,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 5905c3cc4c..f41909f0c5 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -25,18 +25,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -117,7 +107,7 @@ fn validate_tx( #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature, Signature}; use namada::types::transaction::TxType; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging @@ -126,6 +116,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -138,7 +129,7 @@ mod tests { /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let addr: Address = address::testing::established_address_1(); let keys_changed: BTreeSet = BTreeSet::default(); @@ -192,7 +183,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -229,7 +220,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -258,8 +249,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -269,13 +259,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key.clone()]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -341,7 +334,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -365,6 +358,7 @@ mod tests { let target = address::testing::established_address_2(); let target_key = key::testing::keypair_1(); + let _public_key = target_key.ref_to(); let token = address::nam(); let amount = token::Amount::from_uint(amount, 0).unwrap(); @@ -401,7 +395,7 @@ mod tests { let mut vp_env = vp_host_env::take(); // This is set by the protocol when the wrapper tx has a valid PoW vp_env.has_valid_pow = true; - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(solution_bytes)); tx_data.set_code(Code::new(vec![])); tx_data.add_section(Section::Signature(Signature::new(vec![*tx_data.data_sechash(), *tx_data.code_sechash()], &target_key))); @@ -436,7 +430,7 @@ mod tests { let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - tx_env.write_public_key(&vp_owner, public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -448,11 +442,17 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key.clone()]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new( + vec![*tx.data_sechash(), *tx.code_sechash()], + &[keypair], + &pks_map, + ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 6d6977997e..d68801834a 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -28,8 +28,8 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS - } else if gov_storage::is_vote_key(key) { - let voter_address = gov_storage::get_voter_address(key); + } else if gov_storage::keys::is_vote_key(key) { + let voter_address = gov_storage::keys::get_voter_address(key); if let Some(address) = voter_address { Self::GovernanceVote(address) } else { @@ -60,18 +60,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -184,7 +174,7 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -196,6 +186,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -205,7 +196,7 @@ mod tests { /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let addr: Address = address::testing::established_address_1(); let keys_changed: BTreeSet = BTreeSet::default(); @@ -265,7 +256,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -323,7 +314,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -350,6 +341,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -362,8 +354,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -385,13 +375,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -435,7 +428,7 @@ mod tests { let mut tx_env = tx_host_env::take(); let secret_key = key::testing::keypair_1(); - let _public_key = secret_key.ref_to(); + let public_key = secret_key.ref_to(); let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); @@ -445,6 +438,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, @@ -469,7 +463,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -525,6 +519,8 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); + // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, @@ -537,8 +533,6 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, amount); - tx_env.write_public_key(&vp_owner, &public_key); - // 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 @@ -550,13 +544,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -612,7 +609,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -668,7 +665,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -697,8 +694,7 @@ mod tests { // their storage let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -710,11 +706,13 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &[keypair], &pks_map))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -750,7 +748,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); tx_data.set_code(Code::new(vec![])); let keys_changed: BTreeSet = @@ -769,7 +767,7 @@ mod tests { fn test_signed_vp_update_accepted() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, None, None); + tx_env.init_parameters(None, None, None, None); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -781,8 +779,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -792,13 +789,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -817,7 +817,12 @@ mod tests { fn test_signed_vp_update_not_whitelisted_rejected() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + tx_env.init_parameters( + None, + Some(vec!["some_hash".to_string()]), + None, + None, + ); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -829,8 +834,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -840,13 +844,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -874,12 +881,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -889,13 +900,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -927,12 +941,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -942,13 +956,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -975,13 +992,17 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -991,13 +1012,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index eb80929626..9b0de80f3e 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -30,8 +30,8 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS - } else if gov_storage::is_vote_key(key) { - let voter_address = gov_storage::get_voter_address(key); + } else if gov_storage::keys::is_vote_key(key) { + let voter_address = gov_storage::keys::get_voter_address(key); if let Some(address) = voter_address { Self::GovernanceVote(address) } else { @@ -60,18 +60,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -191,7 +181,7 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -203,6 +193,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -212,7 +203,7 @@ mod tests { /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let addr: Address = address::testing::established_address_1(); let keys_changed: BTreeSet = BTreeSet::default(); @@ -272,7 +263,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -330,7 +321,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -357,6 +348,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -369,7 +361,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -391,13 +382,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -441,7 +435,7 @@ mod tests { let mut tx_env = tx_host_env::take(); let secret_key = key::testing::keypair_1(); - let _public_key = secret_key.ref_to(); + let public_key = secret_key.ref_to(); let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); @@ -451,6 +445,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -481,7 +476,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -536,7 +531,8 @@ mod tests { 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]); + tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -549,8 +545,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - // 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 @@ -568,13 +562,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -629,7 +626,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -685,7 +682,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -715,7 +712,7 @@ mod tests { let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -727,11 +724,13 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &[keypair], &pks_map))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -767,7 +766,7 @@ mod tests { }); let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); + let mut tx_data = Tx::from_type(TxType::Raw); tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); @@ -785,7 +784,7 @@ mod tests { fn test_signed_vp_update_accepted() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, None, None); + tx_env.init_parameters(None, None, None, None); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -797,8 +796,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -808,13 +806,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -833,7 +834,12 @@ mod tests { fn test_signed_vp_update_not_whitelisted_rejected() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + tx_env.init_parameters( + None, + Some(vec!["some_hash".to_string()]), + None, + None, + ); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -845,8 +851,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -856,13 +861,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -890,12 +898,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -905,13 +917,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -943,12 +958,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -958,13 +973,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); tx.set_code(Code::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -991,13 +1009,17 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -1007,13 +1029,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new( + tx.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 3f8ae1b079..4fd9f4e18b 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 dba64ff70a..c0e7144a98 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 38696b9d71..7f819f67cd 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 3c937a2ecc..603c2c315a 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 c1372ee1ed..a6336f6a86 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 edd28f97ec..ce797db650 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/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 5d600d185f..a0fb758ae9 100755 Binary files a/wasm_for_tests/tx_write_storage_key.wasm and b/wasm_for_tests/tx_write_storage_key.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 992f4007a7..e478594b61 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 4aa562cc3a..99da4e37ea 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 2ed4af1c95..a9deea4201 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 3fd6989865..52335cd443 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 202f5bf008..bfe1245bc9 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 3788761851..4fe9bd4b19 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3332,7 +3332,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.20.1" +version = "0.21.0" dependencies = [ "async-trait", "bimap", @@ -3392,7 +3392,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.20.1" +version = "0.21.0" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3444,7 +3444,7 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "ethers", @@ -3464,7 +3464,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.20.1" +version = "0.21.0" dependencies = [ "proc-macro2", "quote", @@ -3473,7 +3473,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "data-encoding", @@ -3487,7 +3487,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "namada_core", @@ -3496,7 +3496,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.20.1" +version = "0.21.0" dependencies = [ "async-trait", "chrono", @@ -3513,6 +3513,7 @@ dependencies = [ "num-traits", "prost", "regex", + "serde", "serde_json", "sha2 0.9.9", "tempfile", @@ -3525,7 +3526,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -3539,7 +3540,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "masp_primitives", @@ -3548,7 +3549,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "namada_core", @@ -3561,7 +3562,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.20.1" +version = "0.21.0" dependencies = [ "borsh 0.9.4", "getrandom 0.2.9", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 77f2ee0713..1ce9422658 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.20.1" +version = "0.21.0" [lib] crate-type = ["cdylib"] @@ -13,7 +13,6 @@ crate-type = ["cdylib"] # Newly added wasms should also be added into the Makefile `$(wasms)` list. [features] tx_memory_limit = [] -tx_mint_tokens = [] tx_no_op = [] tx_read_storage_key = [] tx_write = [] diff --git a/wasm_for_tests/wasm_source/Makefile b/wasm_for_tests/wasm_source/Makefile index ca46c0b569..f56ab0afb8 100644 --- a/wasm_for_tests/wasm_source/Makefile +++ b/wasm_for_tests/wasm_source/Makefile @@ -6,7 +6,6 @@ nightly := $(shell cat ../../rust-nightly-version) # All the wasms that can be built from this source, switched via Cargo features # Wasms can be added via the Cargo.toml `[features]` list. wasms := tx_memory_limit -wasms += tx_mint_tokens wasms += tx_no_op wasms += tx_read_storage_key wasms += tx_write diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 561d43fc2e..790ad1a72c 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -34,7 +34,7 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, _tx_data: Tx) -> TxResult { // governance - let target_key = gov_storage::get_min_proposal_grace_epoch_key(); + let target_key = gov_storage::keys::get_min_proposal_grace_epoch_key(); ctx.write(&target_key, 9_u64)?; // parameters @@ -127,30 +127,6 @@ pub mod main { } } -/// A tx that attempts to mint tokens in the transfer's target without debiting -/// the tokens from the source. This tx is expected to be rejected by the -/// token's VP. -#[cfg(feature = "tx_mint_tokens")] -pub mod main { - use namada_test_utils::tx_data::TxMintData; - use namada_tx_prelude::*; - - #[transaction] - fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { - let signed = tx_data; - let mint_data = - TxMintData::try_from_slice(&signed.data().unwrap()[..]).unwrap(); - log_string(format!("apply_tx called to mint tokens: {:#?}", mint_data)); - let TxMintData { - minter, - target, - token, - amount, - } = mint_data; - token::mint(ctx, &minter, &target, &token, amount) - } -} - /// A VP that always returns `true`. #[cfg(feature = "vp_always_true")] pub mod main {