From f787667bd91a8e1d0de0d3029ff08a1b3d875317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 20 Oct 2022 15:22:18 +0200 Subject: [PATCH 01/38] client: add a block query to print hash, height and time of a block --- apps/src/bin/anoma-client/cli.rs | 3 +++ apps/src/lib/cli.rs | 23 +++++++++++++++++++++++ apps/src/lib/client/rpc.rs | 15 +++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index b87cdb5c66..cadc215bd4 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -52,6 +52,9 @@ pub async fn main() -> Result<()> { Sub::QueryEpoch(QueryEpoch(args)) => { rpc::query_epoch(args).await; } + Sub::QueryBlock(QueryBlock(args)) => { + rpc::query_block(args).await; + } Sub::QueryBalance(QueryBalance(args)) => { rpc::query_balance(ctx, args).await; } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 8e4c7f78c9..f31d84b526 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -167,6 +167,7 @@ pub mod cmds { .subcommand(Withdraw::def().display_order(2)) // Queries .subcommand(QueryEpoch::def().display_order(3)) + .subcommand(QueryBlock::def().display_order(3)) .subcommand(QueryBalance::def().display_order(3)) .subcommand(QueryBonds::def().display_order(3)) .subcommand(QueryVotingPower::def().display_order(3)) @@ -198,6 +199,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_block = Self::parse_with_ctx(matches, QueryBlock); let query_balance = Self::parse_with_ctx(matches, QueryBalance); let query_bonds = Self::parse_with_ctx(matches, QueryBonds); let query_voting_power = @@ -224,6 +226,7 @@ pub mod cmds { .or(unbond) .or(withdraw) .or(query_epoch) + .or(query_block) .or(query_balance) .or(query_bonds) .or(query_voting_power) @@ -283,6 +286,7 @@ pub mod cmds { Unbond(Unbond), Withdraw(Withdraw), QueryEpoch(QueryEpoch), + QueryBlock(QueryBlock), QueryBalance(QueryBalance), QueryBonds(QueryBonds), QueryVotingPower(QueryVotingPower), @@ -936,6 +940,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryBlock(pub args::Query); + + impl SubCmd for QueryBlock { + const CMD: &'static str = "block"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| QueryBlock(args::Query::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Query the last committed block.") + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct QueryBalance(pub args::QueryBalance); diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 6c1e3fb5f3..7eb580d212 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -72,6 +72,21 @@ pub async fn query_epoch(args: args::Query) -> Epoch { cli::safe_exit(1) } +/// Query the last committed block +pub async fn query_block( + args: args::Query, +) -> tendermint_rpc::endpoint::block::Response { + let client = HttpClient::new(args.ledger_address).unwrap(); + let response = client.latest_block().await.unwrap(); + println!( + "Last committed block ID: {}, height: {}, time: {}", + response.block_id, + response.block.header.height, + response.block.header.time + ); + response +} + /// Query the raw bytes of given storage key pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { let client = HttpClient::new(args.query.ledger_address).unwrap(); From ca3608dd528443e6e3c93932aab9559dcb786a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 20 Oct 2022 15:26:48 +0200 Subject: [PATCH 02/38] changelog: add #658 --- .changelog/unreleased/features/658-add-client-block-query.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/658-add-client-block-query.md diff --git a/.changelog/unreleased/features/658-add-client-block-query.md b/.changelog/unreleased/features/658-add-client-block-query.md new file mode 100644 index 0000000000..29584d3a0f --- /dev/null +++ b/.changelog/unreleased/features/658-add-client-block-query.md @@ -0,0 +1,2 @@ +- Client: Add a command to query the last committed block's hash, height and + timestamp. ([#658](https://github.com/anoma/namada/issues/658)) From ea2d533065106118931eec9f740ca9477c7a65ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 20 Oct 2022 15:35:01 +0200 Subject: [PATCH 03/38] test/e2e/helpers: add a helper to query and parse block height --- tests/src/e2e/helpers.rs | 60 ++++++++++++++++++++++++++++++++++- tests/src/e2e/ledger_tests.rs | 51 +++++++++++++++++++++++++---- 2 files changed, 103 insertions(+), 8 deletions(-) diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 705c822760..d0dd60a688 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -3,6 +3,7 @@ use std::path::Path; use std::process::Command; use std::str::FromStr; +use std::time::{Duration, Instant}; use std::{env, time}; use color_eyre::eyre::Result; @@ -14,7 +15,7 @@ use namada::types::key::*; use namada::types::storage::Epoch; use namada_apps::config::{Config, TendermintMode}; -use super::setup::{Test, ENV_VAR_DEBUG, ENV_VAR_USE_PREBUILT_BINARIES}; +use super::setup::{sleep, Test, ENV_VAR_DEBUG, ENV_VAR_USE_PREBUILT_BINARIES}; use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; use crate::run; @@ -148,6 +149,63 @@ pub fn get_epoch(test: &Test, ledger_address: &str) -> Result { Ok(Epoch(epoch)) } +/// Get the last committed block height. +pub fn get_height(test: &Test, ledger_address: &str) -> Result { + let mut find = run!( + test, + Bin::Client, + &["block", "--ledger-address", ledger_address], + Some(10) + )?; + let (unread, matched) = find.exp_regex("Last committed block ID: .*")?; + // Expected `matched` string is e.g.: + // + // ``` + // Last committed block F10B5E77F972F68CA051D289474B6E75574B446BF713A7B7B71D7ECFC61A3B21, height: 4, time: 2022-10-20T10:52:28.828745Z + // ``` + let height_str = strip_trailing_newline(&matched) + .trim() + // Find the height part ... + .split_once("height: ") + .unwrap() + // ... take what's after it ... + .1 + // ... find the next comma ... + .rsplit_once(',') + .unwrap() + // ... and take what's before it. + .0; + u64::from_str(height_str).map_err(|e| { + eyre!(format!( + "Height parsing failed from {} trimmed from {}, Error: \ + {}\n\nUnread output: {}", + height_str, matched, e, unread + )) + }) +} + +/// Sleep until the given height is reached or panic when time out is reached +/// before the height +pub fn wait_for_block_height( + test: &Test, + ledger_address: &str, + height: u64, + timeout_secs: u64, +) -> Result<()> { + let start = Instant::now(); + let loop_timeout = Duration::new(timeout_secs, 0); + loop { + let current = get_height(test, ledger_address)?; + if current >= height { + break Ok(()); + } + if Instant::now().duration_since(start) > loop_timeout { + panic!("Timed out waiting for height {height}, current {current}"); + } + sleep(1); + } +} + pub fn generate_bin_command(bin_name: &str, manifest_path: &Path) -> Command { let use_prebuilt_binaries = match env::var(ENV_VAR_USE_PREBUILT_BINARIES) { Ok(var) => var.to_ascii_lowercase() != "false", diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index a998844764..c3e94b9238 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -23,6 +23,7 @@ use namada_apps::config::genesis::genesis_config::{ use serde_json::json; use setup::constants::*; +use super::helpers::{get_height, wait_for_block_height}; use super::setup::get_all_wasms_hashes; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, @@ -123,6 +124,16 @@ fn test_node_connectivity() -> Result<()> { let _bg_validator_0 = validator_0.background(); let _bg_validator_1 = validator_1.background(); + let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_1_rpc = get_actor_rpc(&test, &Who::Validator(1)); + let non_validator_rpc = get_actor_rpc(&test, &Who::NonValidator); + + // Find the block height on the validator + let after_tx_height = get_height(&test, &validator_0_rpc)?; + + // Wait for the non-validator to be synced to at least the same height + wait_for_block_height(&test, &non_validator_rpc, after_tx_height, 10)?; + let query_balance_args = |ledger_rpc| { vec![ "balance", @@ -134,10 +145,6 @@ fn test_node_connectivity() -> Result<()> { ledger_rpc, ] }; - - let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let validator_1_rpc = get_actor_rpc(&test, &Who::Validator(1)); - let non_validator_rpc = get_actor_rpc(&test, &Who::NonValidator); for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { let mut client = run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; @@ -1848,7 +1855,7 @@ fn test_genesis_validators() -> Result<()> { let bg_validator_0 = validator_0.background(); let bg_validator_1 = validator_1.background(); - let bg_non_validator = non_validator.background(); + let _bg_non_validator = non_validator.background(); // 4. Submit a valid token transfer tx let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -1879,12 +1886,42 @@ fn test_genesis_validators() -> Result<()> { // 3. Check that all the nodes processed the tx with the same result let mut validator_0 = bg_validator_0.foreground(); let mut validator_1 = bg_validator_1.foreground(); - let mut non_validator = bg_non_validator.foreground(); let expected_result = "all VPs accepted transaction"; + // We cannot check this on non-validator node as it might sync without + // applying the tx itself, but its state should be the same, checked below. validator_0.exp_string(expected_result)?; validator_1.exp_string(expected_result)?; - non_validator.exp_string(expected_result)?; + let _bg_validator_0 = validator_0.background(); + let _bg_validator_1 = validator_1.background(); + + let validator_0_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let validator_1_rpc = get_actor_rpc(&test, &Who::Validator(1)); + let non_validator_rpc = get_actor_rpc(&test, &Who::NonValidator); + + // Find the block height on the validator + let after_tx_height = get_height(&test, &validator_0_rpc)?; + + // Wait for the non-validator to be synced to at least the same height + wait_for_block_height(&test, &non_validator_rpc, after_tx_height, 10)?; + + let query_balance_args = |ledger_rpc| { + vec![ + "balance", + "--owner", + validator_1_alias, + "--token", + XAN, + "--ledger-address", + ledger_rpc, + ] + }; + for ledger_rpc in &[validator_0_rpc, validator_1_rpc, non_validator_rpc] { + let mut client = + run!(test, Bin::Client, query_balance_args(ledger_rpc), Some(40))?; + client.exp_string("XAN: 1000000000010.1")?; + client.assert_success(); + } Ok(()) } From 12853156a5120796d57412738a4d4d03fa78003f Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 24 Oct 2022 10:49:10 +0200 Subject: [PATCH 04/38] ci: invalide cf cache --- .github/workflows/build-and-test-bridge.yml | 2 +- .github/workflows/build-and-test.yml | 2 +- .github/workflows/docs.yml | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index cee43b941b..f82286f1d5 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -228,7 +228,7 @@ jobs: ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: 20 ANOMA_E2E_USE_PREBUILT_BINARIES: "true" ANOMA_E2E_KEEP_TEMP: "true" - ENV_VAR_TM_STDOUT: "false" + ANOMA_TM_STDOUT: "false" ANOMA_LOG_COLOR: "false" ANOMA_MASP_PARAMS_DIR: "/home/runner/work/masp" ANOMA_LOG: "info" diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1c4cbd3412..3ad569a2fb 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -230,7 +230,7 @@ jobs: ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: 20 ANOMA_E2E_USE_PREBUILT_BINARIES: "true" ANOMA_E2E_KEEP_TEMP: "true" - ENV_VAR_TM_STDOUT: "false" + ANOMA_TM_STDOUT: "false" ANOMA_LOG_COLOR: "false" ANOMA_MASP_PARAMS_DIR: "/home/runner/work/masp" ANOMA_LOG: "info" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0ba9f39b98..5304af902e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,18 +41,21 @@ jobs: command: cd documentation/specs && mdbook build cache_subkey: specs cache_version: v1 + distribution_id: E2Y9R2H4P5LYED - name: Build docs folder: documentation/docs bucket: namada-docs-static-website command: cd documentation/docs && mdbook build cache_subkey: docs cache_version: v1 + distribution_id: E2T9UML53913RV - name: Build development docs folder: documentation/dev bucket: namada-dev-static-website command: cargo run --bin namada_encoding_spec && cd documentation/dev && mdbook build cache_subkey: dev cache_version: v1 + distribution_id: E6XPP5KFWXJFQ env: CARGO_INCREMENTAL: 0 @@ -127,6 +130,9 @@ jobs: - name: Publish docs if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} run: aws s3 sync ${{ matrix.make.folder }}/book/html/ s3://${{ matrix.make.bucket }} --region eu-west-1 --delete + - name: Invalidate distribution cache + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + run: aws cloudfront create-invalidation --distribution-id ${{ matrix.make.distribution_id }} --paths "/*" - name: Print sccache stats if: always() run: sccache --show-stats From 0e57b7fdabdf577b9fe35b57ed4b6828434356d0 Mon Sep 17 00:00:00 2001 From: Abuzer Rafey Date: Mon, 15 Aug 2022 03:04:50 -0400 Subject: [PATCH 05/38] Modify specs --- documentation/specs/src/SUMMARY.md | 5 +- documentation/specs/src/base-ledger.md | 2 +- .../specs/src/base-ledger/core-concepts.md | 15 +++++ .../specs/src/base-ledger/execution.md | 15 +++-- .../specs/src/base-ledger/governance.md | 65 ++++++++++++++----- documentation/specs/src/economics.md | 2 +- .../specs/src/economics/proof-of-stake.md | 4 +- .../proof-of-stake/bonding-mechanism.md | 22 ++----- .../proof-of-stake/cubic-slashing.md | 14 +++- .../proof-of-stake/reward-distribution.md | 11 ---- .../src/economics/public-goods-funding.md | 2 +- documentation/specs/src/further-reading.md | 14 +++- .../src/interoperability/ethereum-bridge.md | 6 +- .../specs/src/interoperability/ibc.md | 12 ++-- documentation/specs/src/introduction.md | 41 +++++------- documentation/specs/src/masp.md | 3 +- documentation/specs/src/masp/asset-type.md | 8 +-- documentation/specs/src/masp/burn-and-mint.md | 2 +- .../specs/src/masp/ledger-integration.md | 64 +++++++++--------- documentation/specs/src/masp/trusted-setup.md | 3 +- .../user-interfaces/external-integrations.md | 2 +- .../user-interfaces/web-explorer-interface.md | 10 +-- .../user-interfaces/web-wallet-interface.md | 10 ++- .../specs/src/user-interfaces/web-wallet.md | 2 +- 24 files changed, 182 insertions(+), 152 deletions(-) create mode 100644 documentation/specs/src/base-ledger/core-concepts.md diff --git a/documentation/specs/src/SUMMARY.md b/documentation/specs/src/SUMMARY.md index 65e2de1ddf..49891b44e9 100644 --- a/documentation/specs/src/SUMMARY.md +++ b/documentation/specs/src/SUMMARY.md @@ -2,12 +2,9 @@ - [Introduction](./introduction.md) - [Base ledger](./base-ledger.md) - - [Consensus](./base-ledger/consensus.md) + - [Core Concepts](./base-ledger/core-concepts.md) - [Execution](./base-ledger/execution.md) - [Governance](./base-ledger/governance.md) - - [Default account](./base-ledger/default-account.md) - - [Multisignature account](./base-ledger/multisignature.md) - - [Fungible token](./base-ledger/fungible-token.md) - [Multi-asset shielded pool](./masp.md) - [Ledger integration](./masp/ledger-integration.md) - [Asset type](./masp/asset-type.md) diff --git a/documentation/specs/src/base-ledger.md b/documentation/specs/src/base-ledger.md index b345208e58..a848fa4eac 100644 --- a/documentation/specs/src/base-ledger.md +++ b/documentation/specs/src/base-ledger.md @@ -1,3 +1,3 @@ ## Base ledger -The base ledger of Namada includes a [consensus system](./base-ledger/consensus.md), validity predicate-based [execution system](./base-ledger/execution.md), and signalling-based [governance mechanism](./base-ledger/governance.md). Namada's ledger also includes proof-of-stake, slashing, fees, and inflation funding for staking rewards, shielded pool incentives, and public goods -- these are specified in the [economics section](./economics.md). \ No newline at end of file +The base ledger of Namada includes a [consensus system](./base-ledger/core-concepts.md), validity predicate-based [execution system](./base-ledger/execution.md), and signalling-based [governance mechanism](./base-ledger/governance.md). Namada's ledger also includes proof-of-stake, slashing, fees, and inflation funding for staking rewards, shielded pool incentives, and public goods -- these are specified in the [economics section](./economics.md) \ No newline at end of file diff --git a/documentation/specs/src/base-ledger/core-concepts.md b/documentation/specs/src/base-ledger/core-concepts.md new file mode 100644 index 0000000000..61214acb95 --- /dev/null +++ b/documentation/specs/src/base-ledger/core-concepts.md @@ -0,0 +1,15 @@ +# Consensus + +Namada uses [Tendermint Go](https://github.com/tendermint/tendermint) through the [tendermint-rs](https://github.com/heliaxdev/tendermint-rs) bindings in order to provide peer-to-peer transaction gossip, BFT consensus, and state machine replication for Namada's custom state machine. + +## Default account + +The default account validity predicate authorises transactions on the basis of a cryptographic signature. + +## Fungible token + +The fungible token validity predicate authorises token balance changes on the basis of conservation-of-supply and approval-by-sender. + +## k-of-n multisignature + +The k-of-n multisignature validity predicate authorises transactions on the basis of k out of n parties approving them. \ No newline at end of file diff --git a/documentation/specs/src/base-ledger/execution.md b/documentation/specs/src/base-ledger/execution.md index 3395ffb858..24b5fb992b 100644 --- a/documentation/specs/src/base-ledger/execution.md +++ b/documentation/specs/src/base-ledger/execution.md @@ -2,17 +2,20 @@ The Namada ledger execution system is based on an initial version of the [Anoma protocol](https://specs.anoma.net). The system implements a generic computational substrate with WASM-based transactions and validity predicate verification architecture, on top of which specific features of Namada such as IBC, proof-of-stake, and the MASP are built. +## Validity predicates + +Conceptually, a validity predicate (VP) is a function from the transaction's data and the storage state prior and posterior to a transaction execution returning a boolean value. A transaction may modify any data in the accounts' dynamic storage sub-space. Upon transaction execution, the VPs associated with the accounts whose storage has been modified are invoked to verify the transaction. If any of them reject the transaction, all of its storage modifications are discarded. ## Namada ledger -The Namada ledger is built on top of [Tendermint](https://docs.tendermint.com/master/spec/)'s [ABCI](https://docs.tendermint.com/master/spec/abci/) interface with a slight deviation from the ABCI convention: in Namada, the transactions are currently *not* being executed in ABCI's `DeliverTx` method, but rather in the `EndBlock` method. The reason for this is to prepare for future DKG and threshold decryption integration, which has not yet been fully finished and hence is out-of-scope for the initial release version of Namada. +The Namada ledger is built on top of [Tendermint](https://docs.tendermint.com/master/spec/)'s [ABCI](https://docs.tendermint.com/master/spec/abci/) interface with a slight deviation from the ABCI convention: in Namada, the transactions are currently *not* being executed in ABCI's [`DeliverTx` method](https://docs.tendermint.com/master/spec/abci/abci.html), but rather in the [`EndBlock` method](https://docs.tendermint.com/master/spec/abci/abci.html). The reason for this is to prepare for future DKG and threshold decryption integration. The ledger features an account-based system (in which UTXO-based systems such as the MASP can be internally implemented as specific accounts), where each account has a unique address and a dynamic key-value storage sub-space. Every account in Namada is associated with exactly one validity predicate. Fungible tokens, for example, are accounts, whose rules are governed by their validity predicates. Many of the base ledger subsystems specified here are themselves just special Namada accounts too (e.g. PoS, IBC and MASP). -Interaction with the Namada ledger are made possible via transactions (note [transaction whitelist](#transaction-and-validity-predicate-whitelist)). Please refer to the [protocol section](https://docs.anoma.network/master/specs/ledger.html#the-protocol) that specifies the transaction execution model. In Namada, transactions are allowed to perform arbitrary modifications to the storage of any account, but the transaction will be accepted and state changes applied only if all the validity predicates that were triggered by the transaction accept it. That is, the accounts whose storage sub-spaces were touched by the transaction and/or an account that was explicitly elected by the transaction as the verifier will all have their validity predicates verifying the transaction. A transaction can add any number of additional verifiers, but cannot remove the ones determined by the protocol. For example, a transparent fungible token transfer would typically trigger 3 validity predicates - those of the token, source and target addresses. +Interaction with the Namada ledger are made possible via transactions (note transaction whitelist below). In Namada, transactions are allowed to perform arbitrary modifications to the storage of any account, but the transaction will be accepted and state changes applied only if all the validity predicates that were triggered by the transaction accept it. That is, the accounts whose storage sub-spaces were touched by the transaction and/or an account that was explicitly elected by the transaction as the verifier will all have their validity predicates verifying the transaction. A transaction can add any number of additional verifiers, but cannot remove the ones determined by the protocol. For example, a transparent fungible token transfer would typically trigger 3 validity predicates - those of the token, source and target addresses. ## Supported validity predicates -Conceptually, a VP is a function from the transaction's data and the storage state prior and posterior to a transaction execution returning a boolean value. A transaction may modify any data in the accounts' dynamic storage sub-space. Upon transaction execution, the VPs associated with the accounts whose storage has been modified are invoked to verify the transaction. If any of them reject the transaction, all of its storage modifications are discarded. While the execution model is fully programmable, for Namada only a selected subset of provided validity predicates and transactions will be permitted through pre-defined whitelists configured at network launch. +While the execution model is fully programmable, for Namada only a selected subset of provided validity predicates and transactions will be permitted through pre-defined whitelists configured at network launch. There are some native VPs for internal transparent addresses that are built into the ledger. All the other VPs are implemented as WASM programs. One can build a custom VP using the [VP template](https://github.com/anoma/anoma/tree/master/wasm/vp_template) or use one of the pre-defined VPs. @@ -24,7 +27,7 @@ Supported validity predicates for Namada: - SlashFund (see [spec](./governance.md#SlashFundAddress)) - Protocol parameters - WASM - - Fungible token (see [spec](./fungible-token.md)) + - Fungible token (see [spec](./core-concepts.md)) - MASP (see [spec](../masp.md)) - - Implicit account VP (see [spec](./default-account.md)) - - k-of-n multisignature VP (see [spec](./multisignature.md)) + - Implicit account VP (see [spec](./core-concepts.md)) + - k-of-n multisignature VP (see [spec](./core-concepts.md)) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index 40c7281af9..7372a00539 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -1,11 +1,23 @@ # Namada Governance +Before describing Namada governance, it is useful to define the concepts of validators, delegators, and NAM. + +Namada's economic model is based around a single native token, NAM, which is controlled by the protocol. + +A Namada validator is an account with a public consensus key, which may participate in producing blocks and governance activities. A validator may not also be a delegator. + +A Namada delegator is an account that delegates some tokens to a validator. A delegator may not also be a validator. + Namada introduces a governance mechanism to propose and apply protocol changes with or without the need for a hard fork. Anyone holding some `NAM` will be able to propose some changes to which delegators and validators will cast their `yay` or `nay` votes. Governance on Namada supports both `signaling` and `voting` mechanisms. The difference between the the two is that the former is needed when the changes require a hard fork. In cases where the chain is not able to produce blocks anymore, Namada relies on [off chain](#off-chain-protocol) signaling to agree on a common move. +Further informtion regarding delegators, validators, and NAM is contained in the [economics section](../economics.md). + ## On-chain protocol ### Governance Address + Governance adds 2 internal addresses: + - `GovernanceAddress` - `SlashFundAddress` @@ -13,6 +25,7 @@ The first internal address contains all the proposals under its address space. The second internal address holds the funds of rejected proposals. ### Governance storage + Each proposal will be stored in a sub-key under the internal proposal address. The storage keys involved are: ``` @@ -26,6 +39,8 @@ Each proposal will be stored in a sub-key under the internal proposal address. T /\$GovernanceAddress/proposal/epoch/\$id: u64 ``` +An epoch is a range of blocks or time that is defined by the base ledger and made available to the PoS system. This document assumes that epochs are identified by consecutive natural numbers. All the data relevant to PoS are [associated with epochs](../economics/proof-of-stake/bonding-mechanism.md/#epoched-data). + - `Author` address field will be used to credit the locked funds if the proposal is approved. - `/\$GovernanceAddress/proposal/\$epoch/\$id` is used for easing the ledger governance execution. `\$epoch` refers to the same value as the on specific in the `grace_epoch` field. - The `content` value should follow a standard format. We leverage a similar format to what is described in the [BIP2](https://github.com/bitcoin/bips/blob/master/bip-0002.mediawiki#bip-format-and-structure) document: @@ -74,14 +89,16 @@ This is to leverage the `NAM` VP to check that the funds were correctly locked. The governance subkey, `/\$GovernanceAddress/proposal/\$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to Treasury. ### GovernanceAddress VP -Just like Pos, also governance has his own storage space. The `GovernanceAddress` validity predicate task is to check the integrity and correctness of new proposals. A proposal, to be correct, must satisfy the following: + +Just like PoS, also governance has its own storage space. The `GovernanceAddress` validity predicate task is to check the integrity and correctness of new proposals. A proposal, to be correct, must satisfy the following: + - Mandatory storage writes are: - - counter - - author - - funds - - voting_start epoch - - voting_end epoch - - grace_epoch + - counter + - author + - funds + - voting_start epoch + - voting_end epoch + - grace_epoch - Lock some funds >= `min_proposal_fund` - Contains a unique ID - Contains a start, end and grace Epoch @@ -89,8 +106,8 @@ Just like Pos, also governance has his own storage space. The `GovernanceAddress - Should contain a text describing the proposal with length < `max_proposal_content_size` characters. - Vote can be done only by a delegator or validator - Validator can vote only in the initial 2/3 of the whole proposal duration (`end_epoch` - `start_epoch`) -- Due to the previous requirement, the following must be true,`(EndEpoch - StartEpoch) % 3 == 0` -- If defined, `proposalCode` should be the wasm bytecode representation of +- Due to the previous requirement, the following must be true,`(EndEpoch - StartEpoch) % 3 == 0` +- If defined, `proposalCode` should be the wasm bytecode representation of the changes. This code is triggered in case the proposal has a position outcome. - The difference between `grace_epoch` and `end_epoch` should be of at least `min_proposal_grace_epochs` @@ -100,6 +117,7 @@ If `proposal_code` is `Empty` or `None` , the proposal upgrade will need to be It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/governance/mod.rs#L69). Example of `proposalCode` could be: + - storage writes to change some protocol parameter - storage writes to restore a slash - storage writes to change a non-native vp @@ -140,19 +158,20 @@ Vote transaction creates or modifies the following storage key: /\$GovernanceAddress/proposal/\$id/vote/\$delegation_address/\$voter_address: Enum(yay|nay) ``` -The storage key will only be created if the transaction is signed either by -a validator or a delegator. +The storage key will only be created if the transaction is signed either by +a validator or a delegator. Validators will be able to vote only for 2/3 of the total voting period, while delegators can vote until the end of the voting period. -If a delegator votes opposite to its validator, this will *override* the +If a delegator votes opposite to its validator, this will *override* the corresponding vote of this validator (e.g. if a delegator has a voting power of 200 and votes opposite to the delegator holding these tokens, than 200 will be subtracted from the voting power of the involved validator). As a small form of space/gas optimization, if a delegator votes accordingly to its validator, the vote will not actually be submitted to the chain. This logic is applied only if the following conditions are satisfied: - The transaction is not being forced -- The vote is submitted in the last third of the voting period (the one exclusive to delegators). This second condition is necessary to prevent a validator from changing its vote after a delegator vote has been submitted, effectively stealing the delegator's vote. +- The vote is submitted in the last third of the voting period (the one exclusive to delegators). This second condition is necessary to prevent a validator from changing its vote after a delegator vote has been submitted, effectively stealing the delegator's vote. ### Tally + At the beginning of each new epoch (and only then), in the `FinalizeBlock` event, tallying will occur for all the proposals ending at this epoch (specified via the `grace_epoch` field of the proposal). The proposal has a positive outcome if 2/3 of the staked `NAM` total is voting `yay`. Tallying is computed with the following rules: @@ -166,33 +185,39 @@ All the computation above must be made at the epoch specified in the `start_epoc It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/governance/utils.rs#L68). ### Refund and Proposal Execution mechanism -Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. For each ended proposal with a positive outcome, it will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). For each proposal that has been rejected, instead, the locked funds will be moved to the `SlashFundAddress`. Moreover, if the proposal had a positive outcome and `proposal_code` is defined, these changes will be executed right away. + +Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. A successful proposal will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). A failed proposal will move locked funds to `SlashFundAddress`. Moreover, if the proposal had a positive outcome and `proposal_code` is defined, these changes will be executed right away. To summarize the execution of governance in the `FinalizeBlock` event: If the proposal outcome is positive and current epoch is equal to the proposal `grace_epoch`, in the `FinalizeBlock` event: + - transfer the locked funds to the proposal `author` - execute any changes specified by `proposal_code` In case the proposal was rejected or if any error, in the `FinalizeBlock` event: -- transfer the locked funds to `SlashFundAddress` -The result is then signaled by creating and inserting a [`Tendermint Event`](https://github.com/tendermint/tendermint/blob/ab0835463f1f89dcadf83f9492e98d85583b0e71/docs/spec/abci/abci.md#events. +- transfer the locked funds to `SlashFundAddress` +The result is then signaled by creating and inserting a [`Tendermint Event`](https://github.com/tendermint/tendermint/blob/ab0835463f1f89dcadf83f9492e98d85583b0e71/docs/spec/abci/abci.md#events). ## SlashFundAddress + Funds locked in `SlashFundAddress` address should be spendable only by proposals. ### SlashFundAddress storage + ``` /\$SlashFundAddress/?: Vec ``` The funds will be stored under: + ``` /\$NAMAddress/balance/\$SlashFundAddress: u64 ``` ### SlashFundAddress VP + The slash_fund validity predicate will approve a transfer only if the transfer has been made by the protocol (by checking the existence of `/\$GovernanceAddress/pending/\$proposal_id` storage key) It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/main/shared/src/ledger/slash_fund/mod.rs#L70). @@ -200,8 +225,10 @@ It is possible to check the actual implementation [here](https://github.com/anom ## Off-chain protocol ### Create proposal -A CLI command to create a signed JSON representation of the proposal. The + +A CLI command to create a signed JSON representation of the proposal. The JSON will have the following structure: + ``` { content: Base64>, @@ -216,8 +243,9 @@ The signature is produced over the hash of the concatenation of: `content`, `aut ### Create vote -A CLI command to create a signed JSON representation of a vote. The JSON +A CLI command to create a signed JSON representation of a vote. The JSON will have the following structure: + ``` { proposalHash: Base64>, @@ -230,6 +258,7 @@ will have the following structure: The proposalHash is produced over the concatenation of: `content`, `author`, `votingStart`, `votingEnd`, `voter` and `vote`. ### Tally + Same mechanism as [on chain](#tally) tally but instead of reading the data from storage it will require a list of serialized json votes. ## Interfaces diff --git a/documentation/specs/src/economics.md b/documentation/specs/src/economics.md index bf7346b63a..f18c7d7023 100644 --- a/documentation/specs/src/economics.md +++ b/documentation/specs/src/economics.md @@ -1,3 +1,3 @@ ## Economics -Namada's economic model is based around a single native token, NAM, which is controlled by the protocol. Users pay transaction fees in NAM and other tokens (see [fee system](./economics/fee-system.md)), so demand for NAM can be expected to track demand for block space. On the supply side, the protocol mints NAM at a fixed maximum per-annum rate based on a fraction of the current supply (see [inflation system](./economics/inflation-system.md)), which is directed to three areas of protocol subsidy: [proof-of-stake](./economics/proof-of-stake.md), [shielded pool incentives](./economics/shielded-pool-incentives.md), and [public-goods funding](./economics/public-goods-funding.md). Inflation rates for these three areas are adjusted independently (the first two on PD controllers and the third based on funding decisions) and excess tokens are slowly burned. \ No newline at end of file +Namada users pay transaction fees in NAM and other tokens (see [fee system](./economics/fee-system.md) and [governance](./base-ledger/governance.md)), so demand for NAM can be expected to track demand for block space. On the supply side, the protocol mints NAM at a fixed maximum per-annum rate based on a fraction of the current supply (see [inflation system](./economics/inflation-system.md)), which is directed to three areas of protocol subsidy: [proof-of-stake](./economics/proof-of-stake.md), [shielded pool incentives](./economics/shielded-pool-incentives.md), and [public-goods funding](./economics/public-goods-funding.md). Inflation rates for these three areas are adjusted independently (the first two on PD controllers and the third based on funding decisions) and excess tokens are slowly burned. \ No newline at end of file diff --git a/documentation/specs/src/economics/proof-of-stake.md b/documentation/specs/src/economics/proof-of-stake.md index 808f02e585..3102616e65 100644 --- a/documentation/specs/src/economics/proof-of-stake.md +++ b/documentation/specs/src/economics/proof-of-stake.md @@ -4,7 +4,7 @@ This section of the specification describes the proof-of-stake mechanism of Nama This section is split into three subcomponents: the [bonding mechanism](./proof-of-stake/bonding-mechanism.md), [reward distribution](./proof-of-stake/reward-distribution.md), and [cubic slashing](./proof-of-stake/cubic-slashing.md). -## Introduction +## Context Blockchain systems rely on economic security (directly or indirectly) to prevent @@ -13,7 +13,7 @@ for actors to behave according to protocol. The aim is that economic incentives promote correct long-term operation of the system and economic punishments discourage diverging from correct protocol execution either by mistake or -with the intent of carrying out attacks. Many PoS blockcains rely on the 1/3 Byzantine rule, where they make the assumption the adversary cannot control more 2/3 of the total stake or 2/3 of the actors. +with the intent of carrying out attacks. Many PoS blockchains rely on the 1/3 Byzantine rule, where they make the assumption the adversary cannot control more 2/3 of the total stake or 2/3 of the actors. ## Goals of Rewards and Slashing: Liveness and Security diff --git a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md index ab0449e026..349e766def 100644 --- a/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md +++ b/documentation/specs/src/economics/proof-of-stake/bonding-mechanism.md @@ -1,9 +1,5 @@ # Bonding mechanism -## Epoch - -An epoch is a range of blocks or time that is defined by the base ledger and made available to the PoS system. This document assumes that epochs are identified by consecutive natural numbers. All the data relevant to PoS are [associated with epochs](#epoched-data). - ### Epoched data Epoched data is data associated with a specific epoch that is set in advance. @@ -16,16 +12,11 @@ The data relevant to the PoS system in the ledger's state are epoched. Each data Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + unboding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. -## Entities - -- [Validator](#validator): An account with a public consensus key, which may participate in producing blocks and governance activities. A validator may not also be a delegator. -- [Delegator](#delegator): An account that delegates some tokens to a validator. A delegator may not also be a validator. - Additionally, any account may submit evidence for [a slashable misbehaviour](#slashing). ### Validator -A validator must have a public consensus key. Additionally, it may also specify optional metadata fields (TBA). +A validator must have a public consensus key. A validator may be in one of the following states: - *inactive*: @@ -115,7 +106,7 @@ Once an offence has been reported: - [cubic slashing](./cubic-slashing.md): escalated slashing -Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. However, because slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unboding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). +Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. Slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`. For this reason, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unboding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). To disincentivize validators misbehaviour in the PoS system a validator may be slashed for any fault that it has done. An evidence of misbehaviour may be submitted by any account for a fault that occurred in epoch `n` anytime before the beginning of epoch `n + unbonding_length`. @@ -123,6 +114,11 @@ A valid evidence reduces the validator's total bonded token amount by the slash The invariant is that the sum of amounts that may be withdrawn from a misbehaving validator must always add up to the total bonded token amount. +## Initialization + +An initial validator set with self-bonded token amounts must be given on system initialization. + +This set is used to pre-compute epochs in the genesis block from epoch `0` to epoch `pipeline_length - 1`. ## System parameters @@ -296,8 +292,4 @@ struct Slash { } ``` -## Initialization - -An initial validator set with self-bonded token amounts must be given on system initialization. -This set is used to pre-compute epochs in the genesis block from epoch `0` to epoch `pipeline_length - 1`. diff --git a/documentation/specs/src/economics/proof-of-stake/cubic-slashing.md b/documentation/specs/src/economics/proof-of-stake/cubic-slashing.md index 4d4ac07523..d63347308e 100644 --- a/documentation/specs/src/economics/proof-of-stake/cubic-slashing.md +++ b/documentation/specs/src/economics/proof-of-stake/cubic-slashing.md @@ -31,4 +31,16 @@ calculateSlashRate slashes = Validator can later submit a transaction to unjail themselves after a configurable period. When the transaction is applied and accepted, the validator updates its state to "candidate" and is added back to the validator set starting at the epoch at pipeline offset (active or inactive, depending on its voting power). -At present, funds slashed are sent to the governance treasury. In the future we could potentially reward the slash discoverer with part of the slash, for which some sort of commit-reveal mechanism will be required to prevent front-running. +At present, funds slashed are sent to the governance treasury. + +## Slashes + +Slashes should lead to punishment for delegators who were contributing voting power to the validator at the height of the infraction, _as if_ the delegations were iterated over and slashed individually. + +This can be implemented as a negative inflation rate for a particular block. + +Instant redelegation is not supported. Redelegations must wait the unbonding period. + + \ No newline at end of file diff --git a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md index 70f662f97a..730e423e76 100644 --- a/documentation/specs/src/economics/proof-of-stake/reward-distribution.md +++ b/documentation/specs/src/economics/proof-of-stake/reward-distribution.md @@ -152,16 +152,5 @@ The commission rate $c_V(e)$ is the same for all delegations to a validator $V$ While rewards are given out at the end of every epoch, voting power is only updated after the pipeline offset. According to the [proof-of-stake system](bonding-mechanism.md#epoched-data), at the current epoch `e`, the validator sets an only be updated for epoch `e + pipeline_offset`, and it should remain unchanged from epoch `e` to `e + pipeline_offset - 1`. Updating voting power in the current epoch would violate this rule. -## Slashes - -Slashes should lead to punishment for delegators who were contributing voting power to the validator at the height of the infraction, _as if_ the delegations were iterated over and slashed individually. - -This can be implemented as a negative inflation rate for a particular block. - -Instant redelegation is not supported. Redelegations must wait the unbonding period. - - diff --git a/documentation/specs/src/economics/public-goods-funding.md b/documentation/specs/src/economics/public-goods-funding.md index 96e82dfc93..bfb779b1fa 100644 --- a/documentation/specs/src/economics/public-goods-funding.md +++ b/documentation/specs/src/economics/public-goods-funding.md @@ -10,7 +10,7 @@ There is a lot of existing research into public-goods funding to which justice c Namada instantiates a dual proactive/retroactive public-goods funding model, stewarded by a public-goods council elected by limited liquid democracy. -This proposal requires the following protocol components: +This requires the following protocol components: - Limited liquid democracy / targeted delegation: Namada's current voting mechanism is altered to add targeted delegation. By default, each delegator delegates their vote in governance to their validator, but they can set an alternative governance delegate who can instead vote on their behalf (but whose vote can be overridden as usual). Validators can also set governance delegates, in which case those delegates can vote on their behalf, and on the behalf of all delegators to that validator who do not override the vote, unless the validator overrides the vote. This is a limited form of liquid democracy which could be extended in the future. - Funding council: bi-annually (every six months), Namada governance elects a public goods funding council by stake-weighted approval vote (see below). Public goods funding councils run as groups. The public goods funding council decides according to internal decision-making procedures (practically probably limited to a k-of-n multisignature) how to allocate continuous funding and retroactive funding during their term. Namada genesis includes an initial funding council, and the next election will occur six months after launch. - Continuous funding: Namada prints an amount of inflation fixed on a percentage basis dedicated to continuous funding. Each quarter, the public goods funding council selects recipients and amounts (which in total must receive all of the funds, although they could burn some) and submits this list to the protocol. Inflation is distributed continuously by the protocol to these recipients during that quarter. diff --git a/documentation/specs/src/further-reading.md b/documentation/specs/src/further-reading.md index 971007c530..d92add791f 100644 --- a/documentation/specs/src/further-reading.md +++ b/documentation/specs/src/further-reading.md @@ -1,3 +1,15 @@ ## Further reading -Thanks for reading! You can find further information about the state of Namada at [namada.net](https://namada.net). \ No newline at end of file +Thanks for reading! You can find further information about the project below: + +[The state of Namada](https://namada.net). +[The state of Anoma](https://anoma.net/) +[Anoma source code](https://github.com/anoma/anoma) +[Namada source code](https://github.com/anoma/namada) +[Anoma Community](https://anoma.net/community) +[Heliax](https://heliax.dev/) +[Anoma Medium page](https://medium.com/anomanetwork) +[Namada Docs](https://docs.namada.net/) +[Anoma Discord](https://discord.com/invite/anoma) +[Namada Twitter](https://twitter.com/namadanetwork) +[Anoma Twitter](https://twitter.com/anomanetwork) \ No newline at end of file diff --git a/documentation/specs/src/interoperability/ethereum-bridge.md b/documentation/specs/src/interoperability/ethereum-bridge.md index cfc5ce6841..c6d52e3447 100644 --- a/documentation/specs/src/interoperability/ethereum-bridge.md +++ b/documentation/specs/src/interoperability/ethereum-bridge.md @@ -320,7 +320,7 @@ struct MintWrappedNam { ``` If a user wishes to mint a wrapped Namada token on Ethereum, they must submit a transaction on Namada that: -- stores `MintWrappedNam` on chain somewhere - TBD +- stores `MintWrappedNam` on chain somewhere - sends the correct amount of Namada token to `#EthBridgeEscrow` Just as in redeeming Ethereum assets above, it is incumbent on the end user to @@ -385,7 +385,7 @@ appropriate state change, emit logs, etc. ## Starting the bridge Before the bridge can start running, some storage may need to be initialized in -Namada. TBD. +Namada. ## Resources which may be helpful: - [Gravity Bridge Solidity contracts](https://github.com/Gravity-Bridge/Gravity-Bridge/tree/main/solidity) @@ -393,5 +393,3 @@ Namada. TBD. - [Rainbow Bridge contracts](https://github.com/aurora-is-near/rainbow-bridge/tree/master/contracts) - [IBC in Solidity](https://github.com/hyperledger-labs/yui-ibc-solidity) -Operational notes: -1. We will bundle the Ethereum full node with the `namada` daemon executable. diff --git a/documentation/specs/src/interoperability/ibc.md b/documentation/specs/src/interoperability/ibc.md index 198266dd93..0848243d33 100644 --- a/documentation/specs/src/interoperability/ibc.md +++ b/documentation/specs/src/interoperability/ibc.md @@ -6,29 +6,27 @@ ## IBC transaction An IBC transaction [`tx_ibc.wasm`](https://github.com/anoma/anoma/blob/fd4b7ab36929f47369ae82c82966891cb0ccc625/wasm/wasm_source/src/lib.rs#L224-L233) is provided. We have to set an IBC message to the transaction data corresponding to execute an IBC operation. -The transaction decodes the data to an IBC message and handles IBC-related data, e.g. it makes a new connection ID and writes a new connection end for `MsgConnectionOpenTry`. The operations are implemented in [`IbcActions`](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/handler/trait.IbcActions.html).The transaction doesn't check the validity for the state changes. IBC validity predicate is in charge of the validity. +The transaction decodes the data to an IBC message and handles IBC-related data, e.g. it makes a new connection ID and writes a new connection end for `MsgConnectionOpenTry`. The operations are implemented in [`IbcActions`](https://github.com/anoma/anoma/blob/50b5e77f04a9afc036656353335bd232fcdba8a7/vm_env/src/ibc.rs).The transaction doesn't check the validity for the state changes. IBC validity predicate is in charge of the validity. ## IBC validity predicate -[IBC validity predicate](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/vp/struct.Ibc.html#impl-NativeVp) checks if an IBC-related transaction satisfies IBC protocol. When an IBC-related transaction is executed, i.e. a transaction changes the state of the key that contains [`InternalAddress::Ibc`](https://docs.anoma.network/master/rustdoc/anoma/types/address/enum.InternalAddress.html#variant.Ibc), IBC validity predicate (one of the native validity predicates) is executed. For example, if an IBC connection end is created in the transaction, IBC validity predicate validates the creation. If the creation with `MsgConnectionOpenTry` is invalid, e.g. the counterpart connection end doesn't exist, the validity predicate makes the transaction fail. +[IBC validity predicate](https://docs.anoma.network/master/rustdoc/anoma/ledger/ibc/vp/struct.Ibc.html#impl-NativeVp) checks if an IBC-related transaction satisfies IBC protocol. When an IBC-related transaction is executed, i.e. a transaction changes the state of the key that contains [`InternalAddress::Ibc`](https://github.com/anoma/anoma/blob/50b5e77f04a9afc036656353335bd232fcdba8a7/shared/src/types/address.rs), IBC validity predicate (one of the native validity predicates) is executed. For example, if an IBC connection end is created in the transaction, IBC validity predicate validates the creation. If the creation with `MsgConnectionOpenTry` is invalid, e.g. the counterpart connection end doesn't exist, the validity predicate makes the transaction fail. ## Fungible Token Transfer The transfer of fungible tokens over an IBC channel on separate chains is defined in [ICS20](https://github.com/cosmos/ibc/blob/master/spec/app/ics-020-fungible-token-transfer/README.md). -In Anoma, the sending tokens is triggered by a transaction having [MsgTransfer](https://github.com/informalsystems/ibc-rs/blob/0a952b295dbcf67bcabb79ce57ce92c9c8d7e5c6/modules/src/applications/ics20_fungible_token_transfer/msgs/transfer.rs#L20-L37) as transaction data. A packet including [`FungibleTokenPacketData`](https://docs.anoma.network/master/rustdoc/anoma/types/ibc/data/struct.FungibleTokenPacketData.html) is made from the message in the transaction execution. +In Anoma, the sending tokens is triggered by a transaction having [MsgTransfer](https://github.com/informalsystems/ibc-rs/blob/0a952b295dbcf67bcabb79ce57ce92c9c8d7e5c6/modules/src/applications/ics20_fungible_token_transfer/msgs/transfer.rs#L20-L37) as transaction data. A packet including [`FungibleTokenPacketData`](https://github.com/anoma/anoma/blob/50b5e77f04a9afc036656353335bd232fcdba8a7/shared/src/types/ibc/data.rs) is made from the message in the transaction execution. Anoma chain receives the tokens by a transaction having [MsgRecvPacket](https://github.com/informalsystems/ibc-rs/blob/0a952b295dbcf67bcabb79ce57ce92c9c8d7e5c6/modules/src/core/ics04_channel/msgs/recv_packet.rs#L19-L23) which has the packet including `FungibleTokenPacketData`. The sending and receiving tokens in a transaction are validated by not only -IBC validity predicate but also [IBC token validity predicate](https://docs. -anoma.network/master/rustdoc/anoma/ledger/ibc/vp/struct.IbcToken. -html#impl-NativeVp). IBC validity predicate validates if sending and receiving the packet is proper. IBC token validity predicate is also one of the native validity predicates and checks if the token transfer is valid. If the transfer is not valid, e.g. an unexpected amount is minted, the validity predicate makes the transaction fail. +IBC validity predicate but also [IBC token validity predicate](https://github.com/anoma/anoma/blob/50b5e77f04a9afc036656353335bd232fcdba8a7/shared/src/ledger/ibc/vp/token.rs). IBC validity predicate validates if sending and receiving the packet is proper. IBC token validity predicate is also one of the native validity predicates and checks if the token transfer is valid. If the transfer is not valid, e.g. an unexpected amount is minted, the validity predicate makes the transaction fail. A transaction escrowing/unescrowing a token changes the escrow account's balance of the token. The key is `{token_addr}/balance/{escrow_addr}`. A transaction burning a token changes the burn account's balance of the token. The key is `{token_addr}/balance/BURN_ADDR`. A transaction minting a token changes the mint account's balance of the token. The key is `{token_addr} -/balance/MINT_ADDR`. `{escrow_addr}`, `{BURN_ADDR}`, and `{MINT_ADDR}` are addresses of [`InternalAddress`](https://docs.anoma.network/master/rustdoc/anoma/types/address/enum.InternalAddress.html). When these addresses are included in the changed keys after transaction execution, IBC token validity predicate is executed. +/balance/MINT_ADDR`. `{escrow_addr}`, `{BURN_ADDR}`, and `{MINT_ADDR}` are addresses of [`InternalAddress`](https://github.com/anoma/anoma/blob/50b5e77f04a9afc036656353335bd232fcdba8a7/shared/src/types/address.rs). When these addresses are included in the changed keys after transaction execution, IBC token validity predicate is executed. ## IBC message diff --git a/documentation/specs/src/introduction.md b/documentation/specs/src/introduction.md index 6f4971e049..0d4f83fd33 100644 --- a/documentation/specs/src/introduction.md +++ b/documentation/specs/src/introduction.md @@ -2,31 +2,28 @@ Welcome to the Namada specifications! +## What is Namada? + Namada is a sovereign proof-of-stake blockchain, using Tendermint BFT consensus, that enables multi-asset private transfers for any native or non-native asset -using a multi-asset shielded pool derived from the Sapling circuit. Namada features -full IBC protocol support, a natively integrated Ethereum bridge, a modern proof-of-stake -system with automatic reward compounding and cubic slashing, a stake-weighted governance -signalling mechanism, and a proactive/retroactive public goods funding system. -Users of shielded transfers are rewarded for their contributions -to the privacy set in the form of native protocol tokens. A multi-asset shielded transfer wallet -is provided in order to facilitate safe and private user interaction with the protocol. +using a [multi-asset shielded pool](https://research.metastate.dev/multi-asset_shielded_pool/) derived from the [Sapling circuit](https://z.cash/upgrade/sapling/). Namada features full IBC protocol support, a natively integrated Ethereum bridge, a modern proof-of-stakesystem with automatic reward compounding and cubic slashing, a stake-weighted governance signalling mechanism, and a proactive/retroactive public goods funding system. Users of shielded transfers are rewarded for their contributions to the privacy set in the form of native protocol tokens. A multi-asset shielded transfer wallet is provided in order to facilitate safe and private user interaction with the protocol. + +You can learn more about Namada [here](https://medium.com/anomanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c). +### What is Anoma? -### How does Namada relate to Anoma? +The Anoma protocol is designed to facilitate the operation of networked fractal instances, which intercommunicate but can utilise varied state machines and security models. +A fractal instance is an instance of the Anoma consensus and execution protocols operated by a set of networked validators. Anoma’s fractal instance architecture is an attempt to build a platform which is architecturally homogeneous and with a heterogeneous security model. Thus, different fractal instances may specialise in different tasks and serve different communities. Privacy should be default and inherent in the systems we use for transacting. -Namada is the first fractal instance launched as part of the Anoma ecosystem. +### How does Namada relate to Anoma? -The Anoma protocol is designed to facilitate the operation of networked fractal instances, -which intercommunicate but can utilise varied state machines and security models. Different -fractal instances may specialise in different tasks and serve different communities. The Namada -instance will be the first such fractal instance, and it will be focused exclusively on the use-case of private asset transfers. +The Namada instance will be the first such fractal instance, and it will be focused exclusively on the use-case of private asset transfers. Namada is a helpful stepping stone to finalise, test, and launch a protocol version that is simpler than the full +Anoma protocol but still encapsulates a unified and useful set of features. ### Raison d'être -Safe and user-friendly multi-asset privacy doesn't yet exist in the blockchain ecosystem. -Up until now users have had the choice of either a sovereign chain that reissues assets (e.g. Zcash) -or a privacy preserving solution built on an existing smart contract chain (e.g. Tornado Cash on -Ethereum). Both have large trade-offs: in the former case, users don't have +Privacy should be default and inherent in the systems we use for transacting. Yet safe and user-friendly multi-asset privacy doesn't yet exist in the blockchain ecosystem. +Up until now users have had the choice of either a sovereign chain that reissues assets (e.g. [Zcash](https://z.cash/)) +or a privacy preserving solution built on an existing smart contract chain. Both have large trade-offs: in the former case, users don't have assets that they actually want to transact with, and in the latter case, the restrictions of existing platforms mean that users leak a ton of metadata and the protocols are expensive and clunky to use. @@ -36,15 +33,7 @@ and fungible or non-fungible assets (such as ERC20 tokens) sent over a custom Et reduces transfer costs and streamlines UX as much as possible. Once assets are on Namada, shielded transfers are cheap and all assets contribute to the same anonymity set. -Namada is also a helpful stepping stone to finalise, test, -and launch a protocol version that is simpler than the full -Anoma protocol but still encapsulates a unified and useful -set of features. There are reasons to expect that it may -make sense for a fractal instance focused exclusively on -shielded transfers to exist in the long-term, as it can -provide throughput and user-friendliness guarantees which -are more difficult to provide with a more general platform. -Namada is designed to be such an instance. +Users on Namada can earn rewards, retain privacy of assets, and contribute to the overall privacy set. ### Layout of this specification diff --git a/documentation/specs/src/masp.md b/documentation/specs/src/masp.md index 4e1fadc087..d1a87e9b8b 100644 --- a/documentation/specs/src/masp.md +++ b/documentation/specs/src/masp.md @@ -7,4 +7,5 @@ See the following documents: - [Ledger integration](./masp/ledger-integration.md) - [Asset type schema](./masp/asset-type.md) - [Burn and mint](./masp/burn-and-mint.md) -- [Convert circuit](./masp/convert-circuit.md) \ No newline at end of file +- [Convert circuit](./masp/convert-circuit.md) +- [Shielded pool incentives](./economics/shielded-pool-incentives.md) \ No newline at end of file diff --git a/documentation/specs/src/masp/asset-type.md b/documentation/specs/src/masp/asset-type.md index 7504c7ef0f..bc6c701698 100644 --- a/documentation/specs/src/masp/asset-type.md +++ b/documentation/specs/src/masp/asset-type.md @@ -2,22 +2,22 @@ MASP notes carry balances that are some positive integer amount of an asset type. Per both the MASP specification and the implementation, the -asset *identifier* is an 32-byte Blake2s hash of an arbitrary asset +asset *identifier* is an 32-byte [Blake2s hash](https://www.blake2.net/) of an arbitrary asset *name* string, although the full 32-byte space is not used because the identifier must itself hash to an elliptic curve point (currently guaranteed by incrementing a nonce until the hash is a curve point). The final curve point is the asset *type* proper, used in computations. The following is a schema for the arbitrary asset name string intended -to support various uses; at least fungible tokens and NFTs, but possibly -others. +to support various uses - currently fungible tokens and NFTs, but possibly +others in future. The asset name string is built up from a number of segments, joined by a separator. We use `/` as the separator. Segments may be one of the following: -- **Controlling address** segment: an Anoma address which controls the +- **Controlling address** segment: a Namada address which controls the asset. For example, this is the fungible token address for a fungible token. This segment must be present, and must be first; it should in theory be an error to transparently transact in assets of this type diff --git a/documentation/specs/src/masp/burn-and-mint.md b/documentation/specs/src/masp/burn-and-mint.md index f66b8e28eb..2c9bb6e3b5 100644 --- a/documentation/specs/src/masp/burn-and-mint.md +++ b/documentation/specs/src/masp/burn-and-mint.md @@ -62,7 +62,7 @@ It is also critical not to allow cycles. For example, if $\{(A_1, -1), (A_2, 2)\ It may theoretically be possible to implement similar mechanisms with only the existing Spend and Output circuits. For example, a Merkle tree of many Notes could be created with asset generator $[-1] vb_1 + vb_2$ and many different values, allowing anyone to Spend these public Notes, which will only balance if proper amounts of asset type 1 are Spent and asset type 2 are Output. -However, the Nullifier integrity check of the Spend circuit reveals the nullifier of each of these Notes, which removes the privacy of the conversion as the public nullifier is linkable to the allowed conversion. In addition, each Note has a fixed value, preventing arbitrary value conversions. +However, the Nullifier integrity check of the Spend circuit reveals the nullifier of each of these Notes. This removes the privacy of the conversion as the public nullifier is linkable to the allowed conversion. In addition, each Note has a fixed value, preventing arbitrary value conversions. ## Conclusion diff --git a/documentation/specs/src/masp/ledger-integration.md b/documentation/specs/src/masp/ledger-integration.md index 0eed1d7d80..0f4f0cabd8 100644 --- a/documentation/specs/src/masp/ledger-integration.md +++ b/documentation/specs/src/masp/ledger-integration.md @@ -4,32 +4,31 @@ The overall aim of this integration is to have the ability to provide a multi-asset shielded pool following the MASP spec as an account on the -current Anoma blockchain implementation. +current Namada blockchain implementation. -## Shielded pool VP +## Shielded pool validity predicate (VP) -The shielded value pool can be an Anoma "established account" with a +The shielded value pool can be an Namada established account with a validity predicate which handles the verification of shielded transactions. Similarly to zcash, the asset balance of the shielded pool itself is transparent - that is, from the transparent perspective, the MASP is just an account holding assets. The shielded pool VP has the following functions: -- Accept only valid transactions involving assets moving in or out of +- Accepts only valid transactions involving assets moving in or out of the pool. -- Accept valid shielded-to-shielded transactions, which don't move - assets from the perspective of transparent Anoma. -- Publish the note commitment and nullifier reveal Merkle trees. +- Accepts valid shielded-to-shielded transactions, which don't move + assets from the perspective of transparent Namada. +- Publishes the note commitment and nullifier reveal Merkle trees. To make this possible, the host environment needs to provide verification primitives to VPs. One possibility is to provide a single -high-level "verify transaction output descriptions and proofs" -operation, but another is to provide cryptographic functions in the host +high-level operation to verify transaction output descriptions and proofs, but another is to provide cryptographic functions in the host environment and implement the verifier as part of the VP. -The shielded pool needs the ability to update the commitment and -nullifier Merkle trees as it receives transactions. This may possibly be -achievable via the temporary storage mechanism added for IBC, with the +In future, the shielded pool will be able to update the commitment and +nullifier Merkle trees as it receives transactions. This could likely be +achieved via the temporary storage mechanism added for IBC, with the trees finalized with each block. The input to the VP is the following set of state changes: @@ -37,7 +36,7 @@ The input to the VP is the following set of state changes: - updates to the shielded pool's asset balances - new encrypted notes - updated note and nullifier tree states (partial, because we only have - the last block's anchor?) + the last block's anchor) and the following data which is ancillary from the ledger's perspective: @@ -71,7 +70,7 @@ struct OutputDescription { c_enc: [u8; ENC_CIPHERTEXT_SIZE], // Encrypted note key recovery ciphertext c_out: [u8; OUT_CIPHERTEXT_SIZE], - // Zero-knowledge proof of the new encrypted note's location (?) + // Zero-knowledge proof of the new encrypted note's location zkproof: Proof, } ``` @@ -79,7 +78,7 @@ struct OutputDescription { Given these inputs: The VP must verify the proofs for all spend and output descriptions -(`bellman::groth16`), as well as the signature for spend notes. +([`bellman::groth16`](https://docs.rs/bellman/latest/bellman/groth16/index.html)), as well as the signature for spend notes. Encrypted notes from output descriptions must be published in the storage so that holders of the viewing key can view them; however, the @@ -89,15 +88,14 @@ Nullifiers and commitments must be appended to their respective Merkle trees in the VP's storage as well, which is a transaction-level rather than a block-level state update. -Additionally to the individual spend and output description +In addition to the individual spend and output description verifications, the final transparent asset value change described in the -transaction must equal the pool asset value change, and as an additional -sanity check, the pool's balance of any asset may not end up negative -(this may already be impossible?). (needs more input) +transaction must equal the pool asset value change. As an additional +sanity check, the pool's balance of any asset may not end up negative. NB: Shielded-to-shielded transactions in an asset do not, from the ledger's perspective, transact in that asset; therefore, the asset's own -VP is not run as described above, and cannot be because the shielded +VP cannot run as described above because the shielded pool is asset-hiding. ## Client capabilities @@ -116,7 +114,7 @@ if any, and proof data computed offline by the client. The client and wallet must be extended to support the shielded pool and the cryptographic operations needed to interact with it. From the -perspective of the transparent Anoma protocol, a shielded transaction is +perspective of the transparent Namada protocol, a shielded transaction is just a data write to the MASP storage, unless it moves value in or out of the pool. The client needs the capability to create notes, transactions, and proofs of transactions, but it has the advantage of @@ -148,7 +146,7 @@ For cryptographic details and further information, see Note that this structure is required only by the client; the VP only handles commitments to this data. -Diversifiers are selected (randomly?) by the client and used to +Diversifiers are selected by the client and used to diversify addresses and their associated keys. `v` and `t` identify the asset type and value. Asset identifiers are derived from asset names, which are arbitrary strings (in this case, token/other asset VP @@ -217,7 +215,7 @@ struct TxOut { ``` Note that in contrast to Sapling's UTXO based approach, our transparent inputs/outputs are based on the account model used -in the rest of Anoma. +in the rest of Namada. # Shielded Transfer Specification ## Transfer Format @@ -242,17 +240,17 @@ pub struct Transfer { ``` ## Conditions Below, the conditions necessary for a valid shielded or unshielded transfer are outlined: -* A shielded component equal to `None` indicates a transparent Anoma transaction -* Otherwise the shielded component must have the form `Some(x)` where `x` has the transaction encoding specified in the [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) +* A shielded component equal to `None` indicates a transparent Namada transaction +* Otherwise the shielded component must have the form `Some(x)` where `x` has the transaction encoding specified in the [Multi-Asset Shielded Pool Specs]() * Hence for a shielded transaction to be valid: - * the `Transfer` must satisfy the usual conditions for Anoma ledger transfers (i.e. sufficient funds, ...) as enforced by token and account validity predicates - * the `Transaction` must satisfy the conditions specified in the [Multi-Asset Shielded Pool Specication](https://raw.githubusercontent.com/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) - * the `Transaction` and `Transfer` together must additionaly satisfy the below boundary conditions intended to ensure consistency between the MASP validity predicate ledger and Anoma ledger + * the `Transfer` must satisfy the usual conditions for Namada ledger transfers (i.e. sufficient funds, ...) as enforced by token and account validity predicates + * the `Transaction` must satisfy the conditions specified in the [Multi-Asset Shielded Pool Specification](https://github.com/anoma/masp/blob/main/docs/multi-asset-shielded-pool.pdf) + * the `Transaction` and `Transfer` together must additionally satisfy the below boundary conditions intended to ensure consistency between the MASP validity predicate ledger and Namada ledger * A key equal to `None` indicates an unpinned shielded transaction; one that can only be found by scanning and trial-decrypting the entire shielded pool * Otherwise the key must have the form `Some(x)` where `x` is a `String` such that there exists no prior accepted transaction with the same key ### Boundary Conditions -Below, the conditions necessary to maintain consistency between the MASP validity predicate ledger and Anoma ledger are outlined: +Below, the conditions necessary to maintain consistency between the MASP validity predicate ledger and Namada ledger are outlined: * If the target address is the MASP validity predicate, then no transparent outputs are permitted in the shielded transaction * If the target address is not the MASP validity predicate, then: * there must be exactly one transparent output in the shielded transaction and: @@ -287,7 +285,7 @@ Below are miscellaneous remarks on the capabilities and limitations of the curre * This key must not be reused, this is in order to avoid revealing that multiple transactions are going to the same entity ## Multi-Asset Shielded Pool Specification Differences from Zcash Protocol Specification -The [Multi-Asset Shielded Pool Specication](https://media.githubusercontent.com/media/anoma/masp/main/docs/multi-asset-shielded-pool.pdf) referenced above is in turn an extension to the [Zcash Protocol Specification](https://zips.z.cash/protocol/protocol.pdf). Below, the changes from the Zcash Protocol Specification assumed to have been integrated into the Multi-Asset Shielded Pool Specification are listed: +The [Multi-Asset Shielded Pool Specification](https://github.com/anoma/masp/blob/main/docs/multi-asset-shielded-pool.pdf) referenced above is in turn an extension to the [Zcash Protocol Specification](https://zips.z.cash/protocol/protocol.pdf). Below, the changes from the Zcash Protocol Specification assumed to have been integrated into the Multi-Asset Shielded Pool Specification are listed: * [3.2 Notes](https://zips.z.cash/protocol/protocol.pdf#notes) * Sapling note tuple must include asset type * Note commitment must be parameterized by asset type @@ -391,7 +389,7 @@ Below, the changes from [ZIP 32: Shielded Hierarchical Deterministic Wallets](ht * For extended full viewing keys on the Testnet, the Human-Readable Part is "xfvktest" # Storage Interface Specification -Anoma nodes provide interfaces that allow Anoma clients to query for specific pinned transactions, transactions accepted into the shielded pool, and allowed conversions between various asset types. Below we describe the ABCI paths and the encodings of the responses to each type of query. +Namada nodes provide interfaces that allow Namada clients to query for specific pinned transactions, transactions accepted into the shielded pool, and allowed conversions between various asset types. Below we describe the ABCI paths and the encodings of the responses to each type of query. ## Shielded Transfer Query In order to determine shielded balances belonging to particular keys or spend one's balance, it is necessary to download the transactions that transferred the assets to you. To this end, the nth transaction in the shielded pool can be obtained by getting the value at the storage path `/tx-`. Note that indexing is 0-based. This will return a quadruple of the type below: @@ -414,7 +412,7 @@ When scanning the shielded pool, it is sometimes useful know when to stop scanni ## Pinned Transfer Query A transaction pinned to the key `x` in the shielded pool can be obtained indirectly by getting the value at the storage path `/pin-`. This will return the index of the desired transaction within the shielded pool encoded as a `u64`. At this point, the above shielded transaction query can then be used to obtain the actual transaction bytes. ## Conversion Query -In order for MASP clients to convert older asset types to their latest variants, they need to query nodes for currently valid conversions. This can be done by querying the ABCI path `conv/` where `asset-type` is a hexadecimal encoding of the asset identifier as defined in [Multi-Asset Shielded Pool Specication](https://media.githubusercontent.com/media/anoma/masp/main/docs/multi-asset-shielded-pool.pdf). This will return a quadruple of the type below: +In order for MASP clients to convert older asset types to their latest variants, they need to query nodes for currently valid conversions. This can be done by querying the ABCI path `conv/` where `asset-type` is a hexadecimal encoding of the asset identifier as defined in [Multi-Asset Shielded Pool Specification](https://github.com/anoma/masp/blob/main/docs/multi-asset-shielded-pool.pdf). This will return a quadruple of the type below: ``` ( /// the token address of this asset type @@ -427,4 +425,4 @@ In order for MASP clients to convert older asset types to their latest variants, MerklePath ) ``` -If no conversions are available the amount will be exactly zero, otherwise the amount must contain negative units of the queried asset type. +If no conversions are available, the amount will be exactly zero, otherwise the amount must contain negative units of the queried asset type. diff --git a/documentation/specs/src/masp/trusted-setup.md b/documentation/specs/src/masp/trusted-setup.md index acc14f723d..328a7fe231 100644 --- a/documentation/specs/src/masp/trusted-setup.md +++ b/documentation/specs/src/masp/trusted-setup.md @@ -1,8 +1,7 @@ # Namada Trusted Setup This spec assumes that you have some previous knowledge about Trusted Setup Ceremonies. If not, you might want to check the following two articles: [Setup Ceremonies - ZKProof](https://zkproof.org/2021/06/30/setup-ceremonies/) and [Parameter Generation - Zcash](https://z.cash/technology/paramgen/). -The Namada Trusted Setup (TS) consists of running the phase 2 of the MPC which is a circuit-specific step to construct the multi-asset shielded pool circuit. Our phase 2 takes as input the Powers of Tau (phase 1) ran by Zcash that can be found [here](https://download.z.cash/downloads/powersoftau/). - +The Namada Trusted Setup (TS) consists of running the phase 2 of the MPC which is a circuit-specific step to construct the multi-asset shielded pool circuit. Our phase 2 takes as input the Powers of Tau (phase 1) ran by Zcash that can be found [here](https://download.z.cash/downloads/powersoftau/). You can sign up for the Namada Trusted Setup [here](https://namada.net/trusted-setup.html). ## Contribution flow diff --git a/documentation/specs/src/user-interfaces/external-integrations.md b/documentation/specs/src/user-interfaces/external-integrations.md index f6fd4855b6..c459d52499 100644 --- a/documentation/specs/src/user-interfaces/external-integrations.md +++ b/documentation/specs/src/user-interfaces/external-integrations.md @@ -10,7 +10,7 @@ * Rosetta integration * Datahub integration * WalletConnect integration -* Ledger integration +* [Ledger integration](../masp/ledger-integration.md) * External integrations * Figment * P2P diff --git a/documentation/specs/src/user-interfaces/web-explorer-interface.md b/documentation/specs/src/user-interfaces/web-explorer-interface.md index 9c6225d674..c7dbfa3c80 100644 --- a/documentation/specs/src/user-interfaces/web-explorer-interface.md +++ b/documentation/specs/src/user-interfaces/web-explorer-interface.md @@ -1,9 +1,9 @@ # Web explorer interface * Block explorer - * Display PoS state - * Display governance state - * Display transparent transfers - * Display transfers in and out of the MASP - * Display total values for the MASP + * Displays PoS state + * Displays governance state + * Displays transparent transfers + * Displays transfers in and out of the MASP + * Displays total values for the MASP * Allows tx hashes of shielded transfers to be looked up for confirmation diff --git a/documentation/specs/src/user-interfaces/web-wallet-interface.md b/documentation/specs/src/user-interfaces/web-wallet-interface.md index 615a74178f..d34628ef6a 100644 --- a/documentation/specs/src/user-interfaces/web-wallet-interface.md +++ b/documentation/specs/src/user-interfaces/web-wallet-interface.md @@ -2,7 +2,7 @@ ## Application Features -The application consist of the an UI that allows the user to perform the following actions in an easy to use and consistent web application: +The application consists of a UI that allows the user to perform the following actions in an easy to use and consistent web application: ### Seed Phrase [hifi Designs](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=4610%3A5890) @@ -16,9 +16,9 @@ The application consist of the an UI that allows the user to perform the followi * When entering the app, the user is being prompted for a password to decrypt the key pair to be able to perform actions [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5801) * Can create accounts derived from the master key pair * Can delete accounts -* User can integrated with Ledger hardware wallet - * Set up flow TBD - * Managing TBD +* User can integrate with Ledger hardware wallet + * Set up + * Management * Can see an overview of the assets in the account and all derived accounts [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5492) * Can see the details of a single asset, containing the following information [wireframe](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6442%3A5681) * Balance @@ -26,7 +26,6 @@ The application consist of the an UI that allows the user to perform the followi * Button to initiate a new transfer using this asset ### Transfers -[TBD]() * Can create transparent transfers * Can create shielded transfers * Bi-directional transfer between Namada and ETH @@ -35,7 +34,6 @@ The application consist of the an UI that allows the user to perform the followi * Supports approving transactions with Keplr ### Staking & Governance -[TBD]() * Can bond funds to a list of validators * Can un-bond funds to a list of validators * Can submit proposals diff --git a/documentation/specs/src/user-interfaces/web-wallet.md b/documentation/specs/src/user-interfaces/web-wallet.md index 370a0a5397..2196400f2f 100644 --- a/documentation/specs/src/user-interfaces/web-wallet.md +++ b/documentation/specs/src/user-interfaces/web-wallet.md @@ -142,7 +142,7 @@ User can: [Wireframe 2](https://www.figma.com/file/aiWZpaXjPLW6fDjE7dpFaU/Projects-2021?node-id=6496%3A14405) User can: view 1: -* enter the details (TBD) of the proposal +* enter the details of the proposal * see a summary of the proposal * submit the proposal From 0bd55bd266ae4a6ce7e96a51325e3efab801ae02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 21 Oct 2022 17:54:26 +0200 Subject: [PATCH 06/38] split back out the core VPs --- documentation/specs/src/SUMMARY.md | 3 +++ documentation/specs/src/base-ledger/core-concepts.md | 12 ------------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/documentation/specs/src/SUMMARY.md b/documentation/specs/src/SUMMARY.md index 49891b44e9..b7537eb315 100644 --- a/documentation/specs/src/SUMMARY.md +++ b/documentation/specs/src/SUMMARY.md @@ -5,6 +5,9 @@ - [Core Concepts](./base-ledger/core-concepts.md) - [Execution](./base-ledger/execution.md) - [Governance](./base-ledger/governance.md) + - [Default account](./base-ledger/default-account.md) + - [Multisignature account](./base-ledger/multisignature.md) + - [Fungible token](./base-ledger/fungible-token.md) - [Multi-asset shielded pool](./masp.md) - [Ledger integration](./masp/ledger-integration.md) - [Asset type](./masp/asset-type.md) diff --git a/documentation/specs/src/base-ledger/core-concepts.md b/documentation/specs/src/base-ledger/core-concepts.md index 61214acb95..083946f0b1 100644 --- a/documentation/specs/src/base-ledger/core-concepts.md +++ b/documentation/specs/src/base-ledger/core-concepts.md @@ -1,15 +1,3 @@ # Consensus Namada uses [Tendermint Go](https://github.com/tendermint/tendermint) through the [tendermint-rs](https://github.com/heliaxdev/tendermint-rs) bindings in order to provide peer-to-peer transaction gossip, BFT consensus, and state machine replication for Namada's custom state machine. - -## Default account - -The default account validity predicate authorises transactions on the basis of a cryptographic signature. - -## Fungible token - -The fungible token validity predicate authorises token balance changes on the basis of conservation-of-supply and approval-by-sender. - -## k-of-n multisignature - -The k-of-n multisignature validity predicate authorises transactions on the basis of k out of n parties approving them. \ No newline at end of file From 5db5320a17628e93883d9ba5e50bd75a956cb7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 21 Oct 2022 17:58:06 +0200 Subject: [PATCH 07/38] fix broken link --- documentation/specs/src/base-ledger/governance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index 7372a00539..3079b6e6dc 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -39,7 +39,7 @@ Each proposal will be stored in a sub-key under the internal proposal address. T /\$GovernanceAddress/proposal/epoch/\$id: u64 ``` -An epoch is a range of blocks or time that is defined by the base ledger and made available to the PoS system. This document assumes that epochs are identified by consecutive natural numbers. All the data relevant to PoS are [associated with epochs](../economics/proof-of-stake/bonding-mechanism.md/#epoched-data). +An epoch is a range of blocks or time that is defined by the base ledger and made available to the PoS system. This document assumes that epochs are identified by consecutive natural numbers. All the data relevant to PoS are [associated with epochs](../economics/proof-of-stake/bonding-mechanism.md#epoched-data). - `Author` address field will be used to credit the locked funds if the proposal is approved. - `/\$GovernanceAddress/proposal/\$epoch/\$id` is used for easing the ledger governance execution. `\$epoch` refers to the same value as the on specific in the `grace_epoch` field. From fa559612ad7ac0c76c4f1949e578584e9e40dfeb Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Tue, 25 Oct 2022 13:05:08 +0200 Subject: [PATCH 08/38] ci: use mold linker --- .github/workflows/build-and-test-bridge.yml | 4 ++++ .github/workflows/build-and-test.yml | 4 ++++ .github/workflows/release.yml | 2 ++ 3 files changed, 10 insertions(+) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index f82286f1d5..5a6d574184 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -177,6 +177,8 @@ jobs: restore-keys: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo- - name: Start sccache server run: sccache --start-server + - name: Install mold linker + uses: rui314/setup-mold@v1 - name: Build run: make build${{ matrix.make.suffix }} - name: Build test @@ -316,6 +318,8 @@ jobs: restore-keys: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo- - name: Start sccache server run: sccache --start-server + - name: Install mold linker + uses: rui314/setup-mold@v1 - name: Build run: make build-release${{ matrix.make.suffix }} - name: Upload target binaries diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3ad569a2fb..a6dfebde45 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -179,6 +179,8 @@ jobs: restore-keys: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo- - name: Start sccache server run: sccache --start-server + - name: Install mold linker + uses: rui314/setup-mold@v1 - name: Build run: make build${{ matrix.make.suffix }} - name: Build test @@ -316,6 +318,8 @@ jobs: ~/.cargo/git key: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo- + - name: Install mold linker + uses: rui314/setup-mold@v1 - name: Start sccache server run: sccache --start-server - name: Build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5cbc1e9207..e65b1fe459 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,6 +74,8 @@ jobs: restore-keys: ${{ runner.os }}-anoma-release-${{ matrix.anoma_cache_version }} - name: Start sccache server run: sccache --start-server + - name: Install mold linker + uses: rui314/setup-mold@v1 - name: ${{ matrix.make.name }} run: make ${{ matrix.make.command }} - name: Upload binaries package From 89fe10afd1bb437a45f4a214e6d83f64447a86a1 Mon Sep 17 00:00:00 2001 From: Awa Sun Yin <11296013+awasunyin@users.noreply.github.com> Date: Wed, 26 Oct 2022 11:46:22 +0200 Subject: [PATCH 09/38] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6888f89e87..b96cb34229 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ ## Overview -[Namada](http://namada.net) is a sovereign proof-of-stake blockchain, using Tendermint BFT -consensus, that enables multi-asset shielded transfers for any native +[Namada](http://namada.net) is a Proof-of-Stake L1 for interchain asset-agnostic privacy. Namada uses Tendermint BFT +consensus and enables multi-asset shielded transfers for any native or non-native asset. Namada features full IBC protocol support, a natively integrated Ethereum bridge, a modern proof-of-stake system with automatic reward compounding and cubic slashing, and a From 97c5d4dcaa74580df612a3b9aceb2f5e7ead9c10 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 26 Oct 2022 14:03:59 +0200 Subject: [PATCH 10/38] ci: fix mold usage --- .github/workflows/build-and-test-bridge.yml | 21 ++++++++++++++++++--- .github/workflows/build-and-test.yml | 19 +++++++++++++++++-- .github/workflows/release.yml | 2 -- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 5a6d574184..15bcfdea1d 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -109,6 +109,7 @@ jobs: matrix: os: [ubuntu-latest] nightly_version: [nightly-2022-05-20] + mold_version: [1.6.0] make: - name: ABCI suffix: '' @@ -178,11 +179,17 @@ jobs: - name: Start sccache server run: sccache --start-server - name: Install mold linker - uses: rui314/setup-mold@v1 + run: | + wget -q -O- https://github.com/rui314/mold/releases/download/v${{ matrix.mold_version }}/mold-${{ matrix.mold_version }}-x86_64-linux.tar.gz | tar -xz + mv mold-${{ matrix.mold_version }}-x86_64-linux/bin/mold /usr/local/bin - name: Build run: make build${{ matrix.make.suffix }} + env: + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Build test run: make build-test${{ matrix.make.suffix }} + env: + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Download wasm artifacts uses: actions/download-artifact@v3 with: @@ -190,6 +197,8 @@ jobs: path: ./wasm - name: Run unit test run: make test-unit${{ matrix.make.suffix }} + env: + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Wait for release binaries uses: lewagon/wait-on-check-action@master with: @@ -234,6 +243,7 @@ jobs: ANOMA_LOG_COLOR: "false" ANOMA_MASP_PARAMS_DIR: "/home/runner/work/masp" ANOMA_LOG: "info" + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Upload e2e logs if: success() || failure() uses: actions/upload-artifact@v3 @@ -242,7 +252,7 @@ jobs: path: | /tmp/.*/logs/ /tmp/.*/e2e-test.*/setup/validator-*/.anoma/logs/*.log - retention-days: 5 + retention-days: 3 - name: Print sccache stats if: always() run: sccache --show-stats @@ -257,6 +267,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] + mold_version: [1.6.0] make: - name: ABCI Release build suffix: '' @@ -319,9 +330,13 @@ jobs: - name: Start sccache server run: sccache --start-server - name: Install mold linker - uses: rui314/setup-mold@v1 + run: | + wget -q -O- https://github.com/rui314/mold/releases/download/v${{ matrix.mold_version }}/mold-${{ matrix.mold_version }}-x86_64-linux.tar.gz | tar -xz + mv mold-${{ matrix.mold_version }}-x86_64-linux/bin/mold /usr/local/bin - name: Build run: make build-release${{ matrix.make.suffix }} + env: + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Upload target binaries uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a6dfebde45..5396e17915 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -111,6 +111,7 @@ jobs: matrix: os: [ubuntu-latest] nightly_version: [nightly-2022-05-20] + mold_version: [1.6.0] make: - name: ABCI suffix: '' @@ -180,11 +181,17 @@ jobs: - name: Start sccache server run: sccache --start-server - name: Install mold linker - uses: rui314/setup-mold@v1 + run: | + wget -q -O- https://github.com/rui314/mold/releases/download/v${{ matrix.mold_version }}/mold-${{ matrix.mold_version }}-x86_64-linux.tar.gz | tar -xz + mv mold-${{ matrix.mold_version }}-x86_64-linux/bin/mold /usr/local/bin - name: Build run: make build${{ matrix.make.suffix }} + env: + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Build test run: make build-test${{ matrix.make.suffix }} + env: + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Download wasm artifacts uses: actions/download-artifact@v3 with: @@ -192,6 +199,8 @@ jobs: path: ./wasm - name: Run unit test run: make test-unit${{ matrix.make.suffix }} + env: + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Wait for release binaries uses: lewagon/wait-on-check-action@master with: @@ -236,6 +245,7 @@ jobs: ANOMA_LOG_COLOR: "false" ANOMA_MASP_PARAMS_DIR: "/home/runner/work/masp" ANOMA_LOG: "info" + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Upload e2e logs if: success() || failure() uses: actions/upload-artifact@v3 @@ -259,6 +269,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] + mold_version: [1.6.0] make: - name: ABCI Release build suffix: '' @@ -319,11 +330,15 @@ jobs: key: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: ${{ runner.os }}-${{ matrix.make.cache_key }}-${{ matrix.make.cache_version }}-cargo- - name: Install mold linker - uses: rui314/setup-mold@v1 + run: | + wget -q -O- https://github.com/rui314/mold/releases/download/v${{ matrix.mold_version }}/mold-${{ matrix.mold_version }}-x86_64-linux.tar.gz | tar -xz + mv mold-${{ matrix.mold_version }}-x86_64-linux/bin/mold /usr/local/bin - name: Start sccache server run: sccache --start-server - name: Build run: make build-release${{ matrix.make.suffix }} + env: + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Upload target binaries uses: actions/upload-artifact@v3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e65b1fe459..5cbc1e9207 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,8 +74,6 @@ jobs: restore-keys: ${{ runner.os }}-anoma-release-${{ matrix.anoma_cache_version }} - name: Start sccache server run: sccache --start-server - - name: Install mold linker - uses: rui314/setup-mold@v1 - name: ${{ matrix.make.name }} run: make ${{ matrix.make.command }} - name: Upload binaries package From b3b261602883d8fd5c950acd5b9c1367956f3f07 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 26 Oct 2022 15:15:00 +0200 Subject: [PATCH 11/38] ci: fix workflow name --- .github/workflows/build-and-test-bridge.yml | 4 ++-- .github/workflows/build-and-test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 15bcfdea1d..0ed0437ce4 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -115,7 +115,7 @@ jobs: suffix: '' cache_key: anoma cache_version: v1 - wait_for: anoma-release-eth (ubuntu-latest, ABCI Release build, anoma-e2e-release, v1) + wait_for: anoma-release-eth (ubuntu-latest, 1.6.0, ABCI Release build, anoma-e2e-release, v1) tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 env: @@ -200,7 +200,7 @@ jobs: env: RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Wait for release binaries - uses: lewagon/wait-on-check-action@master + uses: lewagon/wait-on-check-action@@v1.2.0 with: ref: ${{ github.event.pull_request.head.sha || github.ref }} check-name: ${{ matrix.make.wait_for }} diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5396e17915..6ee13b06f3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -117,7 +117,7 @@ jobs: suffix: '' cache_key: anoma cache_version: v1 - wait_for: anoma-release (ubuntu-latest, ABCI Release build, anoma-e2e-release, v1) + wait_for: anoma-release (ubuntu-latest, 1.6.0, ABCI Release build, anoma-e2e-release, v1) tendermint_artifact: tendermint-unreleased-ad825dcadbd4b98c3f91ce5a711e4fb36a69c377 env: @@ -202,7 +202,7 @@ jobs: env: RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=/usr/local/bin/mold" - name: Wait for release binaries - uses: lewagon/wait-on-check-action@master + uses: lewagon/wait-on-check-action@v1.2.0 with: ref: ${{ github.event.pull_request.head.sha || github.ref }} check-name: ${{ matrix.make.wait_for }} From 3952f18014651f963110131fc0051da3e8e3d085 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 30 Aug 2022 20:29:47 +0300 Subject: [PATCH 12/38] replace floating point arithm from token module with rust_decimal --- proof_of_stake/src/parameters.rs | 5 ++++- shared/src/types/token.rs | 29 ++++++++++++----------------- tests/src/native_vp/pos.rs | 18 +++++++++++------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 7ee0abdf98..1c265bf1a8 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -71,11 +71,14 @@ pub enum ValidationError { UnbondingLenTooShort(u64, u64), } +/// The number of fundamental units per whole token of the native staking token +pub const TOKENS_PER_NAM: u64 = 1_000_000; + /// From Tendermint: const MAX_TOTAL_VOTING_POWER: i64 = i64::MAX / 8; /// Assuming token amount is `u64` in micro units. -const TOKEN_MAX_AMOUNT: u64 = u64::MAX / 1_000_000; +const TOKEN_MAX_AMOUNT: u64 = u64::MAX / TOKENS_PER_NAM; impl PosParams { /// Validate PoS parameters values. Returns an empty list if the values are diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 787a8855dc..b19642b85a 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -6,6 +6,7 @@ use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::prelude::{Decimal, ToPrimitive}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -37,7 +38,6 @@ pub struct Amount { pub const MAX_DECIMAL_PLACES: u32 = 6; /// Decimal scale of token [`Amount`] and [`Change`]. pub const SCALE: u64 = 1_000_000; -const SCALE_F64: f64 = SCALE as f64; /// A change in tokens amount pub type Change = i128; @@ -109,21 +109,16 @@ impl<'de> serde::Deserialize<'de> for Amount { } } -impl From for f64 { - /// Warning: `f64` loses precision and it should not be used when exact - /// values are required. +impl From for Decimal { fn from(amount: Amount) -> Self { - amount.micro as f64 / SCALE_F64 + Into::::into(amount.micro) / Into::::into(SCALE) } } -impl From for Amount { - /// Warning: `f64` loses precision and it should not be used when exact - /// values are required. - fn from(micro: f64) -> Self { - Self { - micro: (micro * SCALE_F64).round() as u64, - } +impl From for Amount { + fn from(micro: Decimal) -> Self { + let res = (micro * Into::::into(SCALE)).to_u64().unwrap(); + Self { micro: res } } } @@ -205,7 +200,7 @@ impl FromStr for Amount { match rust_decimal::Decimal::from_str(s) { Ok(decimal) => { let scale = decimal.scale(); - if scale > 6 { + if scale > MAX_DECIMAL_PLACES { return Err(AmountParseError::ScaleTooLarge(scale)); } let whole = @@ -440,11 +435,11 @@ mod tests { /// The upper limit is set to `2^51`, because then the float is /// starting to lose precision. #[test] - fn test_token_amount_f64_conversion(raw_amount in 0..2_u64.pow(51)) { + fn test_token_amount_decimal_conversion(raw_amount in 0..2_u64.pow(51)) { let amount = Amount::from(raw_amount); - // A round-trip conversion to and from f64 should be an identity - let float = f64::from(amount); - let identity = Amount::from(float); + // A round-trip conversion to and from Decimal should be an identity + let decimal = Decimal::from(amount); + let identity = Amount::from(decimal); assert_eq!(amount, identity); } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index ec6f75a15f..61b859a7d6 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -594,6 +594,10 @@ pub mod testing { use crate::tx::{self, tx_host_env}; + const TOKENS_PER_NAM: i128 = + namada::ledger::pos::namada_proof_of_stake::parameters::TOKENS_PER_NAM + as i128; + #[derive(Clone, Debug, Default)] pub struct TestValidator { pub address: Option
, @@ -940,9 +944,9 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = - params.votes_per_token * ((total_delta) / 1_000_000); + params.votes_per_token * (total_delta / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta + token_delta) / 1_000_000); + * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; @@ -1001,12 +1005,12 @@ pub mod testing { let total_delta = validator_total_deltas .get(epoch) .unwrap_or_default(); - // We convert the tokens from micro units to whole + // We convert the tokens from micro units to whole // tokens with division by 10^6 let vp_before = params.votes_per_token - * ((total_delta) / 1_000_000); + * (total_delta / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta + token_delta) / 1_000_000); + * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta_at_unbonding = vp_after - vp_before - vp_delta - total_vp_delta; @@ -1080,9 +1084,9 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = params.votes_per_token - * ((total_delta_cur) / 1_000_000); + * (total_delta_cur / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta_cur + token_delta) / 1_000_000); + * ((total_delta_cur + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; From 5fdc1853acb4bfeea9f1833176b8b8bb14613a0c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Oct 2022 09:22:00 +0000 Subject: [PATCH 13/38] [ci] wasm checksums update --- wasm/checksums.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 01140354ba..5dfd713ff7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.38c037a51f9215c2be9c1b01f647251ffdc96a02a0c958c5d3db4ee36ccde43b.wasm", - "tx_ibc.wasm": "tx_ibc.5f86477029d987073ebfec66019dc991b0bb8b80717d4885b860f910916cbcdd.wasm", - "tx_init_account.wasm": "tx_init_account.8d901bce15d1ab63a591def00421183a651d4d5e09ace4291bf0a9044692741d.wasm", - "tx_init_nft.wasm": "tx_init_nft.1991808f44c1c24d4376a3d46b602bed27575f6c0359095c53f37b9225050ffc.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.716cd08d59b26bd75815511f03e141e6ac27bc0b7d7be10a71b04559244722c2.wasm", - "tx_init_validator.wasm": "tx_init_validator.611edff2746f71cdaa7547a84a96676b555821f00af8375a28f8dab7ae9fc9fa.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.3f20f1a86da43cc475ccc127428944bd177d40fbe2d2d1588c6fadd069cbe4b2.wasm", - "tx_transfer.wasm": "tx_transfer.5653340103a32e6685f9668ec24855f65ae17bcc43035c2559a13f5c47bb67af.wasm", - "tx_unbond.wasm": "tx_unbond.71e66ac6f792123a2aaafd60b3892d74a7d0e7a03c3ea34f15fea9089010b810.wasm", + "tx_bond.wasm": "tx_bond.059b1256240d15a64cf419f3a2d24af1496211c386d85acc3733095bc2e5da9b.wasm", + "tx_ibc.wasm": "tx_ibc.4eeb30bc1e6a32b8efe8958eab568082a238db01eb98340f28a9fa41371a3753.wasm", + "tx_init_account.wasm": "tx_init_account.85d017ac76e51f359fa07e753c0e6fcbd3341e7661492cbf2801cf3c41480dd4.wasm", + "tx_init_nft.wasm": "tx_init_nft.fbeb1687a364b2c249c9fd69588ff0985bd9c1f8f4c93e5328de1e9ba527d991.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.43b52414649bb0fec86dfb35d001656c1825bc5906e450e8b0c3a60aaa5f3d45.wasm", + "tx_init_validator.wasm": "tx_init_validator.6ccb7fcf246cb7a2f97a5dfdcefe16ee1add72a832081c2572adc2d7a355cf56.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.f2ed21521c676f04be4c6278cc60bec83265c3750437c87d9ea681b830767d71.wasm", + "tx_transfer.wasm": "tx_transfer.b6fc342f4a76918874e6d037a3864e4369dbba7cd7d558622e7a723e3d854da3.wasm", + "tx_unbond.wasm": "tx_unbond.6e7316d08bf8ab9a6fb1889f64a5a2265ee0399661dbb48e33555170545d1c7c.wasm", "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ff3def7b4bb0c46635bd6d544ac1745362757ce063feb8142d2ed9ab207f2a12.wasm", - "tx_withdraw.wasm": "tx_withdraw.ba1a743cf8914a353d7706777e0b1a37e20cd271b16e022fd3b50ad28971291f.wasm", - "vp_nft.wasm": "vp_nft.4471284b5c5f3e28c973f0a2ad2dde52ebe4a1dcd5dc15e93b380706fd0e35ea.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7d7eb09cddc7ae348417da623e21ec4a4f8c78f15ae12de5abe7087eeab1e0db.wasm", - "vp_token.wasm": "vp_token.4a5436f7519de15c80103557add57e8d06e766e1ec1f7a642ffca252be01c5d0.wasm", - "vp_user.wasm": "vp_user.729b18aab60e8ae09b75b5f067658f30459a5ccfcd34f909b88da96523681019.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.d0a87d58f64f46586ae3d83852deee269e22520f4780875c05aaf1731044c356.wasm", + "tx_withdraw.wasm": "tx_withdraw.e5dcc5ef2362018c1fa5ea02912528ee929aa7b6fefcf06f4ccf7509bfa52283.wasm", + "vp_nft.wasm": "vp_nft.9be5a821bc7b3075b917e8ead45893502d82cc7417e6af50dfd3f6baf36243e0.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8e2e45ff165d40dc8249188aca108e5cba86ac5dd1cd989b0237cadd4b66bfdf.wasm", + "vp_token.wasm": "vp_token.4a0446f20e7436de1e889c640a11644d1a1295c4d29e45b24582df2b9ed3176e.wasm", + "vp_user.wasm": "vp_user.eb1d6f1f524c28571ad0f21f75371aa635257313cea2702b9a70e5022fe6c3ef.wasm" } \ No newline at end of file From 87303114fd19d67d77e4a66eebd04c5e0884c670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 17:07:19 +0200 Subject: [PATCH 14/38] changelog: #436 --- .changelog/unreleased/improvements/436-remove-f64.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/436-remove-f64.md diff --git a/.changelog/unreleased/improvements/436-remove-f64.md b/.changelog/unreleased/improvements/436-remove-f64.md new file mode 100644 index 0000000000..e55af7ee8f --- /dev/null +++ b/.changelog/unreleased/improvements/436-remove-f64.md @@ -0,0 +1,2 @@ +- Refactored token decimal formatting. + ([#436](https://github.com/anoma/namada/pull/436)) \ No newline at end of file From 282ff308e5f60e85866ccd4651cdab03428c8ca3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 25 Oct 2022 19:57:07 -0400 Subject: [PATCH 15/38] remove staking reward address from all code --- apps/src/lib/cli.rs | 17 ------- apps/src/lib/client/tx.rs | 32 ------------ apps/src/lib/client/utils.rs | 27 ----------- apps/src/lib/config/genesis.rs | 52 +------------------- proof_of_stake/src/lib.rs | 59 ----------------------- proof_of_stake/src/types.rs | 5 -- proof_of_stake/src/validation.rs | 57 ++-------------------- shared/src/ledger/pos/mod.rs | 11 ----- shared/src/ledger/pos/storage.rs | 52 -------------------- shared/src/ledger/pos/vp.rs | 21 +------- shared/src/types/transaction/mod.rs | 6 --- tests/src/e2e/ledger_tests.rs | 1 - tests/src/native_vp/pos.rs | 23 +-------- tx_prelude/src/proof_of_stake.rs | 25 ++-------- wasm/wasm_source/src/tx_bond.rs | 4 -- wasm/wasm_source/src/tx_init_validator.rs | 8 +-- wasm/wasm_source/src/tx_unbond.rs | 4 -- wasm/wasm_source/src/tx_withdraw.rs | 4 -- 18 files changed, 13 insertions(+), 395 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f31d84b526..53d408322f 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1351,8 +1351,6 @@ pub mod args { const RAW_ADDRESS: Arg
= arg("address"); const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); - const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); - const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); const SIGNER: ArgOpt = arg_opt("signer"); @@ -1590,10 +1588,8 @@ pub mod args { pub scheme: SchemeType, pub account_key: Option, pub consensus_key: Option, - pub rewards_account_key: Option, pub protocol_key: Option, pub validator_vp_code_path: Option, - pub rewards_vp_code_path: Option, pub unsafe_dont_encrypt: bool, } @@ -1604,10 +1600,8 @@ pub mod args { let scheme = SCHEME.parse(matches); let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); - let rewards_account_key = REWARDS_KEY.parse(matches); let protocol_key = PROTOCOL_KEY.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); - let rewards_vp_code_path = REWARDS_CODE_PATH.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { tx, @@ -1615,10 +1609,8 @@ pub mod args { scheme, account_key, consensus_key, - rewards_account_key, protocol_key, validator_vp_code_path, - rewards_vp_code_path, unsafe_dont_encrypt, } } @@ -1640,10 +1632,6 @@ pub mod args { "A consensus key for the validator account. A new one \ will be generated if none given.", )) - .arg(REWARDS_KEY.def().about( - "A public key for the staking reward account. A new one \ - will be generated if none given.", - )) .arg(PROTOCOL_KEY.def().about( "A public key for signing protocol transactions. A new \ one will be generated if none given.", @@ -1653,11 +1641,6 @@ pub mod args { for the validator account. Uses the default validator VP \ if none specified.", )) - .arg(REWARDS_CODE_PATH.def().about( - "The path to the validity predicate WASM code to be used \ - for the staking reward account. Uses the default staking \ - reward VP if none specified.", - )) .arg(UNSAFE_DONT_ENCRYPT.def().about( "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index e749a681c6..93e7e4f3bd 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -159,10 +159,8 @@ pub async fn submit_init_validator( scheme, account_key, consensus_key, - rewards_account_key, protocol_key, validator_vp_code_path, - rewards_vp_code_path, unsafe_dont_encrypt, }: args::TxInitValidator, ) { @@ -174,7 +172,6 @@ pub async fn submit_init_validator( let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = format!("{}-consensus-key", alias); - let rewards_key_alias = format!("{}-rewards-key", alias); let account_key = ctx.get_opt_cached(&account_key).unwrap_or_else(|| { println!("Generating validator account key..."); ctx.wallet @@ -208,18 +205,6 @@ pub async fn submit_init_validator( .1 }); - let rewards_account_key = - ctx.get_opt_cached(&rewards_account_key).unwrap_or_else(|| { - println!("Generating staking reward account key..."); - ctx.wallet - .gen_key( - scheme, - Some(rewards_key_alias.clone()), - unsafe_dont_encrypt, - ) - .1 - .ref_to() - }); let protocol_key = ctx.get_opt_cached(&protocol_key); if protocol_key.is_none() { @@ -250,30 +235,14 @@ pub async fn submit_init_validator( safe_exit(1) } } - let rewards_vp_code = rewards_vp_code_path - .map(|path| ctx.read_wasm(path)) - .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); - // Validate the rewards VP code - if let Err(err) = vm::validate_untrusted_wasm(&rewards_vp_code) { - eprintln!( - "Staking reward account validity predicate code validation failed \ - with {}", - err - ); - if !tx_args.force { - safe_exit(1) - } - } let tx_code = ctx.read_wasm(TX_INIT_VALIDATOR_WASM); let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), - rewards_account_key, protocol_key, dkg_key, validator_vp_code, - rewards_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); @@ -369,7 +338,6 @@ pub async fn submit_init_validator( println!(" Staking reward address \"{}\"", rewards_address_alias); println!(" Validator account key \"{}\"", validator_key_alias); println!(" Consensus key \"{}\"", consensus_key_alias); - println!(" Staking reward key \"{}\"", rewards_key_alias); println!( "The ledger node has been setup to use this validator's address \ and consensus key." diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 8848726792..2c0f54dd91 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -475,10 +475,7 @@ pub fn init_network( // Generate account and reward addresses let address = address::gen_established_address("validator account"); - let reward_address = - address::gen_established_address("validator reward account"); config.address = Some(address.to_string()); - config.staking_reward_address = Some(reward_address.to_string()); // Generate the consensus, account and reward keys, unless they're // pre-defined. @@ -518,24 +515,6 @@ pub fn init_network( keypair.ref_to() }); - let staking_reward_pk = try_parse_public_key( - format!("validator {name} staking reward key"), - &config.staking_reward_public_key, - ) - .unwrap_or_else(|| { - let alias = format!("{}-reward-key", name); - println!( - "Generating validator {} staking reward account key...", - name - ); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - unsafe_dont_encrypt, - ); - keypair.ref_to() - }); - let protocol_pk = try_parse_public_key( format!("validator {name} protocol key"), &config.protocol_public_key, @@ -583,8 +562,6 @@ pub fn init_network( Some(genesis_config::HexString(consensus_pk.to_string())); config.account_public_key = Some(genesis_config::HexString(account_pk.to_string())); - config.staking_reward_public_key = - Some(genesis_config::HexString(staking_reward_pk.to_string())); config.protocol_public_key = Some(genesis_config::HexString(protocol_pk.to_string())); @@ -593,7 +570,6 @@ pub fn init_network( // Write keypairs to wallet wallet.add_address(name.clone(), address); - wallet.add_address(format!("{}-reward", &name), reward_address); wallet.save().unwrap(); }); @@ -940,9 +916,6 @@ pub fn init_genesis_validator( account_public_key: Some(HexString( pre_genesis.account_key.ref_to().to_string(), )), - staking_reward_public_key: Some(HexString( - pre_genesis.rewards_key.ref_to().to_string(), - )), protocol_public_key: Some(HexString( pre_genesis .store diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 9425e3b019..66be875ff4 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -159,8 +159,6 @@ pub mod genesis_config { pub consensus_public_key: Option, // Public key for validator account. (default: generate) pub account_public_key: Option, - // Public key for staking reward account. (default: generate) - pub staking_reward_public_key: Option, // Public protocol signing key for validator account. (default: // generate) pub protocol_public_key: Option, @@ -168,8 +166,6 @@ pub mod genesis_config { pub dkg_public_key: Option, // Validator address (default: generate). pub address: Option, - // Staking reward account address (default: generate). - pub staking_reward_address: Option, // Total number of tokens held at genesis. // XXX: u64 doesn't work with toml-rs! pub tokens: Option, @@ -178,8 +174,6 @@ pub mod genesis_config { pub non_staked_balance: Option, // Filename of validator VP. (default: default validator VP) pub validator_vp: Option, - // Filename of staking reward account VP. (default: user VP) - pub staking_reward_vp: Option, // IP:port of the validator. (used in generation only) pub net_address: Option, /// Tendermint node key is used to derive Tendermint node ID for node @@ -277,17 +271,11 @@ pub mod genesis_config { ) -> Validator { let validator_vp_name = config.validator_vp.as_ref().unwrap(); let validator_vp_config = wasm.get(validator_vp_name).unwrap(); - let reward_vp_name = config.staking_reward_vp.as_ref().unwrap(); - let reward_vp_config = wasm.get(reward_vp_name).unwrap(); Validator { pos_data: GenesisValidator { address: Address::decode(&config.address.as_ref().unwrap()) .unwrap(), - staking_reward_address: Address::decode( - &config.staking_reward_address.as_ref().unwrap(), - ) - .unwrap(), tokens: token::Amount::whole(config.tokens.unwrap_or_default()), consensus_key: config .consensus_public_key @@ -295,12 +283,6 @@ pub mod genesis_config { .unwrap() .to_public_key() .unwrap(), - staking_reward_key: config - .staking_reward_public_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), }, account_key: config .account_public_key @@ -330,16 +312,6 @@ pub mod genesis_config { .unwrap() .to_sha256_bytes() .unwrap(), - reward_vp_code_path: reward_vp_config.filename.to_owned(), - reward_vp_sha256: reward_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown validator VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(), } } @@ -658,10 +630,6 @@ pub struct Validator { pub validator_vp_code_path: String, /// Expected SHA-256 hash of the validator VP pub validator_vp_sha256: [u8; 32], - /// Staking reward account code WASM - pub reward_vp_code_path: String, - /// Expected SHA-256 hash of the staking reward VP - pub reward_vp_sha256: [u8; 32], } #[derive( @@ -736,23 +704,13 @@ pub fn genesis() -> Genesis { // `tests::gen_genesis_validator` below. let consensus_keypair = wallet::defaults::validator_keypair(); let account_keypair = wallet::defaults::validator_keypair(); - let ed_staking_reward_keypair = ed25519::SecretKey::try_from_slice(&[ - 61, 198, 87, 204, 44, 94, 234, 228, 217, 72, 245, 27, 40, 2, 151, 174, - 24, 247, 69, 6, 9, 30, 44, 16, 88, 238, 77, 162, 243, 125, 240, 206, - ]) - .unwrap(); - let staking_reward_keypair = - common::SecretKey::try_from_sk(&ed_staking_reward_keypair).unwrap(); let address = wallet::defaults::validator_address(); - let staking_reward_address = Address::decode("atest1v4ehgw36xcersvee8qerxd35x9prsw2xg5erxv6pxfpygd2x89z5xsf5xvmnysejgv6rwd2rnj2avt").unwrap(); let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); let validator = Validator { pos_data: GenesisValidator { address, - staking_reward_address, tokens: token::Amount::whole(200_000), consensus_key: consensus_keypair.ref_to(), - staking_reward_key: staking_reward_keypair.ref_to(), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), @@ -761,8 +719,6 @@ pub fn genesis() -> Genesis { // TODO replace with https://github.com/anoma/anoma/issues/25) validator_vp_code_path: vp_user_path.into(), validator_vp_sha256: Default::default(), - reward_vp_code_path: vp_user_path.into(), - reward_vp_sha256: Default::default(), }; let parameters = Parameters { epoch_duration: EpochDuration { @@ -853,24 +809,18 @@ pub mod tests { use crate::wallet; /// Run `cargo test gen_genesis_validator -- --nocapture` to generate a - /// new genesis validator address, staking reward address and keypair. + /// new genesis validator address and keypair. #[test] fn gen_genesis_validator() { let address = gen_established_address(); - let staking_reward_address = gen_established_address(); let mut rng: ThreadRng = thread_rng(); let keypair: common::SecretKey = ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap(); let kp_arr = keypair.try_to_vec().unwrap(); - let staking_reward_keypair: common::SecretKey = - ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap(); - let srkp_arr = staking_reward_keypair.try_to_vec().unwrap(); let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); println!("address: {}", address); - println!("staking_reward_address: {}", staking_reward_address); println!("keypair: {:?}", kp_arr); - println!("staking_reward_keypair: {:?}", srkp_arr); println!("protocol_keypair: {:?}", protocol_keypair); println!("dkg_keypair: {:?}", dkg_keypair.try_to_vec().unwrap()); } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 7676ee7d2e..33723f92c7 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -107,11 +107,6 @@ pub trait PosReadOnly { /// Read PoS parameters. fn read_pos_params(&self) -> Result; - /// Read PoS validator's staking reward address. - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> Result, Self::Error>; /// Read PoS validator's consensus key (used for signing block votes). fn read_validator_consensus_key( &self, @@ -186,13 +181,6 @@ pub trait PosActions: PosReadOnly { address: &Self::Address, consensus_key: &Self::PublicKey, ) -> Result<(), Self::Error>; - /// Write PoS validator's staking reward address, into which staking rewards - /// will be credited. - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: Self::Address, - ) -> Result<(), Self::Error>; /// Write PoS validator's consensus key (used for signing block votes). fn write_validator_consensus_key( &mut self, @@ -267,7 +255,6 @@ pub trait PosActions: PosReadOnly { fn become_validator( &mut self, address: &Self::Address, - staking_reward_address: &Self::Address, consensus_key: &Self::PublicKey, current_epoch: impl Into, ) -> Result<(), Self::BecomeValidatorError> { @@ -277,13 +264,6 @@ pub trait PosActions: PosReadOnly { if self.is_validator(address)? { Err(BecomeValidatorError::AlreadyValidator(address.clone()))?; } - if address == staking_reward_address { - Err( - BecomeValidatorError::StakingRewardAddressEqValidatorAddress( - address.clone(), - ), - )?; - } let consensus_key_clone = consensus_key.clone(); let BecomeValidatorData { consensus_key, @@ -297,10 +277,6 @@ pub trait PosActions: PosReadOnly { &mut validator_set, current_epoch, ); - self.write_validator_staking_reward_address( - address, - staking_reward_address.clone(), - )?; self.write_validator_consensus_key(address, consensus_key)?; self.write_validator_state(address, state)?; self.write_validator_set(validator_set)?; @@ -626,13 +602,6 @@ pub trait PosBase { address: &Self::Address, consensus_key: &Self::PublicKey, ); - /// Write PoS validator's staking reward address, into which staking rewards - /// will be credited. - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: &Self::Address, - ); /// Write PoS validator's consensus key (used for signing block votes). fn write_validator_consensus_key( &mut self, @@ -674,12 +643,6 @@ pub trait PosBase { fn write_validator_set(&mut self, value: &ValidatorSets); /// Read PoS total voting power of all validators (active and inactive). fn write_total_voting_power(&mut self, value: &TotalVotingPowers); - /// Initialize staking reward account with the given public key. - fn init_staking_reward_account( - &mut self, - address: &Self::Address, - pk: &Self::PublicKey, - ); /// Credit tokens to the `target` account. This should only be used at /// genesis. fn credit_tokens( @@ -727,9 +690,7 @@ pub trait PosBase { for res in validators { let GenesisValidatorData { ref address, - staking_reward_address, consensus_key, - staking_reward_key, state, total_deltas, voting_power, @@ -741,19 +702,11 @@ pub trait PosBase { .get(current_epoch) .expect("Consensus key must be set"), ); - self.write_validator_staking_reward_address( - address, - &staking_reward_address, - ); self.write_validator_consensus_key(address, &consensus_key); self.write_validator_state(address, &state); self.write_validator_total_deltas(address, &total_deltas); self.write_validator_voting_power(address, &voting_power); self.write_bond(&bond_id, &bond); - self.init_staking_reward_account( - &staking_reward_address, - &staking_reward_key, - ); } self.write_validator_set(&validator_set); self.write_total_voting_power(&total_voting_power); @@ -955,11 +908,6 @@ pub enum GenesisError { pub enum BecomeValidatorError { #[error("The given address {0} is already a validator")] AlreadyValidator(Address), - #[error( - "The staking reward address must be different from the validator's \ - address {0}" - )] - StakingRewardAddressEqValidatorAddress(Address), } #[allow(missing_docs)] @@ -1104,9 +1052,7 @@ where PK: Debug + Clone + BorshDeserialize + BorshSerialize + BorshSchema, { address: Address, - staking_reward_address: Address, consensus_key: ValidatorConsensusKeys, - staking_reward_key: PK, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, @@ -1205,11 +1151,8 @@ where let validators = validators.map( move |GenesisValidator { address, - staking_reward_address, - tokens, consensus_key, - staking_reward_key, }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); @@ -1240,9 +1183,7 @@ where ); Ok(GenesisValidatorData { address: address.clone(), - staking_reward_address: staking_reward_address.clone(), consensus_key, - staking_reward_key: staking_reward_key.clone(), state, total_deltas, voting_power, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 5f5ac6846d..783c5855aa 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -108,15 +108,10 @@ pub struct VotingPowerDelta(i64); pub struct GenesisValidator { /// Validator's address pub address: Address, - /// An address to which any staking rewards will be credited, must be - /// different from the `address` - pub staking_reward_address: Address, /// Staked tokens are put into a self-bond pub tokens: Token, /// A public key used for signing validator's consensus actions pub consensus_key: PK, - /// An public key associated with the staking reward address - pub staking_reward_key: PK, } /// An update of the active and inactive validator set. diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index faef13f457..13356cbb55 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -50,13 +50,6 @@ where MissingNewValidatorConsensusKey(u64), #[error("Invalid validator consensus key update in epoch {0}")] InvalidValidatorConsensusKeyUpdate(u64), - #[error("Validator staking reward address is required for validator {0}")] - StakingRewardAddressIsRequired(Address), - #[error( - "Staking reward address must be different from the validator's \ - address {0}" - )] - StakingRewardAddressEqValidator(Address), #[error("Unexpectedly missing total deltas value for validator {0}")] MissingValidatorTotalDeltas(Address), #[error("The sum of total deltas for validator {0} are negative")] @@ -245,7 +238,7 @@ where /// Validator's address address: Address, /// Validator's data update - update: ValidatorUpdate, + update: ValidatorUpdate, }, /// Validator set update ValidatorSet(Data>), @@ -262,9 +255,8 @@ where /// An update of a validator's data. #[derive(Clone, Debug)] -pub enum ValidatorUpdate +pub enum ValidatorUpdate where - Address: Clone + Debug, TokenChange: Display + Debug + Default @@ -283,8 +275,6 @@ where State(Data), /// Consensus key update ConsensusKey(Data>), - /// Staking reward address update - StakingRewardAddress(Data
), /// Total deltas update TotalDeltas(Data>), /// Voting power update @@ -313,7 +303,6 @@ pub struct NewValidator { has_consensus_key: Option, has_total_deltas: bool, has_voting_power: bool, - has_staking_reward_address: bool, has_address_raw_hash: Option, voting_power: VotingPower, } @@ -806,16 +795,11 @@ where has_consensus_key, has_total_deltas, has_voting_power, - has_staking_reward_address, has_address_raw_hash, voting_power, } = &new_validator; // The new validator must have set all the required fields - if !(*has_state - && *has_total_deltas - && *has_voting_power - && *has_staking_reward_address) - { + if !(*has_state && *has_total_deltas && *has_voting_power) { errors.push(Error::InvalidNewValidator( address.clone(), new_validator.clone(), @@ -1129,15 +1113,6 @@ where address, data, ), - StakingRewardAddress(data) => { - Self::validator_staking_reward_address( - errors, - new_validators, - address, - data, - ) - } - TotalDeltas(data) => Self::validator_total_deltas( constants, errors, @@ -1327,32 +1302,6 @@ where } } - fn validator_staking_reward_address( - errors: &mut Vec>, - new_validators: &mut HashMap>, - address: Address, - data: Data
, - ) { - match (data.pre, data.post) { - (Some(_), Some(post)) => { - if post == address { - errors - .push(Error::StakingRewardAddressEqValidator(address)); - } - } - (None, Some(post)) => { - if post == address { - errors.push(Error::StakingRewardAddressEqValidator( - address.clone(), - )); - } - let validator = new_validators.entry(address).or_default(); - validator.has_staking_reward_address = true; - } - _ => errors.push(Error::StakingRewardAddressIsRequired(address)), - } - } - fn validator_total_deltas( constants: &Constants, errors: &mut Vec>, diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 3b498727df..3f71fa2119 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -166,17 +166,6 @@ mod macros { Ok($crate::ledger::storage::types::decode(value).unwrap()) } - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = $crate::ledger::storage_api::StorageRead::read_bytes( - self, - &validator_staking_reward_address_key(key), - )?; - Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) - } - fn read_validator_consensus_key( &self, key: &Self::Address, diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 366ce489b5..7afe1a3b4f 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -19,8 +19,6 @@ use crate::types::{key, token}; const PARAMS_STORAGE_KEY: &str = "params"; const VALIDATOR_STORAGE_PREFIX: &str = "validator"; const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; -const VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY: &str = - "staking_reward_address"; const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; @@ -92,31 +90,6 @@ pub fn is_validator_address_raw_hash_key(key: &Key) -> Option<&str> { } } -/// Storage key for validator's staking reward address. -pub fn validator_staking_reward_address_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's staking reward address? -pub fn is_validator_staking_reward_address_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY => - { - Some(validator) - } - _ => None, - } -} - /// Storage key for validator's consensus key. pub fn validator_consensus_key_key(validator: &Address) -> Key { validator_prefix(validator) @@ -464,15 +437,6 @@ where .unwrap(); } - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: &Self::Address, - ) { - self.write(&validator_staking_reward_address_key(key), encode(value)) - .unwrap(); - } - fn write_validator_consensus_key( &mut self, key: &Self::Address, @@ -533,22 +497,6 @@ where .unwrap(); } - fn init_staking_reward_account( - &mut self, - address: &Self::Address, - pk: &Self::PublicKey, - ) { - // let user_vp = - // std::fs::read("wasm/vp_user.wasm").expect("cannot load user VP"); - // // The staking reward accounts are setup with a user VP - // self.write(&Key::validity_predicate(address), user_vp.to_vec()) - // .unwrap(); - - // Write the public key - let pk_key = key::pk_key(address); - self.write(&pk_key, encode(pk)).unwrap(); - } - fn credit_tokens( &mut self, token: &Self::Address, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 60264e4926..058acac262 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -17,12 +17,10 @@ use thiserror::Error; use super::{ bond_key, is_bond_key, is_params_key, is_total_voting_power_key, - is_unbond_key, is_validator_set_key, - is_validator_staking_reward_address_key, is_validator_total_deltas_key, + is_unbond_key, is_validator_set_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, total_voting_power_key, unbond_key, validator_consensus_key_key, - validator_set_key, validator_slashes_key, - validator_staking_reward_address_key, validator_state_key, + validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, }; @@ -149,21 +147,6 @@ where address: validator.clone(), update: State(Data { pre, post }), }); - } else if let Some(validator) = - is_validator_staking_reward_address_key(key) - { - let pre = - self.ctx.pre().read_bytes(key)?.and_then(|bytes| { - Address::try_from_slice(&bytes[..]).ok() - }); - let post = - self.ctx.post().read_bytes(key)?.and_then(|bytes| { - Address::try_from_slice(&bytes[..]).ok() - }); - changes.push(Validator { - address: validator.clone(), - update: StakingRewardAddress(Data { pre, post }), - }); } else if let Some(validator) = is_validator_consensus_key_key(key) { let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index a7d5ee864b..2a2092214a 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -187,18 +187,12 @@ pub struct InitValidator { pub account_key: common::PublicKey, /// A key to be used for signing blocks and votes on blocks. pub consensus_key: common::PublicKey, - /// Public key to be written into the staking reward account's storage. - /// This can be used for signature verification of transactions for the - /// newly created account. - pub rewards_account_key: common::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: DkgPublicKey, /// The VP code for validator account pub validator_vp_code: Vec, - /// The VP code for validator's staking reward account - pub rewards_vp_code: Vec, } /// Module that includes helper functions for classifying diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index c3e94b9238..c046c08a8e 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1674,7 +1674,6 @@ fn test_genesis_validators() -> Result<()> { config.tokens = Some(200000); config.non_staked_balance = Some(1000000000000); config.validator_vp = Some("vp_user".into()); - config.staking_reward_vp = Some("vp_user".into()); // Setup the validator ports same as what // `setup::add_validators` would do let mut net_address = net_address_0; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 61b859a7d6..2a748f0259 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -71,7 +71,6 @@ //! address in Tendermint) //! - `#{PoS}/validator_set` //! - `#{PoS}/validator/#{validator}/consensus_key` -//! - `#{PoS}/validator/#{validator}/staking_reward_address` //! - `#{PoS}/validator/#{validator}/state` //! - `#{PoS}/validator/#{validator}/total_deltas` //! - `#{PoS}/validator/#{validator}/voting_power` @@ -121,10 +120,7 @@ pub fn init_pos( // addresses exist tx_env.spawn_accounts([&staking_token_address()]); for validator in genesis_validators { - tx_env.spawn_accounts([ - &validator.address, - &validator.staking_reward_address, - ]); + tx_env.spawn_accounts([&validator.address]); } tx_env.storage.block.epoch = start_epoch; // Initialize PoS storage @@ -601,7 +597,6 @@ pub mod testing { #[derive(Clone, Debug, Default)] pub struct TestValidator { pub address: Option
, - pub staking_reward_address: Option
, pub stake: Option, /// Balance is a pair of token address and its amount pub unstaked_balances: Vec<(Address, token::Amount)>, @@ -683,10 +678,6 @@ pub mod testing { #[derivative(Debug = "ignore")] pk: PublicKey, }, - ValidatorStakingRewardsAddress { - validator: Address, - address: Address, - }, ValidatorTotalDeltas { validator: Address, delta: i128, @@ -897,10 +888,6 @@ pub mod testing { validator: address.clone(), pk: consensus_key, }, - PosStorageChange::ValidatorStakingRewardsAddress { - validator: address.clone(), - address: address::testing::established_address_1(), - }, PosStorageChange::ValidatorState { validator: address.clone(), state: ValidatorState::Pending, @@ -1389,14 +1376,6 @@ pub mod testing { .write_validator_consensus_key(&validator, consensus_key) .unwrap(); } - PosStorageChange::ValidatorStakingRewardsAddress { - validator, - address, - } => { - tx::ctx() - .write_validator_staking_reward_address(&validator, address) - .unwrap(); - } PosStorageChange::ValidatorTotalDeltas { validator, delta, diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index c11b035495..ee50943336 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -4,8 +4,7 @@ pub use namada::ledger::pos::*; use namada::ledger::pos::{ bond_key, namada_proof_of_stake, params_key, total_voting_power_key, unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, - validator_set_key, validator_slashes_key, - validator_staking_reward_address_key, validator_state_key, + validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, }; use namada::types::address::Address; @@ -74,19 +73,17 @@ impl Ctx { } /// Attempt to initialize a validator account. On success, returns the - /// initialized validator account's address and its staking reward address. + /// initialized validator account's address. pub fn init_validator( &mut self, InitValidator { account_key, consensus_key, - rewards_account_key, protocol_key, dkg_key, validator_vp_code, - rewards_vp_code, }: InitValidator, - ) -> EnvResult<(Address, Address)> { + ) -> EnvResult
{ let current_epoch = self.get_block_epoch()?; // Init validator account let validator_address = self.init_account(&validator_vp_code)?; @@ -97,19 +94,13 @@ impl Ctx { let dkg_pk_key = key::dkg_session_keys::dkg_pk_key(&validator_address); self.write(&dkg_pk_key, &dkg_key)?; - // Init staking reward account - let rewards_address = self.init_account(&rewards_vp_code)?; - let pk_key = key::pk_key(&rewards_address); - self.write(&pk_key, &rewards_account_key)?; - self.become_validator( &validator_address, - &rewards_address, &consensus_key, current_epoch, )?; - Ok((validator_address, rewards_address)) + Ok(validator_address) } } @@ -140,14 +131,6 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_address_raw_hash_key(raw_hash), address) } - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: Self::Address, - ) -> Result<(), Self::Error> { - self.write(&validator_staking_reward_address_key(key), &value) - } - fn write_validator_consensus_key( &mut self, key: &Self::Address, diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 6718988657..bda4818722 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -68,16 +68,12 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &bond.source, Some(source) if *source != bond.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: bond.validator.clone(), - staking_reward_address, tokens: initial_stake, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 2d5f1a6256..a99bb8cde9 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -15,12 +15,8 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { // Register the validator in PoS match ctx.init_validator(init_validator) { - Ok((validator_address, staking_reward_address)) => { - debug_log!( - "Created validator {} and staking reward account {}", - validator_address.encode(), - staking_reward_address.encode() - ) + Ok(validator_address) => { + debug_log!("Created validator {}", validator_address.encode(),) } Err(err) => { debug_log!("Validator creation failed with: {}", err); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 6199393fb1..df4299deab 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -67,13 +67,10 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &unbond.source, Some(source) if *source != unbond.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: unbond.validator.clone(), - staking_reward_address, tokens: if is_delegation { // If we're unbonding a delegation, we'll give the initial stake // to the delegation instead of the validator @@ -82,7 +79,6 @@ mod tests { initial_stake }, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 8add20a78d..4cc0e35691 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -73,13 +73,10 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &withdraw.source, Some(source) if *source != withdraw.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: withdraw.validator.clone(), - staking_reward_address, tokens: if is_delegation { // If we're withdrawing a delegation, we'll give the initial // stake to the delegation instead of the @@ -89,7 +86,6 @@ mod tests { initial_stake }, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); From d4eb5d53d4e00d62b333ad77ebdede132199a7ba Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 25 Oct 2022 20:30:45 -0400 Subject: [PATCH 16/38] remove staking reward address from genesis toml files --- genesis/dev.toml | 6 ------ genesis/e2e-tests-single-node.toml | 2 -- 2 files changed, 8 deletions(-) diff --git a/genesis/dev.toml b/genesis/dev.toml index 29a4b4d791..c3f0eebac0 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -7,20 +7,14 @@ genesis_time = "2021-09-30:10:00.00Z" consensus_public_key = "5e704c4e46265e1ccc87505149f79b9d2e414d01a4e3806dfc65f0a73901c1d0" # Public key of the validator's Anoma account. account_public_key = "5e704c4e46265e1ccc87505149f79b9d2e414d01a4e3806dfc65f0a73901c1d0" -# Public key of the Anoma account for this validator's staking rewards. -staking_reward_public_key = "6f5c421769d321ec05d01158b170649a01848f43a27988f71443041be23f2f39" # Address of the validator. address = "a1qq5qqqqqgfqnsd6pxse5zdj9g5crzsf5x4zyzv6yxerr2d2rxpryzwp5g5m5zvfjxv6ygsekjmraj0" -# Staking reward address of the validator. -staking_reward_address = "a1qq5qqqqqxaz5vven8yu5gdpng9zrys6ygvurwv3sgsmrvd6xgdzrys6yg4pnwd6z89rrqv2xvjcy9t" # 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_user" -# VP for the staking reward account -staking_reward_vp = "vp_user" # Public IP:port address net_address = "127.0.0.1:26656" diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 0e3a6d3fc8..5be391cbe9 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -11,8 +11,6 @@ tokens = 200000 non_staked_balance = 1000000000000 # VP for the validator account validator_vp = "vp_user" -# VP for the staking reward account -staking_reward_vp = "vp_user" # Public IP:port address. # We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. From 3024c30d52806423222cade0e9fc679d47df77c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Oct 2022 00:54:20 +0000 Subject: [PATCH 17/38] [ci] wasm checksums update --- wasm/checksums.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5dfd713ff7..22dc699c30 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -15,4 +15,4 @@ "vp_testnet_faucet.wasm": "vp_testnet_faucet.8e2e45ff165d40dc8249188aca108e5cba86ac5dd1cd989b0237cadd4b66bfdf.wasm", "vp_token.wasm": "vp_token.4a0446f20e7436de1e889c640a11644d1a1295c4d29e45b24582df2b9ed3176e.wasm", "vp_user.wasm": "vp_user.eb1d6f1f524c28571ad0f21f75371aa635257313cea2702b9a70e5022fe6c3ef.wasm" -} \ No newline at end of file +} From c1fc428f554eeab0efa6639f1fc732a0edef8486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:29:06 +0200 Subject: [PATCH 18/38] client: remove staking rewards address from init-validator result --- apps/src/lib/client/tx.rs | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 93e7e4f3bd..a396914016 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -249,21 +249,10 @@ pub async fn submit_init_validator( let (mut ctx, initialized_accounts) = process_tx(ctx, &tx_args, tx, Some(&source)).await; if !tx_args.dry_run { - let (validator_address_alias, validator_address, rewards_address_alias) = + let (validator_address_alias, validator_address) = match &initialized_accounts[..] { - // There should be 2 accounts, one for the validator itself, one - // for its staking reward address. - [account_1, account_2] => { - // We need to find out which address is which - let (validator_address, rewards_address) = - if rpc::is_validator(account_1, tx_args.ledger_address) - .await - { - (account_1, account_2) - } else { - (account_2, account_1) - }; - + // There should be 1 account for the validator itself + [validator_address] => { let validator_address_alias = match tx_args .initialized_account_alias { @@ -298,23 +287,7 @@ pub async fn submit_init_validator( validator_address.encode() ); } - let rewards_address_alias = - format!("{}-rewards", validator_address_alias); - if let Some(new_alias) = ctx.wallet.add_address( - rewards_address_alias.clone(), - rewards_address.clone(), - ) { - println!( - "Added alias {} for address {}.", - new_alias, - rewards_address.encode() - ); - } - ( - validator_address_alias, - validator_address.clone(), - rewards_address_alias, - ) + (validator_address_alias, validator_address.clone()) } _ => { eprintln!("Expected two accounts to be created"); @@ -335,7 +308,6 @@ pub async fn submit_init_validator( "The validator's addresses and keys were stored in the wallet:" ); println!(" Validator address \"{}\"", validator_address_alias); - println!(" Staking reward address \"{}\"", rewards_address_alias); println!(" Validator account key \"{}\"", validator_key_alias); println!(" Consensus key \"{}\"", consensus_key_alias); println!( From eb39d7c9a42d4cceaf7506a23abcc2de032078ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:29:45 +0200 Subject: [PATCH 19/38] wallet: remove validator rewards key --- apps/src/lib/wallet/alias.rs | 5 ----- apps/src/lib/wallet/pre_genesis.rs | 11 ----------- apps/src/lib/wallet/store.rs | 5 ----- 3 files changed, 21 deletions(-) diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs index 25fcf03d11..13d977b852 100644 --- a/apps/src/lib/wallet/alias.rs +++ b/apps/src/lib/wallet/alias.rs @@ -97,11 +97,6 @@ pub fn validator_consensus_key(validator_alias: &Alias) -> Alias { format!("{validator_alias}-consensus-key").into() } -/// Default alias of a validator's staking rewards key -pub fn validator_rewards_key(validator_alias: &Alias) -> Alias { - format!("{validator_alias}-rewards-key").into() -} - /// Default alias of a validator's Tendermint node key pub fn validator_tendermint_node_key(validator_alias: &Alias) -> Alias { format!("{validator_alias}-tendermint-node-key").into() diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index f28be00d1b..fb47fb9f88 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -40,8 +40,6 @@ pub struct ValidatorWallet { pub account_key: Rc, /// Cryptographic keypair for consensus key pub consensus_key: Rc, - /// Cryptographic keypair for rewards key - pub rewards_key: Rc, /// Cryptographic keypair for Tendermint node key pub tendermint_node_key: Rc, } @@ -54,8 +52,6 @@ pub struct ValidatorStore { pub account_key: wallet::StoredKeypair, /// Cryptographic keypair for consensus key pub consensus_key: wallet::StoredKeypair, - /// Cryptographic keypair for rewards key - pub rewards_key: wallet::StoredKeypair, /// Cryptographic keypair for Tendermint node key pub tendermint_node_key: wallet::StoredKeypair, /// Special validator keys @@ -107,7 +103,6 @@ impl ValidatorWallet { let password = if store.account_key.is_encrypted() || store.consensus_key.is_encrypted() - || store.rewards_key.is_encrypted() || store.account_key.is_encrypted() { Some(wallet::read_password("Enter decryption password: ")) @@ -119,8 +114,6 @@ impl ValidatorWallet { store.account_key.get(true, password.clone())?; let consensus_key = store.consensus_key.get(true, password.clone())?; - let rewards_key = - store.rewards_key.get(true, password.clone())?; let tendermint_node_key = store.tendermint_node_key.get(true, password)?; @@ -128,7 +121,6 @@ impl ValidatorWallet { store, account_key, consensus_key, - rewards_key, tendermint_node_key, }) } @@ -149,7 +141,6 @@ impl ValidatorWallet { SchemeType::Ed25519, &password, ); - let (rewards_key, rewards_sk) = gen_key_to_store(scheme, &password); let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store( // Note that TM only allows ed25519 for node IDs SchemeType::Ed25519, @@ -159,7 +150,6 @@ impl ValidatorWallet { let store = ValidatorStore { account_key, consensus_key, - rewards_key, tendermint_node_key, validator_keys, }; @@ -167,7 +157,6 @@ impl ValidatorWallet { store, account_key: account_sk, consensus_key: consensus_sk, - rewards_key: rewards_sk, tendermint_node_key: tendermint_node_sk, } } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index e189255355..8668b6ed1b 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -392,7 +392,6 @@ impl Store { other: pre_genesis::ValidatorWallet, ) { let account_key_alias = alias::validator_key(&validator_alias); - let rewards_key_alias = alias::validator_rewards_key(&validator_alias); let consensus_key_alias = alias::validator_consensus_key(&validator_alias); let tendermint_node_key_alias = @@ -400,7 +399,6 @@ impl Store { let keys = [ (account_key_alias.clone(), other.store.account_key), - (rewards_key_alias.clone(), other.store.rewards_key), (consensus_key_alias.clone(), other.store.consensus_key), ( tendermint_node_key_alias.clone(), @@ -410,12 +408,10 @@ impl Store { self.keys.extend(keys.into_iter()); let account_pk = other.account_key.ref_to(); - let rewards_pk = other.rewards_key.ref_to(); let consensus_pk = other.consensus_key.ref_to(); let tendermint_node_pk = other.tendermint_node_key.ref_to(); let addresses = [ (account_key_alias.clone(), (&account_pk).into()), - (rewards_key_alias.clone(), (&rewards_pk).into()), (consensus_key_alias.clone(), (&consensus_pk).into()), ( tendermint_node_key_alias.clone(), @@ -426,7 +422,6 @@ impl Store { let pkhs = [ ((&account_pk).into(), account_key_alias), - ((&rewards_pk).into(), rewards_key_alias), ((&consensus_pk).into(), consensus_key_alias), ((&tendermint_node_pk).into(), tendermint_node_key_alias), ]; From 69480c8d65493a5c03054bf54ebfd40b74b9bca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:30:45 +0200 Subject: [PATCH 20/38] remove staking rewards address from cli strings and docs strings --- apps/src/lib/cli.rs | 10 +++++----- apps/src/lib/client/utils.rs | 5 ++--- shared/src/types/transaction/mod.rs | 3 +-- wasm/wasm_source/src/tx_init_validator.rs | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 53d408322f..6b68ceb8ca 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -857,8 +857,8 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Send a signed transaction to create a new validator and \ - its staking reward account.", + "Send a signed transaction to create a new validator \ + account.", ) .add_args::() } @@ -1255,9 +1255,9 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Initialize genesis validator's address, staking reward \ - address, consensus key, validator account key and \ - staking rewards key and use it in the ledger's node.", + "Initialize genesis validator's address, consensus key \ + and validator account key and use it in the ledger's \ + node.", ) .add_args::() } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 2c0f54dd91..d3a4f31fd7 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -874,9 +874,8 @@ fn init_established_account( } } -/// Initialize genesis validator's address, staking reward address, -/// consensus key, validator account key and staking rewards key and use -/// it in the ledger's node. +/// Initialize genesis validator's address, consensus key and validator account +/// key and use it in the ledger's node. pub fn init_genesis_validator( global_args: args::Global, args::InitGenesisValidator { diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 2a2092214a..81b5f5aeb0 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -168,8 +168,7 @@ pub struct InitAccount { pub vp_code: Vec, } -/// A tx data type to initialize a new validator account and its staking reward -/// account. +/// A tx data type to initialize a new validator account. #[derive( Debug, Clone, diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index a99bb8cde9..6a823faf3f 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -1,5 +1,5 @@ -//! A tx to initialize a new validator account and staking reward account with a -//! given public keys and a validity predicates. +//! 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::*; From c356e0d7e2e4e3458e3d974174680f75e0fc6bba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 17:14:28 +0200 Subject: [PATCH 21/38] changelog: #687 --- .changelog/unreleased/features/687-remove-staking-address.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/687-remove-staking-address.md diff --git a/.changelog/unreleased/features/687-remove-staking-address.md b/.changelog/unreleased/features/687-remove-staking-address.md new file mode 100644 index 0000000000..39d4def2aa --- /dev/null +++ b/.changelog/unreleased/features/687-remove-staking-address.md @@ -0,0 +1,2 @@ +- PoS: Removed staking reward addresses in preparation of auto-staked rewards + system. ([#687](https://github.com/anoma/namada/pull/687)) \ No newline at end of file From 365cd27b83019c2c36adc0ea838c8ce0c1575743 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 21 Sep 2022 17:13:47 -0400 Subject: [PATCH 22/38] introduce validator commission rate and changes --- apps/src/lib/config/genesis.rs | 30 ++++++++++ proof_of_stake/src/lib.rs | 27 +++++++++ proof_of_stake/src/types.rs | 4 ++ shared/src/ledger/pos/storage.rs | 94 +++++++++++++++++++++++++++++++- 4 files changed, 153 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 66be875ff4..c0135708bd 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -179,6 +179,11 @@ pub mod genesis_config { /// Tendermint node key is used to derive Tendermint node ID for node /// authentication pub tendermint_node_key: Option, + /// Commission rate charged on rewards for delegators (bounded inside + /// 0-1) + pub commission_rate: Option, + /// Maximum change in commission rate permitted per epoch + pub max_commission_rate_change: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -283,6 +288,29 @@ pub mod genesis_config { .unwrap() .to_public_key() .unwrap(), + commission_rate: config + .commission_rate + .and_then(|rate| { + if rate >= Decimal::ZERO && rate <= Decimal::ONE { + Some(rate) + } else { + None + } + }) + .expect("Commission rate must be between 0.0 and 1.0"), + max_commission_rate_change: config + .max_commission_rate_change + .and_then(|rate| { + if rate >= Decimal::ZERO && rate <= Decimal::ONE { + Some(rate) + } else { + None + } + }) + .expect( + "Max commission rate change must be between 0.0 and \ + 1.0", + ), }, account_key: config .account_public_key @@ -711,6 +739,8 @@ pub fn genesis() -> Genesis { address, tokens: token::Amount::whole(200_000), consensus_key: consensus_keypair.ref_to(), + commission_rate: dec!(0.05), + max_commission_rate_change: dec!(0.01), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 33723f92c7..e4e23f3345 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -589,6 +589,13 @@ pub trait PosBase { ) -> Option; /// Read PoS slashes applied to a validator. fn read_validator_slashes(&self, key: &Self::Address) -> Slashes; + /// Read PoS validator's commission rate + fn read_validator_commission_rate(&self, key: &Self::Address) -> Decimal; + /// Read PoS validator's maximum commission rate change per epoch + fn read_validator_max_commission_rate_change( + &self, + key: &Self::Address, + ) -> Decimal; /// Read PoS validator set (active and inactive). fn read_validator_set(&self) -> ValidatorSets; /// Read PoS total voting power of all validators (active and inactive). @@ -627,6 +634,18 @@ pub trait PosBase { key: &Self::Address, value: &ValidatorVotingPowers, ); + /// Write PoS validator's commission rate. + fn write_validator_commission_rate( + &mut self, + key: &Self::Address, + value: &Decimal, + ); + /// Write PoS validator's commission rate. + fn write_validator_max_commission_rate_change( + &mut self, + key: &Self::Address, + value: &Decimal, + ); /// Write (append) PoS slash applied to a validator. fn write_validator_slash( &mut self, @@ -691,6 +710,8 @@ pub trait PosBase { let GenesisValidatorData { ref address, consensus_key, + commission_rate, + max_commission_rate_change, state, total_deltas, voting_power, @@ -1053,6 +1074,8 @@ where { address: Address, consensus_key: ValidatorConsensusKeys, + commission_rate: Decimal, + max_commission_rate_change: Decimal, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, @@ -1153,6 +1176,8 @@ where address, tokens, consensus_key, + commission_rate, + max_commission_rate_change, }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); @@ -1184,6 +1209,8 @@ where Ok(GenesisValidatorData { address: address.clone(), consensus_key, + commission_rate: commission_rate.clone(), + max_commission_rate_change: max_commission_rate_change.clone(), state, total_deltas, voting_power, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 783c5855aa..7b9f809e38 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -112,6 +112,10 @@ pub struct GenesisValidator { pub tokens: Token, /// A public key used for signing validator's consensus actions pub consensus_key: PK, + /// Commission rate charged on rewards for delegators (bounded inside 0-1) + pub commission_rate: Decimal, + /// Maximum change in commission rate permitted per epoch + pub max_commission_rate_change: Decimal, } /// An update of the active and inactive validator set. diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 7afe1a3b4f..ac4b007b63 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -23,6 +23,9 @@ const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_VOTING_POWER_STORAGE_KEY: &str = "voting_power"; +const VALIDATOR_COMMISSION_RATE_STORAGE_KEY: &str = "commission_rate"; +const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = + "max_commission_rate_change"; const SLASHES_PREFIX: &str = "slash"; const BOND_STORAGE_KEY: &str = "bond"; const UNBOND_STORAGE_KEY: &str = "unbond"; @@ -115,8 +118,60 @@ pub fn is_validator_consensus_key_key(key: &Key) -> Option<&Address> { } } -/// Storage key for validator's state. -pub fn validator_state_key(validator: &Address) -> Key { +/// Storage key for validator's commission rate. +pub fn validator_commission_rate_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_COMMISSION_RATE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's commissionr ate? +pub fn is_validator_commission_rate_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_COMMISSION_RATE_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's maximum commission rate change per epoch. +pub fn validator_max_commission_rate_change_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's maximum commission rate change per epoch? +pub fn is_validator_max_commission_rate_change_key( + key: &Key, +) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's consensus key. +pub fn validator_consensus_key_key(validator: &Address) -> Key { validator_prefix(validator) .push(&VALIDATOR_STATE_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") @@ -413,6 +468,23 @@ where .unwrap_or_default() } + fn read_validator_commission_rate( + &self, + key: &Self::Address, + ) -> rust_decimal::Decimal { + let (value, _gas) = + self.read(&validator_commission_rate_key(key)).unwrap(); + value.map(|value| decode(value).unwrap()).unwrap() + } + + fn read_validator_max_commission_rate_change( + &self, + key: &Self::Address, + ) -> rust_decimal::Decimal { + let (value, _gas) = + self.read(&validator_commission_rate_key(key)).unwrap(); + value.map(|value| decode(value).unwrap()).unwrap() + } fn read_validator_set(&self) -> ValidatorSets { let (value, _gas) = self.read(&validator_set_key()).unwrap(); decode(value.unwrap()).unwrap() @@ -437,6 +509,24 @@ where .unwrap(); } + fn write_validator_commission_rate( + &mut self, + key: &Self::Address, + value: &rust_decimal::Decimal, + ) { + self.write(&validator_commission_rate_key(key), encode(value)) + .unwrap(); + } + + fn write_validator_max_commission_rate_change( + &mut self, + key: &Self::Address, + value: &rust_decimal::Decimal, + ) { + self.write(&validator_max_commission_rate_change_key(key), encode(value)) + .unwrap(); +} + fn write_validator_consensus_key( &mut self, key: &Self::Address, From 081642edfeb0a1a16867244d214f91319597580e Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 22 Sep 2022 20:40:22 -0400 Subject: [PATCH 23/38] require commission rate input data for new validators --- Cargo.lock | 1 + apps/src/lib/cli.rs | 30 +++++++++++++++++++++++++++++ apps/src/lib/client/tx.rs | 17 ++++++++++++++++ apps/src/lib/client/utils.rs | 4 ++++ apps/src/lib/config/genesis.rs | 10 +++++----- genesis/dev.toml | 4 ++++ genesis/e2e-tests-single-node.toml | 4 ++++ proof_of_stake/src/lib.rs | 28 +++++++++++++++++++++++++++ shared/src/types/transaction/mod.rs | 5 +++++ tx_prelude/Cargo.toml | 1 + tx_prelude/src/proof_of_stake.rs | 24 +++++++++++++++++++++++ 11 files changed, 123 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34ab8ab550..edad87b845 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3124,6 +3124,7 @@ dependencies = [ "namada", "namada_macros", "namada_vm_env", + "rust_decimal", "sha2 0.10.6", "thiserror", ] diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 6b68ceb8ca..0c4ad9b1a4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1278,6 +1278,10 @@ pub mod args { use namada::types::storage::{self, Epoch}; use namada::types::token; use namada::types::transaction::GasLimit; + use rust_decimal::Decimal; + use serde::Deserialize; + use tendermint::Timeout; + use tendermint_config::net::Address as TendermintAddress; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; @@ -1306,6 +1310,7 @@ pub mod args { const CHAIN_ID_PREFIX: Arg = arg("chain-prefix"); const CODE_PATH: Arg = arg("code-path"); const CODE_PATH_OPT: ArgOpt = CODE_PATH.opt(); + const COMMISSION_RATE: Arg = arg("commission-rate"); const CONSENSUS_TIMEOUT_COMMIT: ArgDefault = arg_default( "consensus-timeout-commit", DefaultFn(|| Timeout::from_str("1s").unwrap()), @@ -1337,6 +1342,7 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("ledger-address"); const LOCALHOST: ArgFlag = flag("localhost"); + const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); const MODE: ArgOpt = arg_opt("mode"); const NET_ADDRESS: Arg = arg("net-address"); const NFT_ADDRESS: Arg
= arg("nft-address"); @@ -1589,6 +1595,8 @@ pub mod args { pub account_key: Option, pub consensus_key: Option, pub protocol_key: Option, + pub commission_rate: Decimal, + pub max_commission_rate_change: Decimal, pub validator_vp_code_path: Option, pub unsafe_dont_encrypt: bool, } @@ -1601,6 +1609,8 @@ pub mod args { let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); let protocol_key = PROTOCOL_KEY.parse(matches); + let commission_rate = COMMISSION_RATE.parse(matches); + let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { @@ -1610,6 +1620,8 @@ pub mod args { account_key, consensus_key, protocol_key, + commission_rate, + max_commission_rate_change, validator_vp_code_path, unsafe_dont_encrypt, } @@ -1636,6 +1648,12 @@ pub mod args { "A public key for signing protocol transactions. A new \ one will be generated if none given.", )) + .arg(COMMISSION_RATE.def().about( + "The commission rate charged by the validator for delegation rewards. This is a required parameter.", + )) + .arg(MAX_COMMISSION_RATE_CHANGE.def().about( + "The maximum change per epoch in the commission rate charged by the validator for delegation rewards. This is a required parameter.", + )) .arg(VALIDATOR_CODE_PATH.def().about( "The path to the validity predicate WASM code to be used \ for the validator account. Uses the default validator VP \ @@ -2674,6 +2692,8 @@ pub mod args { #[derive(Clone, Debug)] pub struct InitGenesisValidator { pub alias: String, + pub commission_rate: Decimal, + pub max_commission_rate_change: Decimal, pub net_address: SocketAddr, pub unsafe_dont_encrypt: bool, pub key_scheme: SchemeType, @@ -2682,6 +2702,8 @@ pub mod args { impl Args for InitGenesisValidator { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); + let commission_rate = COMMISSION_RATE.parse(matches); + let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); let net_address = NET_ADDRESS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let key_scheme = SCHEME.parse(matches); @@ -2690,6 +2712,8 @@ pub mod args { net_address, unsafe_dont_encrypt, key_scheme, + commission_rate, + max_commission_rate_change } } @@ -2700,6 +2724,12 @@ pub mod args { Anoma uses port `26656` for P2P connections by default, \ but you can configure a different value.", )) + .arg(COMMISSION_RATE.def().about( + "The commission rate charged by the validator for delegation rewards. This is a required parameter.", + )) + .arg(MAX_COMMISSION_RATE_CHANGE.def().about( + "The maximum change per epoch in the commission rate charged by the validator for delegation rewards. This is a required parameter.", + )) .arg(UNSAFE_DONT_ENCRYPT.def().about( "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index a396914016..14c4f6ed4d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -25,6 +25,11 @@ use namada::types::transaction::nft::{CreateNft, MintNft}; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, storage, token}; use namada::{ledger, vm}; +use rust_decimal::Decimal; +use tendermint_config::net::Address as TendermintAddress; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use tendermint_rpc::query::{EventType, Query}; +use tendermint_rpc::{Client, HttpClient}; use super::rpc; use crate::cli::context::WalletAddress; @@ -160,6 +165,8 @@ pub async fn submit_init_validator( account_key, consensus_key, protocol_key, + commission_rate, + max_commission_rate_change, validator_vp_code_path, unsafe_dont_encrypt, }: args::TxInitValidator, @@ -225,6 +232,14 @@ pub async fn submit_init_validator( let validator_vp_code = validator_vp_code_path .map(|path| ctx.read_wasm(path)) .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); + + // Validate the commission rate data + if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { + eprintln!("The validator commission rate must not exceed 1.0 or 100%, and it must be 0 or positive"); + } + if max_commission_rate_change > Decimal::ONE || max_commission_rate_change < Decimal::ZERO { + eprintln!("The validator maximum change in commission rate per epoch must not exceed 1.0 or 100%"); + } // Validate the validator VP code if let Err(err) = vm::validate_untrusted_wasm(&validator_vp_code) { eprintln!( @@ -242,6 +257,8 @@ pub async fn submit_init_validator( consensus_key: consensus_key.ref_to(), protocol_key, dkg_key, + commission_rate, + max_commission_rate_change, validator_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index d3a4f31fd7..d892601ac6 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -880,6 +880,8 @@ pub fn init_genesis_validator( global_args: args::Global, args::InitGenesisValidator { alias, + commission_rate, + max_commission_rate_change, net_address, unsafe_dont_encrypt, key_scheme, @@ -933,6 +935,8 @@ pub fn init_genesis_validator( .public() .to_string(), )), + commission_rate: Some(commission_rate), + max_commission_rate_change: Some(max_commission_rate_change), tendermint_node_key: Some(HexString( pre_genesis.tendermint_node_key.ref_to().to_string(), )), diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index c0135708bd..89b07b14af 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -172,6 +172,11 @@ pub mod genesis_config { // Unstaked balance at genesis. // XXX: u64 doesn't work with toml-rs! pub non_staked_balance: Option, + /// Commission rate charged on rewards for delegators (bounded inside + /// 0-1) + pub commission_rate: Option, + /// Maximum change in commission rate permitted per epoch + pub max_commission_rate_change: Option, // Filename of validator VP. (default: default validator VP) pub validator_vp: Option, // IP:port of the validator. (used in generation only) @@ -179,11 +184,6 @@ pub mod genesis_config { /// Tendermint node key is used to derive Tendermint node ID for node /// authentication pub tendermint_node_key: Option, - /// Commission rate charged on rewards for delegators (bounded inside - /// 0-1) - pub commission_rate: Option, - /// Maximum change in commission rate permitted per epoch - pub max_commission_rate_change: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/genesis/dev.toml b/genesis/dev.toml index c3f0eebac0..8d8d158033 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -15,6 +15,10 @@ tokens = 200000 non_staked_balance = 100000 # VP for the validator account validator_vp = "vp_user" +# 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" diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 5be391cbe9..029c2c2e75 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -11,6 +11,10 @@ tokens = 200000 non_staked_balance = 1000000000000 # VP for the validator account validator_vp = "vp_user" +# 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. # We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index e4e23f3345..cca57fb0df 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -193,6 +193,18 @@ pub trait PosActions: PosReadOnly { key: &Self::Address, value: ValidatorStates, ) -> Result<(), Self::Error>; + /// Write PoS validator's commission rate for delegator rewards + fn write_validator_commission_rate( + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error>; + /// Write PoS validator's maximum change in the commission rate per epoch + fn write_validator_max_commission_rate_change( + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error>; /// Write PoS validator's total deltas of their bonds (validator self-bonds /// and delegations). fn write_validator_total_deltas( @@ -257,6 +269,8 @@ pub trait PosActions: PosReadOnly { address: &Self::Address, consensus_key: &Self::PublicKey, current_epoch: impl Into, + commission_rate: Decimal, + max_commission_rate_change: Decimal ) -> Result<(), Self::BecomeValidatorError> { let current_epoch = current_epoch.into(); let params = self.read_pos_params()?; @@ -270,12 +284,16 @@ pub trait PosActions: PosReadOnly { state, total_deltas, voting_power, + commission_rate, + max_commission_rate_change } = become_validator( ¶ms, address, consensus_key, &mut validator_set, current_epoch, + commission_rate, + max_commission_rate_change ); self.write_validator_consensus_key(address, consensus_key)?; self.write_validator_state(address, state)?; @@ -283,6 +301,10 @@ pub trait PosActions: PosReadOnly { self.write_validator_address_raw_hash(address, &consensus_key_clone)?; self.write_validator_total_deltas(address, total_deltas)?; self.write_validator_voting_power(address, voting_power)?; + self.write_validator_commission_rate(address, commission_rate)?; + self.write_validator_max_commission_rate_change(address, max_commission_rate_change)?; + + // Do we need to write the total deltas of all validators? Ok(()) } @@ -1328,6 +1350,8 @@ where state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, + commission_rate: Decimal, + max_commission_rate_change: Decimal, } /// A function that initialized data for a new validator. @@ -1337,6 +1361,8 @@ fn become_validator( consensus_key: &PK, validator_set: &mut ValidatorSets
, current_epoch: Epoch, + commission_rate: Decimal, + max_commission_rate_change: Decimal, ) -> BecomeValidatorData where Address: Debug @@ -1399,6 +1425,8 @@ where state, total_deltas, voting_power, + commission_rate, + max_commission_rate_change } } diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 81b5f5aeb0..65a76a4661 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -23,6 +23,7 @@ pub use decrypted::*; #[cfg(feature = "ferveo-tpke")] pub use encrypted::EncryptionKey; pub use protocol::UpdateDkgSessionKey; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; pub use wrapper::*; @@ -190,6 +191,10 @@ pub struct InitValidator { pub protocol_key: common::PublicKey, /// Serialization of the public session key used in the DKG pub dkg_key: DkgPublicKey, + /// The initial commission rate charged for delegation rewards + pub commission_rate: Decimal, + /// The maximum change allowed per epoch to the commission rate. This is immutable once set here. + pub max_commission_rate_change: Decimal, /// The VP code for validator account pub validator_vp_code: Vec, } diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 992a2146e1..8629c10d18 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -16,3 +16,4 @@ namada_macros = {path = "../macros"} borsh = "0.9.0" sha2 = "0.10.1" thiserror = "1.0.30" +rust_decimal = "1.26.1" diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index ee50943336..d7ad7099fd 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -6,6 +6,7 @@ use namada::ledger::pos::{ unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, + validator_commission_rate_key, validator_max_commission_rate_change_key }; use namada::types::address::Address; use namada::types::transaction::InitValidator; @@ -14,6 +15,7 @@ pub use namada_proof_of_stake::{ epoched, parameters, types, PosActions as PosWrite, PosReadOnly as PosRead, }; +use rust_decimal::Decimal; use super::*; impl Ctx { @@ -81,6 +83,8 @@ impl Ctx { consensus_key, protocol_key, dkg_key, + commission_rate, + max_commission_rate_change, validator_vp_code, }: InitValidator, ) -> EnvResult
{ @@ -98,6 +102,8 @@ impl Ctx { &validator_address, &consensus_key, current_epoch, + commission_rate, + max_commission_rate_change )?; Ok(validator_address) @@ -147,6 +153,24 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_state_key(key), &value) } + fn write_validator_commission_rate( + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error> { + self.write(&validator_commission_rate_key(key), &value) + .into_env_result() + } + + fn write_validator_max_commission_rate_change( + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error> { + self.write(&validator_max_commission_rate_change_key(key), &value) + .into_env_result() + } + fn write_validator_total_deltas( &mut self, key: &Self::Address, From e8c0f6e3ba2cfaa4c75e0788567fb1e05d5e0275 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 27 Sep 2022 11:01:00 -0400 Subject: [PATCH 24/38] epoched commission rate and tx for validator to change their rate --- proof_of_stake/src/lib.rs | 130 +++++++++++++++++++++++++++++-- proof_of_stake/src/types.rs | 2 + shared/src/ledger/pos/mod.rs | 21 +++++ shared/src/ledger/pos/storage.rs | 12 +-- shared/src/ledger/pos/vp.rs | 3 +- tx_prelude/src/proof_of_stake.rs | 8 +- wasm/wasm_source/src/tx_bond.rs | 4 + 7 files changed, 161 insertions(+), 19 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index cca57fb0df..fcfdddf1ee 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -33,7 +33,7 @@ use epoched::{ use parameters::PosParams; use thiserror::Error; use types::{ - ActiveValidator, Bonds, Epoch, GenesisValidator, Slash, SlashType, Slashes, + ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, @@ -133,6 +133,17 @@ pub trait PosReadOnly { &self, key: &Self::Address, ) -> Result, Self::Error>; + /// Read PoS validator's commission rate for delegation rewards + fn read_validator_commission_rate( + &self, + key: &Self::Address, + ) -> Result, Self::Error>; + /// Read PoS validator's maximum change in the commission rate for + /// delegation rewards + fn read_validator_max_commission_rate_change( + &self, + key: &Self::Address, + ) -> Result, Self::Error>; /// Read PoS bond (validator self-bond or a delegation). fn read_bond( &self, @@ -197,7 +208,7 @@ pub trait PosActions: PosReadOnly { fn write_validator_commission_rate( &mut self, key: &Self::Address, - value: Decimal, + value: CommissionRates, ) -> Result<(), Self::Error>; /// Write PoS validator's maximum change in the commission rate per epoch fn write_validator_max_commission_rate_change( @@ -301,8 +312,14 @@ pub trait PosActions: PosReadOnly { self.write_validator_address_raw_hash(address, &consensus_key_clone)?; self.write_validator_total_deltas(address, total_deltas)?; self.write_validator_voting_power(address, voting_power)?; - self.write_validator_commission_rate(address, commission_rate)?; - self.write_validator_max_commission_rate_change(address, max_commission_rate_change)?; + self.write_validator_max_commission_rate_change( + address, + max_commission_rate_change, + )?; + + let commission_rates = + Epoched::init(commission_rate, current_epoch, ¶ms); + self.write_validator_commission_rate(address, commission_rates)?; // Do we need to write the total deltas of all validators? Ok(()) @@ -511,6 +528,76 @@ pub trait PosActions: PosReadOnly { Ok(slashed) } + + /// Change the commission rate of a validator + fn change_validator_commission_rate( + &mut self, + params: &PosParams, + validator: &Self::Address, + change: Decimal, + current_epoch: impl Into, + ) -> Result<(), CommissionRateChangeError> { + let current_epoch = current_epoch.into(); + let max_change = self + .read_validator_max_commission_rate_change(validator) + .map_err(|_| { + CommissionRateChangeError::NoMaxSetInStorage(validator) + }) + .unwrap() + .unwrap(); + + if change == Decimal::ZERO { + return Err(CommissionRateChangeError::ChangeIsZero( + change, + validator.clone(), + )); + } else if change.abs() > max_change { + return Err(CommissionRateChangeError::RateChangeTooLarge( + change, + validator.clone(), + )); + } else { + let mut commission_rates = + match self.read_validator_commission_rate(validator) { + Ok(Some(rates)) => rates, + _ => { + return Err(CommissionRateChangeError::ChangeIsZero( + change, + validator.clone(), + )); + } + }; + let commission_rate = *commission_rates + .get_at_offset( + current_epoch, + DynEpochOffset::PipelineLen, + params, + ) + .expect("Could not find a rate in given epoch"); + if commission_rate + change < Decimal::ZERO { + return Err(CommissionRateChangeError::NegativeRate( + change, + validator.clone(), + )); + } else { + commission_rates.update_from_offset( + |val, _epoch| { + *val += commission_rate; + }, + current_epoch, + DynEpochOffset::PipelineLen, + params, + ); + self.write_validator_commission_rate( + validator, + commission_rates, + ) + .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) + .unwrap(); + } + } + Ok(()) + } } /// PoS system base trait for system initialization on genesis block, updating @@ -612,7 +699,10 @@ pub trait PosBase { /// Read PoS slashes applied to a validator. fn read_validator_slashes(&self, key: &Self::Address) -> Slashes; /// Read PoS validator's commission rate - fn read_validator_commission_rate(&self, key: &Self::Address) -> Decimal; + fn read_validator_commission_rate( + &self, + key: &Self::Address, + ) -> CommissionRates; /// Read PoS validator's maximum commission rate change per epoch fn read_validator_max_commission_rate_change( &self, @@ -660,7 +750,7 @@ pub trait PosBase { fn write_validator_commission_rate( &mut self, key: &Self::Address, - value: &Decimal, + value: &CommissionRates, ); /// Write PoS validator's commission rate. fn write_validator_max_commission_rate_change( @@ -1028,6 +1118,26 @@ where NegativeStake(i128, Address), } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum CommissionRateChangeError
+where + Address: Display + Debug + Clone + PartialOrd + Ord + Hash, +{ + #[error("Unexpected negative commission rate {0} for validator {1}")] + NegativeRate(Decimal, Address), + #[error("Rate change of {0} is too large for validator {1}")] + RateChangeTooLarge(Decimal, Address), + #[error("The rate change is {0} for validator {1}")] + ChangeIsZero(Decimal, Address), + #[error( + "There is no maximum rate change written in storage for validator {0}" + )] + NoMaxSetInStorage(Address), + #[error("Cannot write to storage for validator {0}")] + CannotWrite(Address), +} + struct GenesisData where Validators: Iterator< @@ -1096,7 +1206,7 @@ where { address: Address, consensus_key: ValidatorConsensusKeys, - commission_rate: Decimal, + commission_rate: CommissionRates, max_commission_rate_change: Decimal, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, @@ -1203,6 +1313,10 @@ where }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); + let commission_rate = Epoched::init_at_genesis( + commission_rate.clone(), + current_epoch, + ); let state = Epoched::init_at_genesis( ValidatorState::Candidate, current_epoch, @@ -1231,7 +1345,7 @@ where Ok(GenesisValidatorData { address: address.clone(), consensus_key, - commission_rate: commission_rate.clone(), + commission_rate, max_commission_rate_change: max_commission_rate_change.clone(), state, total_deltas, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 7b9f809e38..4366cf65c9 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -37,6 +37,8 @@ pub type ValidatorSets
= Epoched, OffsetUnbondingLen>; /// Epoched total voting power. pub type TotalVotingPowers = EpochedDelta; +/// Epoched validator commission rate +pub type CommissionRates = Epoched; /// Epoch identifier. Epochs are identified by consecutive natural numbers. /// diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 3f71fa2119..ef0c39444c 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -75,6 +75,9 @@ pub type GenesisValidator = namada_proof_of_stake::types::GenesisValidator< key::common::PublicKey, >; +/// Alias for a PoS type with the same name with concrete type parameters +pub type CommissionRates = namada_proof_of_stake::types::CommissionRates; + impl From for namada_proof_of_stake::types::Epoch { fn from(epoch: Epoch) -> Self { let epoch: u64 = epoch.into(); @@ -175,6 +178,24 @@ mod macros { Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) } + fn read_validator_commission_rate( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_commission_rate_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_max_commission_rate_change( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_max_commission_rate_change_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + fn read_validator_state( &self, key: &Self::Address, diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index ac4b007b63..a5707073ef 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -8,7 +8,7 @@ use namada_proof_of_stake::{types, PosBase}; use super::{ BondId, Bonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, - ADDRESS, + ADDRESS, CommissionRates }; use crate::ledger::storage::types::{decode, encode}; use crate::ledger::storage::{self, Storage, StorageHasher}; @@ -471,7 +471,7 @@ where fn read_validator_commission_rate( &self, key: &Self::Address, - ) -> rust_decimal::Decimal { + ) -> CommissionRates { let (value, _gas) = self.read(&validator_commission_rate_key(key)).unwrap(); value.map(|value| decode(value).unwrap()).unwrap() @@ -510,10 +510,10 @@ where } fn write_validator_commission_rate( - &mut self, - key: &Self::Address, - value: &rust_decimal::Decimal, - ) { + &mut self, + key: &Self::Address, + value: &CommissionRates, + ) { self.write(&validator_commission_rate_key(key), encode(value)) .unwrap(); } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 058acac262..191a2c19a2 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -13,6 +13,7 @@ pub use namada_proof_of_stake::types::{ }; use namada_proof_of_stake::validation::validate; use namada_proof_of_stake::{validation, PosReadOnly}; +use rust_decimal::Decimal; use thiserror::Error; use super::{ @@ -22,7 +23,7 @@ use super::{ total_voting_power_key, unbond_key, validator_consensus_key_key, validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, - Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, + Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, validator_commission_rate_key, max_commission_rate_change_key, }; use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index d7ad7099fd..e929b0fb57 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -154,10 +154,10 @@ impl namada_proof_of_stake::PosActions for Ctx { } fn write_validator_commission_rate( - &mut self, - key: &Self::Address, - value: Decimal, - ) -> Result<(), Self::Error> { + &mut self, + key: &Self::Address, + value: CommissionRates, + ) -> Result<(), Self::Error> { self.write(&validator_commission_rate_key(key), &value) .into_env_result() } diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index bda4818722..fd43138662 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -69,11 +69,15 @@ mod tests { let is_delegation = matches!( &bond.source, Some(source) if *source != bond.validator); let consensus_key = key::testing::keypair_1().ref_to(); + let commission_rate = rust_decimal::Decimal::new(5, 2); + let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); let genesis_validators = [GenesisValidator { address: bond.validator.clone(), tokens: initial_stake, consensus_key, + commission_rate, + max_commission_rate_change, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); From 3947e171ea7fbe3b483152dd6144021ef80939f5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 28 Sep 2022 14:16:35 -0400 Subject: [PATCH 25/38] commission rate: query + refactor validator change tx --- apps/src/bin/anoma-client/cli.rs | 3 + apps/src/lib/cli.rs | 55 +++++++++++++++++ apps/src/lib/client/rpc.rs | 41 +++++++++++++ proof_of_stake/src/lib.rs | 102 +++++++++++++++++-------------- 4 files changed, 154 insertions(+), 47 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index cadc215bd4..9d73199150 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -64,6 +64,9 @@ pub async fn main() -> Result<()> { Sub::QueryVotingPower(QueryVotingPower(args)) => { rpc::query_voting_power(ctx, args).await; } + Sub::QueryCommissionRate(QueryCommissionRate(args)) => { + rpc::query_commission_rate(ctx, args).await; + } Sub::QuerySlashes(QuerySlashes(args)) => { rpc::query_slashes(ctx, args).await; } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 0c4ad9b1a4..da2437bcd0 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -290,6 +290,7 @@ pub mod cmds { QueryBalance(QueryBalance), QueryBonds(QueryBonds), QueryVotingPower(QueryVotingPower), + QueryCommissionRate(QueryCommissionRate), QuerySlashes(QuerySlashes), QueryRawBytes(QueryRawBytes), QueryProposal(QueryProposal), @@ -1016,6 +1017,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryCommissionRate(pub args::QueryCommissionRate); + + impl SubCmd for QueryCommissionRate { + const CMD: &'static str = "commission-rate"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + QueryCommissionRate(args::QueryCommissionRate::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Query commission rate.") + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct QuerySlashes(pub args::QuerySlashes); @@ -2193,6 +2213,41 @@ pub mod args { } } + /// Query PoS commission rate + #[derive(Clone, Debug)] + pub struct QueryCommissionRate { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: Option, + /// Epoch in which to find commission rate + pub epoch: Option, + } + + impl Args for QueryCommissionRate { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let validator = VALIDATOR_OPT.parse(matches); + let epoch = EPOCH.parse(matches); + Self { + query, + validator, + epoch, + } + } + + fn def(app: App) -> App { + app.add_args::() + .arg(VALIDATOR_OPT.def().about( + "The validator's address whose commission rate to query.", + )) + .arg(EPOCH.def().about( + "The epoch at which to query (last committed, if not \ + specified).", + )) + } + } + /// Query PoS slashes #[derive(Clone, Debug)] pub struct QuerySlashes { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7eb580d212..e4e4475087 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1015,6 +1015,47 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { println!("Total voting power: {}", total_voting_power); } +/// Query PoS commssion rate +pub async fn query_commission_rate(ctx: Context, args: args::QueryCommissionRate) { + let epoch = match args.epoch { + Some(epoch) => epoch, + None => query_epoch(args.query.clone()).await, + }; + let client = HttpClient::new(args.query.ledger_address).unwrap(); + + match args.validator { + Some(validator) => { + let validator = ctx.get(&validator); + let validator_commission_key = pos::validator_commission_rate_key(&validator); + let commission_rates = query_storage_value::( + &client, + &validator_commission_key, + ) + .await; + let commission_rates = commission_rates + .expect("No commission rate found "); + match commission_rates.get(epoch) { + Some(rate) => { + println!( + "Validator {} commission rate: {}", + validator.encode(), + *rate + ) + } + None => { + println!("No commission rate found for {} in epoch {}", + validator.encode(), + epoch + ) + } + } + } + None => { + println!("No validator found from the args") + } + } +} + /// Query PoS slashes pub async fn query_slashes(ctx: Context, args: args::QuerySlashes) { let client = HttpClient::new(args.query.ledger_address).unwrap(); diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index fcfdddf1ee..cc1e0246d7 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -534,9 +534,15 @@ pub trait PosActions: PosReadOnly { &mut self, params: &PosParams, validator: &Self::Address, - change: Decimal, + new_rate: Decimal, current_epoch: impl Into, ) -> Result<(), CommissionRateChangeError> { + if new_rate < Decimal::ZERO { + return Err(CommissionRateChangeError::NegativeRate( + new_rate, + validator.clone(), + )); + } let current_epoch = current_epoch.into(); let max_change = self .read_validator_max_commission_rate_change(validator) @@ -545,57 +551,57 @@ pub trait PosActions: PosReadOnly { }) .unwrap() .unwrap(); - - if change == Decimal::ZERO { + let mut commission_rates = + match self.read_validator_commission_rate(validator) { + Ok(Some(rates)) => rates, + _ => { + return Err(CommissionRateChangeError::CannotRead( + validator.clone(), + )); + } + }; + let rate_at_pipeline = *commission_rates + .get_at_offset( + current_epoch, + DynEpochOffset::PipelineLen, + params, + ) + .expect("Could not find a rate in given epoch"); + if new_rate == rate_at_pipeline { return Err(CommissionRateChangeError::ChangeIsZero( - change, validator.clone(), )); - } else if change.abs() > max_change { + } + + let rate_before_pipeline = *commission_rates + .get_at_offset( + current_epoch-1, + DynEpochOffset::PipelineLen, + params, + ) + .expect("Could not find a rate in given epoch"); + let change_from_prev = new_rate - rate_before_pipeline; + if change_from_prev.abs() > max_change { return Err(CommissionRateChangeError::RateChangeTooLarge( - change, + change_from_prev, validator.clone(), )); - } else { - let mut commission_rates = - match self.read_validator_commission_rate(validator) { - Ok(Some(rates)) => rates, - _ => { - return Err(CommissionRateChangeError::ChangeIsZero( - change, - validator.clone(), - )); - } - }; - let commission_rate = *commission_rates - .get_at_offset( - current_epoch, - DynEpochOffset::PipelineLen, - params, - ) - .expect("Could not find a rate in given epoch"); - if commission_rate + change < Decimal::ZERO { - return Err(CommissionRateChangeError::NegativeRate( - change, - validator.clone(), - )); - } else { - commission_rates.update_from_offset( - |val, _epoch| { - *val += commission_rate; - }, - current_epoch, - DynEpochOffset::PipelineLen, - params, - ); - self.write_validator_commission_rate( - validator, - commission_rates, - ) - .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) - .unwrap(); - } } + commission_rates.update_from_offset( + |val, _epoch| { + *val = new_rate; + }, + current_epoch, + DynEpochOffset::PipelineLen, + params, + ); + self.write_validator_commission_rate( + validator, + commission_rates, + ) + .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) + .unwrap(); + Ok(()) } } @@ -1128,14 +1134,16 @@ where NegativeRate(Decimal, Address), #[error("Rate change of {0} is too large for validator {1}")] RateChangeTooLarge(Decimal, Address), - #[error("The rate change is {0} for validator {1}")] - ChangeIsZero(Decimal, Address), + #[error("The rate change is 0 for validator {0}")] + ChangeIsZero(Address), #[error( "There is no maximum rate change written in storage for validator {0}" )] NoMaxSetInStorage(Address), #[error("Cannot write to storage for validator {0}")] CannotWrite(Address), + #[error("Cannot read storage for validator {0}")] + CannotRead(Address), } struct GenesisData From 5b1a09d57d33c52f0e5c611c963db46e5616effb Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 14:19:06 -0400 Subject: [PATCH 26/38] add missing commission rate-related instances --- proof_of_stake/src/lib.rs | 5 +++++ shared/src/ledger/pos/vp.rs | 6 ++++-- wasm/wasm_source/src/tx_unbond.rs | 4 ++++ wasm/wasm_source/src/tx_withdraw.rs | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index cc1e0246d7..0861245141 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -846,6 +846,11 @@ pub trait PosBase { self.write_validator_total_deltas(address, &total_deltas); self.write_validator_voting_power(address, &voting_power); self.write_bond(&bond_id, &bond); + self.write_validator_commission_rate(address, &commission_rate); + self.write_validator_max_commission_rate_change( + address, + &max_commission_rate_change, + ); } self.write_validator_set(&validator_set); self.write_total_voting_power(&total_voting_power); diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 191a2c19a2..185a1bbc4d 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -20,10 +20,12 @@ use super::{ bond_key, is_bond_key, is_params_key, is_total_voting_power_key, is_unbond_key, is_validator_set_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, - total_voting_power_key, unbond_key, validator_consensus_key_key, + total_voting_power_key, unbond_key, validator_commission_rate_key, + validator_consensus_key_key, validator_max_commission_rate_change_key, validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, - Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, validator_commission_rate_key, max_commission_rate_change_key, + CommissionRates, Unbonds, ValidatorConsensusKeys, ValidatorSets, + ValidatorTotalDeltas, }; use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index df4299deab..3b9f9bc76e 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -68,6 +68,8 @@ mod tests { let is_delegation = matches!( &unbond.source, Some(source) if *source != unbond.validator); let consensus_key = key::testing::keypair_1().ref_to(); + let commission_rate = rust_decimal::Decimal::new(5, 2); + let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); let genesis_validators = [GenesisValidator { address: unbond.validator.clone(), @@ -79,6 +81,8 @@ mod tests { initial_stake }, consensus_key, + commission_rate, + max_commission_rate_change, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 4cc0e35691..3525b7b7cc 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -74,6 +74,8 @@ mod tests { let is_delegation = matches!( &withdraw.source, Some(source) if *source != withdraw.validator); let consensus_key = key::testing::keypair_1().ref_to(); + let commission_rate = rust_decimal::Decimal::new(5, 2); + let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); let genesis_validators = [GenesisValidator { address: withdraw.validator.clone(), @@ -86,6 +88,8 @@ mod tests { initial_stake }, consensus_key, + commission_rate, + max_commission_rate_change, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); From 9d83fc86fc3779534e9f804c8188bfbf9e9b8ad2 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 14:21:43 -0400 Subject: [PATCH 27/38] include and update `rust_decimal` --- Cargo.lock | 15 +++++++++++++++ apps/Cargo.toml | 2 ++ apps/src/lib/config/genesis.rs | 2 ++ proof_of_stake/Cargo.toml | 2 ++ proof_of_stake/src/lib.rs | 1 + proof_of_stake/src/types.rs | 1 + wasm/Cargo.lock | 14 ++++++++++++++ wasm/wasm_source/Cargo.toml | 3 ++- wasm/wasm_source/src/tx_bond.rs | 1 + wasm_for_tests/wasm_source/Cargo.lock | 14 ++++++++++++++ 10 files changed, 54 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index edad87b845..dbff60b458 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3020,6 +3020,8 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", + "rust_decimal", + "rust_decimal_macros", "serde 1.0.145", "serde_bytes", "serde_json", @@ -3079,6 +3081,8 @@ dependencies = [ "borsh", "derivative", "proptest", + "rust_decimal", + "rust_decimal_macros", "thiserror", ] @@ -4327,10 +4331,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", + "borsh", "num-traits 0.2.15", "serde 1.0.145", ] +[[package]] +name = "rust_decimal_macros" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 45cdb9aa5e..af18e076c0 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -142,6 +142,8 @@ tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} websocket = "0.26.2" winapi = "0.3.9" bimap = {version = "0.6.2", features = ["serde"]} +rust_decimal = "1.26.1" +rust_decimal_macros = "1.26.1" [dev-dependencies] namada = {path = "../shared", features = ["testing", "wasm-runtime"]} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 89b07b14af..03a614abde 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -36,6 +36,7 @@ pub mod genesis_config { use namada::types::key::*; use namada::types::time::Rfc3339String; use namada::types::{storage, token}; + use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -721,6 +722,7 @@ pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { pub fn genesis() -> Genesis { use namada::ledger::parameters::EpochDuration; use namada::types::address; + use rust_decimal_macros::dec; use crate::wallet; diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 82522bc3d6..7da1405161 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -19,5 +19,7 @@ thiserror = "1.0.30" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} derivative = "2.2.0" +rust_decimal = { version = "1.26.1", features = ["borsh"] } +rust_decimal_macros = "1.26.1" [dev-dependencies] diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 0861245141..089df36cfb 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -31,6 +31,7 @@ use epoched::{ DynEpochOffset, EpochOffset, Epoched, EpochedDelta, OffsetPipelineLen, }; use parameters::PosParams; +use rust_decimal::Decimal; use thiserror::Error; use types::{ ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 4366cf65c9..a8cfcdbe1c 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -9,6 +9,7 @@ use std::num::TryFromIntError; use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::Decimal; use crate::epoched::{ Epoched, EpochedDelta, OffsetPipelineLen, OffsetUnbondingLen, diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f9030a471a..e95f3b753f 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1415,6 +1415,8 @@ dependencies = [ "borsh", "derivative", "proptest", + "rust_decimal", + "rust_decimal_macros", "thiserror", ] @@ -1445,6 +1447,7 @@ dependencies = [ "namada", "namada_macros", "namada_vm_env", + "rust_decimal", "sha2 0.10.6", "thiserror", ] @@ -1984,10 +1987,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", + "borsh", "num-traits", "serde", ] +[[package]] +name = "rust_decimal_macros" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 7f48f2aea6..e438f45106 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -35,7 +35,7 @@ namada_tx_prelude = {path = "../../tx_prelude", optional = true} namada_vp_prelude = {path = "../../vp_prelude", optional = true} borsh = "0.9.0" once_cell = {version = "1.8.0", optional = true} -rust_decimal = {version = "1.14.3", optional = true} +rust_decimal = {version = "1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } @@ -48,3 +48,4 @@ namada_vp_prelude = {path = "../../vp_prelude"} proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} +rust_decimal = "1.26.1" \ No newline at end of file diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index fd43138662..38002d2495 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -39,6 +39,7 @@ mod tests { staking_token_address, BondId, GenesisValidator, PosVP, }; use proptest::prelude::*; + use rust_decimal; use super::*; diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index b82f3b3d59..f33d73cb36 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1415,6 +1415,8 @@ dependencies = [ "borsh", "derivative", "proptest", + "rust_decimal", + "rust_decimal_macros", "thiserror", ] @@ -1445,6 +1447,7 @@ dependencies = [ "namada", "namada_macros", "namada_vm_env", + "rust_decimal", "sha2 0.10.6", "thiserror", ] @@ -1978,10 +1981,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", + "borsh", "num-traits", "serde", ] +[[package]] +name = "rust_decimal_macros" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc-demangle" version = "0.1.21" From fa06a5d57597317bf9f235126dab89844db4158e Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 14:23:29 -0400 Subject: [PATCH 28/38] bug fix from splitting this PR off of #388 --- shared/src/ledger/pos/storage.rs | 12 +++++++----- tx_prelude/src/proof_of_stake.rs | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index a5707073ef..aaa48d164e 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -171,7 +171,7 @@ pub fn is_validator_max_commission_rate_change_key( } /// Storage key for validator's consensus key. -pub fn validator_consensus_key_key(validator: &Address) -> Key { +pub fn validator_state_key(validator: &Address) -> Key { validator_prefix(validator) .push(&VALIDATOR_STATE_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") @@ -474,17 +474,19 @@ where ) -> CommissionRates { let (value, _gas) = self.read(&validator_commission_rate_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()).unwrap() + decode(value.unwrap()).unwrap() } fn read_validator_max_commission_rate_change( &self, key: &Self::Address, ) -> rust_decimal::Decimal { - let (value, _gas) = - self.read(&validator_commission_rate_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()).unwrap() + let (value, _gas) = self + .read(&validator_max_commission_rate_change_key(key)) + .unwrap(); + decode(value.unwrap()).unwrap() } + fn read_validator_set(&self) -> ValidatorSets { let (value, _gas) = self.read(&validator_set_key()).unwrap(); decode(value.unwrap()).unwrap() diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index e929b0fb57..70b7f991c5 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -159,7 +159,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: CommissionRates, ) -> Result<(), Self::Error> { self.write(&validator_commission_rate_key(key), &value) - .into_env_result() } fn write_validator_max_commission_rate_change( @@ -168,7 +167,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: Decimal, ) -> Result<(), Self::Error> { self.write(&validator_max_commission_rate_change_key(key), &value) - .into_env_result() } fn write_validator_total_deltas( From 7a7d82afd9d3c16f74c880739a40e910c51c2e0b Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 14:28:59 -0400 Subject: [PATCH 29/38] cleaning: incl fmt + clippy --- apps/src/lib/cli.rs | 32 ++++++++++++--------- apps/src/lib/client/rpc.rs | 15 ++++++---- apps/src/lib/client/tx.rs | 18 +++++++----- proof_of_stake/src/lib.rs | 44 ++++++++++++----------------- shared/src/ledger/pos/storage.rs | 11 +++++--- shared/src/types/transaction/mod.rs | 3 +- tx_prelude/src/proof_of_stake.rs | 16 +++++------ 7 files changed, 75 insertions(+), 64 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index da2437bcd0..0ac6e3ac7c 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1026,7 +1026,7 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).map(|matches| { QueryCommissionRate(args::QueryCommissionRate::parse(matches)) - }) + }) } fn def() -> App { @@ -1299,9 +1299,6 @@ pub mod args { use namada::types::token; use namada::types::transaction::GasLimit; use rust_decimal::Decimal; - use serde::Deserialize; - use tendermint::Timeout; - use tendermint_config::net::Address as TendermintAddress; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; @@ -1362,7 +1359,8 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("ledger-address"); const LOCALHOST: ArgFlag = flag("localhost"); - const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); + const MAX_COMMISSION_RATE_CHANGE: Arg = + arg("max-commission-rate-change"); const MODE: ArgOpt = arg_opt("mode"); const NET_ADDRESS: Arg = arg("net-address"); const NFT_ADDRESS: Arg
= arg("nft-address"); @@ -1630,7 +1628,8 @@ pub mod args { let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); let protocol_key = PROTOCOL_KEY.parse(matches); let commission_rate = COMMISSION_RATE.parse(matches); - let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); + let max_commission_rate_change = + MAX_COMMISSION_RATE_CHANGE.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { @@ -1669,10 +1668,13 @@ pub mod args { one will be generated if none given.", )) .arg(COMMISSION_RATE.def().about( - "The commission rate charged by the validator for delegation rewards. This is a required parameter.", + "The commission rate charged by the validator for \ + delegation rewards. This is a required parameter.", )) .arg(MAX_COMMISSION_RATE_CHANGE.def().about( - "The maximum change per epoch in the commission rate charged by the validator for delegation rewards. This is a required parameter.", + "The maximum change per epoch in the commission rate \ + charged by the validator for delegation rewards. This is \ + a required parameter.", )) .arg(VALIDATOR_CODE_PATH.def().about( "The path to the validity predicate WASM code to be used \ @@ -2247,7 +2249,7 @@ pub mod args { )) } } - + /// Query PoS slashes #[derive(Clone, Debug)] pub struct QuerySlashes { @@ -2758,7 +2760,8 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let commission_rate = COMMISSION_RATE.parse(matches); - let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); + let max_commission_rate_change = + MAX_COMMISSION_RATE_CHANGE.parse(matches); let net_address = NET_ADDRESS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let key_scheme = SCHEME.parse(matches); @@ -2768,7 +2771,7 @@ pub mod args { unsafe_dont_encrypt, key_scheme, commission_rate, - max_commission_rate_change + max_commission_rate_change, } } @@ -2780,10 +2783,13 @@ pub mod args { but you can configure a different value.", )) .arg(COMMISSION_RATE.def().about( - "The commission rate charged by the validator for delegation rewards. This is a required parameter.", + "The commission rate charged by the validator for \ + delegation rewards. This is a required parameter.", )) .arg(MAX_COMMISSION_RATE_CHANGE.def().about( - "The maximum change per epoch in the commission rate charged by the validator for delegation rewards. This is a required parameter.", + "The maximum change per epoch in the commission rate \ + charged by the validator for delegation rewards. This is \ + a required parameter.", )) .arg(UNSAFE_DONT_ENCRYPT.def().about( "UNSAFE: Do not encrypt the generated keypairs. Do not \ diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index e4e4475087..8d7cd9cded 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1016,7 +1016,10 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { } /// Query PoS commssion rate -pub async fn query_commission_rate(ctx: Context, args: args::QueryCommissionRate) { +pub async fn query_commission_rate( + ctx: Context, + args: args::QueryCommissionRate, +) { let epoch = match args.epoch { Some(epoch) => epoch, None => query_epoch(args.query.clone()).await, @@ -1026,14 +1029,15 @@ pub async fn query_commission_rate(ctx: Context, args: args::QueryCommissionRate match args.validator { Some(validator) => { let validator = ctx.get(&validator); - let validator_commission_key = pos::validator_commission_rate_key(&validator); + let validator_commission_key = + pos::validator_commission_rate_key(&validator); let commission_rates = query_storage_value::( &client, &validator_commission_key, ) .await; - let commission_rates = commission_rates - .expect("No commission rate found "); + let commission_rates = + commission_rates.expect("No commission rate found "); match commission_rates.get(epoch) { Some(rate) => { println!( @@ -1043,7 +1047,8 @@ pub async fn query_commission_rate(ctx: Context, args: args::QueryCommissionRate ) } None => { - println!("No commission rate found for {} in epoch {}", + println!( + "No commission rate found for {} in epoch {}", validator.encode(), epoch ) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 14c4f6ed4d..7b98ceaf60 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -26,10 +26,6 @@ use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, storage, token}; use namada::{ledger, vm}; use rust_decimal::Decimal; -use tendermint_config::net::Address as TendermintAddress; -use tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use tendermint_rpc::query::{EventType, Query}; -use tendermint_rpc::{Client, HttpClient}; use super::rpc; use crate::cli::context::WalletAddress; @@ -235,10 +231,18 @@ pub async fn submit_init_validator( // Validate the commission rate data if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { - eprintln!("The validator commission rate must not exceed 1.0 or 100%, and it must be 0 or positive"); + eprintln!( + "The validator commission rate must not exceed 1.0 or 100%, and \ + it must be 0 or positive" + ); } - if max_commission_rate_change > Decimal::ONE || max_commission_rate_change < Decimal::ZERO { - eprintln!("The validator maximum change in commission rate per epoch must not exceed 1.0 or 100%"); + if max_commission_rate_change > Decimal::ONE + || max_commission_rate_change < Decimal::ZERO + { + eprintln!( + "The validator maximum change in commission rate per epoch must \ + not exceed 1.0 or 100%" + ); } // Validate the validator VP code if let Err(err) = vm::validate_untrusted_wasm(&validator_vp_code) { diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 089df36cfb..1aad8b6a6e 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -34,10 +34,11 @@ use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, - TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, - ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, - ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, + ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, + SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, + ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, + ValidatorState, ValidatorStates, ValidatorTotalDeltas, + ValidatorVotingPowers, VotingPower, VotingPowerDelta, }; use crate::btree_set::BTreeSetShims; @@ -282,7 +283,7 @@ pub trait PosActions: PosReadOnly { consensus_key: &Self::PublicKey, current_epoch: impl Into, commission_rate: Decimal, - max_commission_rate_change: Decimal + max_commission_rate_change: Decimal, ) -> Result<(), Self::BecomeValidatorError> { let current_epoch = current_epoch.into(); let params = self.read_pos_params()?; @@ -297,7 +298,7 @@ pub trait PosActions: PosReadOnly { total_deltas, voting_power, commission_rate, - max_commission_rate_change + max_commission_rate_change, } = become_validator( ¶ms, address, @@ -305,7 +306,7 @@ pub trait PosActions: PosReadOnly { &mut validator_set, current_epoch, commission_rate, - max_commission_rate_change + max_commission_rate_change, ); self.write_validator_consensus_key(address, consensus_key)?; self.write_validator_state(address, state)?; @@ -562,11 +563,7 @@ pub trait PosActions: PosReadOnly { } }; let rate_at_pipeline = *commission_rates - .get_at_offset( - current_epoch, - DynEpochOffset::PipelineLen, - params, - ) + .get_at_offset(current_epoch, DynEpochOffset::PipelineLen, params) .expect("Could not find a rate in given epoch"); if new_rate == rate_at_pipeline { return Err(CommissionRateChangeError::ChangeIsZero( @@ -576,7 +573,7 @@ pub trait PosActions: PosReadOnly { let rate_before_pipeline = *commission_rates .get_at_offset( - current_epoch-1, + current_epoch - 1, DynEpochOffset::PipelineLen, params, ) @@ -596,13 +593,10 @@ pub trait PosActions: PosReadOnly { DynEpochOffset::PipelineLen, params, ); - self.write_validator_commission_rate( - validator, - commission_rates, - ) - .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) - .unwrap(); - + self.write_validator_commission_rate(validator, commission_rates) + .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) + .unwrap(); + Ok(()) } } @@ -1327,10 +1321,8 @@ where }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); - let commission_rate = Epoched::init_at_genesis( - commission_rate.clone(), - current_epoch, - ); + let commission_rate = + Epoched::init_at_genesis(*commission_rate, current_epoch); let state = Epoched::init_at_genesis( ValidatorState::Candidate, current_epoch, @@ -1360,7 +1352,7 @@ where address: address.clone(), consensus_key, commission_rate, - max_commission_rate_change: max_commission_rate_change.clone(), + max_commission_rate_change: *max_commission_rate_change, state, total_deltas, voting_power, @@ -1554,7 +1546,7 @@ where total_deltas, voting_power, commission_rate, - max_commission_rate_change + max_commission_rate_change, } } diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index aaa48d164e..be8709f285 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -7,8 +7,8 @@ use namada_proof_of_stake::types::{ use namada_proof_of_stake::{types, PosBase}; use super::{ - BondId, Bonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, - ADDRESS, CommissionRates + BondId, Bonds, CommissionRates, ValidatorConsensusKeys, ValidatorSets, + ValidatorTotalDeltas, ADDRESS, }; use crate::ledger::storage::types::{decode, encode}; use crate::ledger::storage::{self, Storage, StorageHasher}; @@ -525,9 +525,12 @@ where key: &Self::Address, value: &rust_decimal::Decimal, ) { - self.write(&validator_max_commission_rate_change_key(key), encode(value)) + self.write( + &validator_max_commission_rate_change_key(key), + encode(value), + ) .unwrap(); -} + } fn write_validator_consensus_key( &mut self, diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 65a76a4661..4f1769eec2 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -193,7 +193,8 @@ pub struct InitValidator { pub dkg_key: DkgPublicKey, /// The initial commission rate charged for delegation rewards pub commission_rate: Decimal, - /// The maximum change allowed per epoch to the commission rate. This is immutable once set here. + /// The maximum change allowed per epoch to the commission rate. This is + /// immutable once set here. pub max_commission_rate_change: Decimal, /// The VP code for validator account pub validator_vp_code: Vec, diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 70b7f991c5..e732597461 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -3,10 +3,10 @@ pub use namada::ledger::pos::*; use namada::ledger::pos::{ bond_key, namada_proof_of_stake, params_key, total_voting_power_key, - unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, + unbond_key, validator_address_raw_hash_key, validator_commission_rate_key, + validator_consensus_key_key, validator_max_commission_rate_change_key, validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, - validator_commission_rate_key, validator_max_commission_rate_change_key }; use namada::types::address::Address; use namada::types::transaction::InitValidator; @@ -14,8 +14,8 @@ use namada::types::{key, token}; pub use namada_proof_of_stake::{ epoched, parameters, types, PosActions as PosWrite, PosReadOnly as PosRead, }; - use rust_decimal::Decimal; + use super::*; impl Ctx { @@ -103,7 +103,7 @@ impl Ctx { &consensus_key, current_epoch, commission_rate, - max_commission_rate_change + max_commission_rate_change, )?; Ok(validator_address) @@ -162,10 +162,10 @@ impl namada_proof_of_stake::PosActions for Ctx { } fn write_validator_max_commission_rate_change( - &mut self, - key: &Self::Address, - value: Decimal, - ) -> Result<(), Self::Error> { + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error> { self.write(&validator_max_commission_rate_change_key(key), &value) } From 32722b1c10274d6afcc2416d567fd6d380f6702d Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 16:06:21 -0400 Subject: [PATCH 30/38] init validator: default values for commission rate attributes (bug fix) --- apps/src/lib/cli.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 0ac6e3ac7c..162ccd9393 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1299,6 +1299,7 @@ pub mod args { use namada::types::token; use namada::types::transaction::GasLimit; use rust_decimal::Decimal; + use rust_decimal_macros::dec; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; @@ -1327,7 +1328,8 @@ pub mod args { const CHAIN_ID_PREFIX: Arg = arg("chain-prefix"); const CODE_PATH: Arg = arg("code-path"); const CODE_PATH_OPT: ArgOpt = CODE_PATH.opt(); - const COMMISSION_RATE: Arg = arg("commission-rate"); + const COMMISSION_RATE: ArgDefault = + arg_default("commission-rate", DefaultFn(|| dec!(0.05))); const CONSENSUS_TIMEOUT_COMMIT: ArgDefault = arg_default( "consensus-timeout-commit", DefaultFn(|| Timeout::from_str("1s").unwrap()), @@ -1359,8 +1361,8 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("ledger-address"); const LOCALHOST: ArgFlag = flag("localhost"); - const MAX_COMMISSION_RATE_CHANGE: Arg = - arg("max-commission-rate-change"); + const MAX_COMMISSION_RATE_CHANGE: ArgDefault = + arg_default("max-commission-rate-change", DefaultFn(|| dec!(0.01))); const MODE: ArgOpt = arg_opt("mode"); const NET_ADDRESS: Arg = arg("net-address"); const NFT_ADDRESS: Arg
= arg("nft-address"); From 758da4104f8ecff0f2217f1dd76dac0a0f0d4bb7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 8 Sep 2022 20:19:56 +0300 Subject: [PATCH 31/38] remove BasisPoints and change relevant parameters to Decimal type --- apps/src/lib/config/genesis.rs | 27 ++++++++--------- proof_of_stake/src/parameters.rs | 38 +++++++++++++----------- proof_of_stake/src/rewards.rs | 0 proof_of_stake/src/types.rs | 51 +++----------------------------- 4 files changed, 36 insertions(+), 80 deletions(-) create mode 100644 proof_of_stake/src/rewards.rs diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 03a614abde..8126241947 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -29,7 +29,6 @@ pub mod genesis_config { use eyre::Context; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::{EpochDuration, Parameters}; - use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::address::Address; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -248,21 +247,21 @@ pub mod genesis_config { pub unbonding_len: u64, // Votes per token (in basis points). // XXX: u64 doesn't work with toml-rs! - pub votes_per_token: u64, + pub votes_per_token: Decimal, // Reward for proposing a block. // XXX: u64 doesn't work with toml-rs! - pub block_proposer_reward: u64, + pub block_proposer_reward: Decimal, // Reward for voting on a block. // XXX: u64 doesn't work with toml-rs! - pub block_vote_reward: u64, + pub block_vote_reward: Decimal, // Portion of a validator's stake that should be slashed on a // duplicate vote (in basis points). // XXX: u64 doesn't work with toml-rs! - pub duplicate_vote_slash_rate: u64, + pub duplicate_vote_slash_rate: Decimal, // Portion of a validator's stake that should be slashed on a // light client attack (in basis points). // XXX: u64 doesn't work with toml-rs! - pub light_client_attack_slash_rate: u64, + pub light_client_attack_slash_rate: Decimal, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -547,17 +546,15 @@ pub mod genesis_config { max_validator_slots: config.pos_params.max_validator_slots, pipeline_len: config.pos_params.pipeline_len, unbonding_len: config.pos_params.unbonding_len, - votes_per_token: BasisPoints::new( - config.pos_params.votes_per_token, - ), + votes_per_token: config.pos_params.votes_per_token, block_proposer_reward: config.pos_params.block_proposer_reward, block_vote_reward: config.pos_params.block_vote_reward, - duplicate_vote_slash_rate: BasisPoints::new( - config.pos_params.duplicate_vote_slash_rate, - ), - light_client_attack_slash_rate: BasisPoints::new( - config.pos_params.light_client_attack_slash_rate, - ), + duplicate_vote_slash_rate: config + .pos_params + .duplicate_vote_slash_rate, + light_client_attack_slash_rate: config + .pos_params + .light_client_attack_slash_rate, }; let mut genesis = Genesis { diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 1c265bf1a8..944981d6ad 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -1,10 +1,11 @@ //! Proof-of-Stake system parameters use borsh::{BorshDeserialize, BorshSerialize}; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; use thiserror::Error; -use crate::types::BasisPoints; - /// Proof-of-Stake system parameters #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] pub struct PosParams { @@ -20,18 +21,18 @@ pub struct PosParams { pub unbonding_len: u64, /// Used in validators' voting power calculation. Given in basis points /// (voting power per ten thousand tokens). - pub votes_per_token: BasisPoints, + pub votes_per_token: Decimal, /// Amount of tokens rewarded to a validator for proposing a block - pub block_proposer_reward: u64, + pub block_proposer_reward: Decimal, /// Amount of tokens rewarded to each validator that voted on a block /// proposal - pub block_vote_reward: u64, + pub block_vote_reward: Decimal, /// Portion of validator's stake that should be slashed on a duplicate /// vote. Given in basis points (slashed amount per ten thousand tokens). - pub duplicate_vote_slash_rate: BasisPoints, + pub duplicate_vote_slash_rate: Decimal, /// Portion of validator's stake that should be slashed on a light client /// attack. Given in basis points (slashed amount per ten thousand tokens). - pub light_client_attack_slash_rate: BasisPoints, + pub light_client_attack_slash_rate: Decimal, } impl Default for PosParams { @@ -40,14 +41,15 @@ impl Default for PosParams { max_validator_slots: 128, pipeline_len: 2, unbonding_len: 6, - // 1 voting power per 1000 tokens - votes_per_token: BasisPoints::new(10), - block_proposer_reward: 100, - block_vote_reward: 1, + // 1 voting power per 1 fundamental token (10^6 per NAM or 1 per + // namnam) + votes_per_token: dec!(1.0), + block_proposer_reward: dec!(0.0625), + block_vote_reward: dec!(0.05), // slash 5% - duplicate_vote_slash_rate: BasisPoints::new(500), + duplicate_vote_slash_rate: dec!(0.05), // slash 5% - light_client_attack_slash_rate: BasisPoints::new(500), + light_client_attack_slash_rate: dec!(0.05), } } } @@ -61,7 +63,7 @@ pub enum ValidationError { )] TotalVotingPowerTooLarge(u64), #[error("Votes per token cannot be greater than 1, got {0}")] - VotesPerTokenGreaterThanOne(BasisPoints), + VotesPerTokenGreaterThanOne(Decimal), #[error("Pipeline length must be >= 2, got {0}")] PipelineLenTooShort(u64), #[error( @@ -101,8 +103,8 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows - let max_total_voting_power = self.max_validator_slots - * (self.votes_per_token * TOKEN_MAX_AMOUNT); + let max_total_voting_power = Decimal::from(self.max_validator_slots) + * self.votes_per_token * Decimal::from(TOKEN_MAX_AMOUNT); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { @@ -117,7 +119,7 @@ impl PosParams { } // Check that there is no more than 1 vote per token - if self.votes_per_token > BasisPoints::new(10_000) { + if self.votes_per_token > dec!(1.0) { errors.push(ValidationError::VotesPerTokenGreaterThanOne( self.votes_per_token, )) @@ -170,7 +172,7 @@ pub mod testing { max_validator_slots, pipeline_len, unbonding_len, - votes_per_token: BasisPoints::new(votes_per_token), + votes_per_token: Decimal::from(votes_per_token) / dec!(10_000), // The rest of the parameters that are not being used in the PoS // VP are constant for now ..Default::default() diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index a8cfcdbe1c..7d92c63750 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -6,9 +6,10 @@ use std::convert::TryFrom; use std::fmt::Display; use std::hash::Hash; use std::num::TryFromIntError; -use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; use crate::epoched::{ @@ -326,7 +327,7 @@ pub struct Slash { /// A type of slashsable event. pub r#type: SlashType, /// A rate is the portion of staked tokens that are slashed. - pub rate: BasisPoints, + pub rate: Decimal, } /// Slashes applied to validator, to punish byzantine behavior by removing @@ -342,23 +343,6 @@ pub enum SlashType { LightClientAttack, } -/// ‱ (Parts per ten thousand). This can be multiplied by any type that -/// implements [`Into`] or [`Into`]. -#[derive( - Debug, - Clone, - Copy, - BorshDeserialize, - BorshSerialize, - BorshSchema, - PartialOrd, - Ord, - PartialEq, - Eq, - Hash, -)] -pub struct BasisPoints(u64); - /// Derive Tendermint raw hash from the public key pub trait PublicKeyTmRawHash { /// Derive Tendermint raw hash from the public key @@ -720,7 +704,7 @@ where impl SlashType { /// Get the slash rate applicable to the given slash type from the PoS /// parameters. - pub fn get_slash_rate(&self, params: &PosParams) -> BasisPoints { + pub fn get_slash_rate(&self, params: &PosParams) -> Decimal { match self { SlashType::DuplicateVote => params.duplicate_vote_slash_rate, SlashType::LightClientAttack => { @@ -739,35 +723,8 @@ impl Display for SlashType { } } -impl BasisPoints { - /// Initialize basis points from an integer. - pub fn new(value: u64) -> Self { - Self(value) - } -} - -impl Display for BasisPoints { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}‱", self.0) - } -} - -impl Mul for BasisPoints { - type Output = u64; - - fn mul(self, rhs: u64) -> Self::Output { - // TODO checked arithmetics - rhs * self.0 / 10_000 - } } -impl Mul for BasisPoints { - type Output = i128; - - fn mul(self, rhs: i128) -> Self::Output { - // TODO checked arithmetics - rhs * self.0 as i128 / 10_000 - } } #[cfg(test)] From 0949b61747defc40528310f93633365c42109ff9 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 8 Sep 2022 20:28:39 +0300 Subject: [PATCH 32/38] add fns to multiply Decimal and integer type, return truncated integer --- apps/src/lib/client/rpc.rs | 5 +++-- proof_of_stake/src/lib.rs | 2 +- proof_of_stake/src/types.rs | 16 ++++++++++++++-- proof_of_stake/src/validation.rs | 27 +++++++++++++++------------ shared/src/ledger/governance/utils.rs | 4 +++- tests/src/native_vp/pos.rs | 1 + 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 8d7cd9cded..4cac88820d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -18,7 +18,7 @@ use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ - Epoch as PosEpoch, VotingPower, WeightedValidator, + decimal_mult_u64, Epoch as PosEpoch, VotingPower, WeightedValidator, }; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, @@ -1234,7 +1234,8 @@ fn apply_slashes( .unwrap(); } let raw_delta: u64 = delta.into(); - let current_slashed = token::Amount::from(slash.rate * raw_delta); + let current_slashed = + token::Amount::from(decimal_mult_u64(slash.rate, raw_delta)); slashed += current_slashed; delta -= current_slashed; } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 1aad8b6a6e..9c0afd8617 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -34,7 +34,7 @@ use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, + decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 7d92c63750..02a5e8a30c 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -723,8 +723,20 @@ impl Display for SlashType { } } -} - +/// Multiply a value of type Decimal with one of type u64 and then return the +/// truncated u64 +pub fn decimal_mult_u64(dec: Decimal, int: u64) -> u64 { + let prod = dec * Decimal::from(int); + // truncate the number to the floor + prod.to_u64().expect("Product is out of bounds") +} + +/// Multiply a value of type Decimal with one of type i128 and then return the +/// truncated i128 +pub fn decimal_mult_i128(dec: Decimal, int: i128) -> i128 { + let prod = dec * Decimal::from(int); + // truncate the number to the floor + prod.to_i128().expect("Product is out of bounds") } #[cfg(test)] diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 13356cbb55..65708c2570 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -16,10 +16,11 @@ use crate::btree_set::BTreeSetShims; use crate::epoched::DynEpochOffset; use crate::parameters::PosParams; use crate::types::{ - BondId, Bonds, Epoch, PublicKeyTmRawHash, Slash, Slashes, - TotalVotingPowers, Unbonds, ValidatorConsensusKeys, ValidatorSets, - ValidatorState, ValidatorStates, ValidatorTotalDeltas, - ValidatorVotingPowers, VotingPower, VotingPowerDelta, WeightedValidator, + decimal_mult_i128, decimal_mult_u64, BondId, Bonds, Epoch, + PublicKeyTmRawHash, Slash, Slashes, TotalVotingPowers, Unbonds, + ValidatorConsensusKeys, ValidatorSets, ValidatorState, ValidatorStates, + ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, + WeightedValidator, }; #[allow(missing_docs)] @@ -1754,8 +1755,9 @@ where for slash in &slashes { if slash.epoch >= *start_epoch { let raw_delta: i128 = (*delta).into(); - let current_slashed = - TokenChange::from(slash.rate * raw_delta); + let current_slashed = TokenChange::from( + decimal_mult_i128(slash.rate, raw_delta), + ); *delta -= current_slashed; } } @@ -1820,7 +1822,7 @@ where if slash.epoch >= *start_epoch { let raw_delta: u64 = delta.into(); let current_slashed = TokenAmount::from( - slash.rate * raw_delta, + decimal_mult_u64(slash.rate, raw_delta), ); delta -= current_slashed; } @@ -1852,7 +1854,7 @@ where if slash.epoch >= *start_epoch { let raw_delta: u64 = delta.into(); let current_slashed = TokenAmount::from( - slash.rate * raw_delta, + decimal_mult_u64(slash.rate, raw_delta), ); delta -= current_slashed; } @@ -1946,8 +1948,9 @@ where && slash.epoch <= *end_epoch { let raw_delta: i128 = (*delta).into(); - let current_slashed = - TokenChange::from(slash.rate * raw_delta); + let current_slashed = TokenChange::from( + decimal_mult_i128(slash.rate, raw_delta), + ); *delta -= current_slashed; } } @@ -1983,7 +1986,7 @@ where { let raw_delta: u64 = delta.into(); let current_slashed = TokenAmount::from( - slash.rate * raw_delta, + decimal_mult_u64(slash.rate, raw_delta), ); delta -= current_slashed; } @@ -2016,7 +2019,7 @@ where { let raw_delta: u64 = delta.into(); let current_slashed = TokenAmount::from( - slash.rate * raw_delta, + decimal_mult_u64(slash.rate, raw_delta), ); delta -= current_slashed; } diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 152a629575..8bbea9dbea 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -8,6 +8,7 @@ use thiserror::Error; use crate::ledger::governance::storage as gov_storage; use crate::ledger::pos; +use crate::ledger::pos::types::decimal_mult_u64; use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::types::address::Address; @@ -198,7 +199,8 @@ fn apply_slashes( for slash in slashes { if Epoch::from(slash.epoch) >= epoch_start { let raw_delta: u64 = delta.into(); - let current_slashed = token::Amount::from(slash.rate * raw_delta); + let current_slashed = + token::Amount::from(decimal_mult_u64(slash.rate, raw_delta)); delta -= current_slashed; } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 2a748f0259..0ecb1a48d2 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -571,6 +571,7 @@ pub mod testing { use derivative::Derivative; use itertools::Either; use namada::ledger::pos::namada_proof_of_stake::btree_set::BTreeSetShims; + use namada::ledger::pos::types::decimal_mult_i128; use namada::types::key::common::PublicKey; use namada::types::key::RefTo; use namada::types::storage::Epoch; From 9420915237faa7eb5650ae0579d6c69ac059c9e6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 8 Sep 2022 20:53:07 +0300 Subject: [PATCH 33/38] more decimal_mult function uses --- proof_of_stake/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 9c0afd8617..851f8702cf 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -1413,7 +1413,8 @@ where )); } let raw_current_stake: i128 = current_stake.into(); - let slashed_amount: TokenChange = (slash.rate * raw_current_stake).into(); + let slashed_amount: TokenChange = + decimal_mult_i128(slash.rate, raw_current_stake).into(); let token_change = -slashed_amount; // Apply slash at pipeline offset @@ -1861,7 +1862,8 @@ where for slash in &slashes { if slash.epoch >= *epoch_start { let raw_delta: u64 = slashed_bond_delta.into(); - let raw_slashed_delta = slash.rate * raw_delta; + let raw_slashed_delta = + decimal_mult_u64(slash.rate, raw_delta); let slashed_delta = TokenAmount::from(raw_slashed_delta); slashed_bond_delta -= slashed_delta; @@ -2158,8 +2160,9 @@ where for slash in &slashes { if slash.epoch >= *epoch_start && slash.epoch <= *epoch_end { let raw_delta: u64 = delta.into(); - let current_slashed = - TokenAmount::from(slash.rate * raw_delta); + let current_slashed = TokenAmount::from(decimal_mult_u64( + slash.rate, raw_delta, + )); slashed += current_slashed; delta -= current_slashed; } From 8546aa716d0def9d4d5bf9e095d420672a4862f5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 13 Sep 2022 01:02:01 +0200 Subject: [PATCH 34/38] fix correct inner type of ValidationError::TotalVotingPowerTooLarge --- proof_of_stake/src/parameters.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 944981d6ad..31f81fc118 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -109,12 +109,12 @@ impl PosParams { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { errors.push(ValidationError::TotalVotingPowerTooLarge( - max_total_voting_power, + max_total_voting_power.to_u64().unwrap(), )) } } Err(_) => errors.push(ValidationError::TotalVotingPowerTooLarge( - max_total_voting_power, + max_total_voting_power.to_u64().unwrap(), )), } From 5cadc76bcd26bf6c4efa32bee4786a518ae6590f Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 18:54:00 -0400 Subject: [PATCH 35/38] clean comments and toml files of basis points --- apps/src/lib/config/genesis.rs | 6 +++--- genesis/dev.toml | 12 ++++++------ genesis/e2e-tests-single-node.toml | 12 ++++++------ proof_of_stake/src/parameters.rs | 7 +++---- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 8126241947..a1ae2e4bc0 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -245,7 +245,7 @@ pub mod genesis_config { // Unbonding length (in epochs). // XXX: u64 doesn't work with toml-rs! pub unbonding_len: u64, - // Votes per token (in basis points). + // Votes per token. // XXX: u64 doesn't work with toml-rs! pub votes_per_token: Decimal, // Reward for proposing a block. @@ -255,11 +255,11 @@ pub mod genesis_config { // XXX: u64 doesn't work with toml-rs! pub block_vote_reward: Decimal, // Portion of a validator's stake that should be slashed on a - // duplicate vote (in basis points). + // duplicate vote. // XXX: u64 doesn't work with toml-rs! pub duplicate_vote_slash_rate: Decimal, // Portion of a validator's stake that should be slashed on a - // light client attack (in basis points). + // light client attack. // XXX: u64 doesn't work with toml-rs! pub light_client_attack_slash_rate: Decimal, } diff --git a/genesis/dev.toml b/genesis/dev.toml index 8d8d158033..85e486c1a4 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -151,18 +151,18 @@ 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 = 6 -# Votes per token (in basis points, i.e., per 10,000 tokens) -votes_per_token = 10 +# Votes per token +votes_per_token = 0.001 # Reward for proposing a block. block_proposer_reward = 100 # Reward for voting on a block. block_vote_reward = 1 # Portion of a validator's stake that should be slashed on a duplicate -# vote (in basis points, i.e., 500 = 5%). -duplicate_vote_slash_rate = 500 +# vote. +duplicate_vote_slash_rate = 0.05 # Portion of a validator's stake that should be slashed on a light -# client attack (in basis points, i.e., 500 = 5%). -light_client_attack_slash_rate = 500 +# client attack. +light_client_attack_slash_rate = 0.05 # Governance parameters. [gov_params] diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 029c2c2e75..e3858c6f29 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -155,18 +155,18 @@ 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 = 3 -# Votes per token (in basis points, i.e., per 10,000 tokens) -votes_per_token = 10 +# Votes per token +votes_per_token = 0.001 # Reward for proposing a block. block_proposer_reward = 100 # Reward for voting on a block. block_vote_reward = 1 # Portion of a validator's stake that should be slashed on a duplicate -# vote (in basis points, i.e., 500 = 5%). -duplicate_vote_slash_rate = 500 +# vote. +duplicate_vote_slash_rate = 0.05 # Portion of a validator's stake that should be slashed on a light -# client attack (in basis points, i.e., 500 = 5%). -light_client_attack_slash_rate = 500 +# client attack. +light_client_attack_slash_rate = 0.05 # Governance parameters. [gov_params] diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 31f81fc118..77dafdefbb 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -19,8 +19,7 @@ pub struct PosParams { /// `n + slashable_period_len` epoch. /// The value must be greater or equal to `pipeline_len`. pub unbonding_len: u64, - /// Used in validators' voting power calculation. Given in basis points - /// (voting power per ten thousand tokens). + /// Used in validators' voting power calculation. pub votes_per_token: Decimal, /// Amount of tokens rewarded to a validator for proposing a block pub block_proposer_reward: Decimal, @@ -28,10 +27,10 @@ pub struct PosParams { /// proposal pub block_vote_reward: Decimal, /// Portion of validator's stake that should be slashed on a duplicate - /// vote. Given in basis points (slashed amount per ten thousand tokens). + /// vote. pub duplicate_vote_slash_rate: Decimal, /// Portion of validator's stake that should be slashed on a light client - /// attack. Given in basis points (slashed amount per ten thousand tokens). + /// attack. pub light_client_attack_slash_rate: Decimal, } From fd53b0ed13afc36f383515b970abe96680b5ebba Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 18:55:33 -0400 Subject: [PATCH 36/38] use `decimal_mult` in some more places --- proof_of_stake/src/types.rs | 14 ++++++-------- tests/src/native_vp/pos.rs | 34 +++++++++++++++++++++------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 02a5e8a30c..c488a96155 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -353,8 +353,8 @@ impl VotingPower { /// Convert token amount into a voting power. pub fn from_tokens(tokens: impl Into, params: &PosParams) -> Self { // The token amount is expected to be in micro units - let whole_tokens = tokens.into() / 1_000_000; - Self(params.votes_per_token * whole_tokens) + let vp = decimal_mult_u64(params.votes_per_token, tokens.into()); + Self(vp) } } @@ -381,9 +381,8 @@ impl VotingPowerDelta { params: &PosParams, ) -> Result { // The token amount is expected to be in micro units - let whole_tokens = change.into() / 1_000_000; - let delta: i128 = params.votes_per_token * whole_tokens; - let delta: i64 = TryFrom::try_from(delta)?; + let vp = decimal_mult_i128(params.votes_per_token, change.into()); + let delta: i64 = vp.to_i64().unwrap(); Ok(Self(delta)) } @@ -393,9 +392,8 @@ impl VotingPowerDelta { params: &PosParams, ) -> Result { // The token amount is expected to be in micro units - let whole_tokens = tokens.into() / 1_000_000; - let delta: i64 = - TryFrom::try_from(params.votes_per_token * whole_tokens)?; + let vp = decimal_mult_u64(params.votes_per_token, tokens.into()); + let delta: i64 = vp.to_i64().unwrap(); Ok(Self(delta)) } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 0ecb1a48d2..cf57e6dd4b 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -931,10 +931,14 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole tokens // with division by 10^6 - let vp_before = - params.votes_per_token * (total_delta / TOKENS_PER_NAM); - let vp_after = params.votes_per_token - * ((total_delta + token_delta) / TOKENS_PER_NAM); + let vp_before = decimal_mult_i128( + params.votes_per_token, + total_delta / TOKENS_PER_NAM, + ); + let vp_after = decimal_mult_i128( + params.votes_per_token, + total_delta / TOKENS_PER_NAM, + ); // voting power delta let vp_delta = vp_after - vp_before; @@ -995,11 +999,11 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole // tokens with division by 10^6 - let vp_before = params.votes_per_token - * (total_delta / TOKENS_PER_NAM); - let vp_after = params.votes_per_token - * ((total_delta + token_delta) / TOKENS_PER_NAM); - // voting power delta + let vp_before = decimal_mult_i128( + params.votes_per_token, total_delta / TOKENS_PER_NAM); + let vp_after = decimal_mult_i128(params.votes_per_token, + total_delta / TOKENS_PER_NAM); + // voting power delta let vp_delta_at_unbonding = vp_after - vp_before - vp_delta - total_vp_delta; total_vp_delta += vp_delta_at_unbonding; @@ -1071,10 +1075,14 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole tokens // with division by 10^6 - let vp_before = params.votes_per_token - * (total_delta_cur / TOKENS_PER_NAM); - let vp_after = params.votes_per_token - * ((total_delta_cur + token_delta) / TOKENS_PER_NAM); + let vp_before = decimal_mult_i128( + params.votes_per_token, + total_delta_cur / TOKENS_PER_NAM, + ); + let vp_after = decimal_mult_i128( + params.votes_per_token, + (total_delta_cur + token_delta) / TOKENS_PER_NAM, + ); // voting power delta let vp_delta = vp_after - vp_before; From 37899adf3871db1d5020d91a3361d47bf69ece4f Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 18:56:13 -0400 Subject: [PATCH 37/38] update rust_decimal version + fmt --- proof_of_stake/src/lib.rs | 10 +++++----- proof_of_stake/src/parameters.rs | 3 ++- shared/Cargo.toml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 851f8702cf..ff56239a37 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -34,11 +34,11 @@ use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, - SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, - ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, - ValidatorState, ValidatorStates, ValidatorTotalDeltas, - ValidatorVotingPowers, VotingPower, VotingPowerDelta, + decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, + CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, + TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, + ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, + ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, }; use crate::btree_set::BTreeSetShims; diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 77dafdefbb..fa1a1023bf 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -103,7 +103,8 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows let max_total_voting_power = Decimal::from(self.max_validator_slots) - * self.votes_per_token * Decimal::from(TOKEN_MAX_AMOUNT); + * self.votes_per_token + * Decimal::from(TOKEN_MAX_AMOUNT); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6469694ea9..93646d297a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -101,7 +101,7 @@ pwasm-utils = {version = "0.18.0", optional = true} rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} -rust_decimal = "1.14.3" +rust_decimal = "1.26.1" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 8bed9893e281bb1ff9c2e2b946b8139981b45b85 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 21:19:34 -0400 Subject: [PATCH 38/38] fix arb_amount to max out at max tm voting power (fix wasm test bug) --- wasm/wasm_source/src/tx_bond.rs | 4 ++-- wasm/wasm_source/src/tx_unbond.rs | 14 ++++++++------ wasm/wasm_source/src/tx_withdraw.rs | 17 ++++++++++------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 38002d2495..875c15e752 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -329,9 +329,9 @@ mod tests { /// overflow. fn arb_initial_stake_and_bond() // Generate initial stake - (initial_stake in token::testing::arb_amount()) + (initial_stake in token::testing::arb_amount_ceiled((i64::MAX/8) as u64)) // Use the initial stake to limit the bond amount - (bond in arb_bond(u64::MAX - u64::from(initial_stake)), + (bond in arb_bond(((i64::MAX/8) as u64) - u64::from(initial_stake)), // Use the generated initial stake too initial_stake in Just(initial_stake), ) -> (token::Amount, transaction::pos::Bond) { diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 3b9f9bc76e..fa59670a56 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -385,12 +385,14 @@ mod tests { fn arb_initial_stake_and_unbond() -> impl Strategy { // Generate initial stake - token::testing::arb_amount().prop_flat_map(|initial_stake| { - // Use the initial stake to limit the bond amount - let unbond = arb_unbond(u64::from(initial_stake)); - // Use the generated initial stake too too - (Just(initial_stake), unbond) - }) + token::testing::arb_amount_ceiled((i64::MAX / 8) as u64).prop_flat_map( + |initial_stake| { + // Use the initial stake to limit the bond amount + let unbond = arb_unbond(u64::from(initial_stake)); + // Use the generated initial stake too too + (Just(initial_stake), unbond) + }, + ) } /// Generates an initial validator stake and a unbond, while making sure diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 3525b7b7cc..dc054fcd6f 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -201,13 +201,16 @@ mod tests { fn arb_initial_stake_and_unbonded_amount() -> impl Strategy { // Generate initial stake - token::testing::arb_amount().prop_flat_map(|initial_stake| { - // Use the initial stake to limit the unbonded amount from the stake - let unbonded_amount = - token::testing::arb_amount_ceiled(initial_stake.into()); - // Use the generated initial stake too too - (Just(initial_stake), unbonded_amount) - }) + token::testing::arb_amount_ceiled((i64::MAX / 8) as u64).prop_flat_map( + |initial_stake| { + // Use the initial stake to limit the unbonded amount from the + // stake + let unbonded_amount = + token::testing::arb_amount_ceiled(initial_stake.into()); + // Use the generated initial stake too too + (Just(initial_stake), unbonded_amount) + }, + ) } fn arb_withdraw() -> impl Strategy {