diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0c21ad37ec7..97d3304298d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -75,6 +75,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Improvements
* (cli) [\#9593](https://github.com/cosmos/cosmos-sdk/pull/9593) Check if chain-id is blank before verifying signatures in multisign and error.
+* (cli) [\#9717](https://github.com/cosmos/cosmos-sdk/pull/9717) Added CLI flag `--output json/text` to `tx` cli commands.
### Bug Fixes
@@ -86,6 +87,12 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/distribution) [\#9599](https://github.com/cosmos/cosmos-sdk/pull/9599) Withdraw rewards event now includes a value attribute even if there are 0 rewards (due to situations like 100% commission).
* (x/genutil) [\#9638](https://github.com/cosmos/cosmos-sdk/pull/9638) Added missing validator key save when recovering from mnemonic
* (server) [#9704](https://github.com/cosmos/cosmos-sdk/pull/9704) Start GRPCWebServer in goroutine, avoid blocking other services from starting.
+* [\#9762](https://github.com/cosmos/cosmos-sdk/pull/9762) The init command uses the chain-id from the client config if --chain-id is not provided
+
+### State Machine Breaking
+
+* (x/bank) [\#9611](https://github.com/cosmos/cosmos-sdk/pull/9611) Introduce a new index to act as a reverse index between a denomination and address allowing to query for
+ token holders of a specific denomination. `DenomOwners` is updated to use the new reverse index.
## [v0.43.0-rc0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.43.0-rc0) - 2021-06-25
diff --git a/client/flags/flags.go b/client/flags/flags.go
index 80c3e792256a..c4faad1b035a 100644
--- a/client/flags/flags.go
+++ b/client/flags/flags.go
@@ -93,6 +93,7 @@ func AddQueryFlagsToCmd(cmd *cobra.Command) {
// AddTxFlagsToCmd adds common flags to a module tx command.
func AddTxFlagsToCmd(cmd *cobra.Command) {
+ cmd.Flags().StringP(tmcli.OutputFlag, "o", "json", "Output format (text|json)")
cmd.Flags().String(FlagKeyringDir, "", "The client Keyring directory; if omitted, the default 'home' directory will be used")
cmd.Flags().String(FlagFrom, "", "Name or address of private key with which to sign")
cmd.Flags().Uint64P(FlagAccountNumber, "a", 0, "The account number of the signing account (offline mode only)")
diff --git a/container/option_test.go b/container/option_test.go
new file mode 100644
index 000000000000..f447afa5a964
--- /dev/null
+++ b/container/option_test.go
@@ -0,0 +1 @@
+package container_test
diff --git a/docs/architecture/adr-043-nft-module.md b/docs/architecture/adr-043-nft-module.md
index 814be6cb1898..8c2f967b1694 100644
--- a/docs/architecture/adr-043-nft-module.md
+++ b/docs/architecture/adr-043-nft-module.md
@@ -281,6 +281,15 @@ message QueryClassesResponse {
}
```
+
+### Interoperability
+
+Interoperability is all about reusing assets between modules and chains. The former one is achieved by ADR-33: Protobuf client - server communication. At the time of writing ADR-33 is not finalized. The latter is achieved by IBC. Here we will focus on the IBC side.
+IBC is implemented per module. Here, we aligned that NFTs will be recorded and managed in the x/nft. This requires creation of a new IBC standard and implementation of it.
+
+For IBC interoperability, NFT custom modules MUST use the NFT object type understood by the IBC client. So, for x/nft interoperability, custom NFT implementations (example: x/cryptokitty) should use the canonical x/nft module and proxy all NFT balance keeping functionality to x/nft or else re-implement all functionality using the NFT object type understood by the IBC client. In other words: x/nft becomes the standard NFT registry for all Cosmos NFTs (example: x/cryptokitty will register a kitty NFT in x/nft and use x/nft for book keeping). This was [discussed](https://github.com/cosmos/cosmos-sdk/discussions/9065#discussioncomment-873206) in the context of using x/bank as a general asset balance book. Not using x/nft will require implementing another module for IBC.
+
+
## Consequences
### Backward Compatibility
@@ -299,6 +308,7 @@ This specification conforms to the ERC-721 smart contract specification for NFT
### Negative
++ New IBC app is required for x/nft
### Neutral
diff --git a/docs/basics/app-anatomy.md b/docs/basics/app-anatomy.md
index d25720279399..6c423a4b88fd 100644
--- a/docs/basics/app-anatomy.md
+++ b/docs/basics/app-anatomy.md
@@ -212,17 +212,6 @@ The REST endpoints are defined in the Protobuf files, along with the gRPC servic
The SDK also provides a development endpoint to generate [Swagger](https://swagger.io/) definition files for these REST endpoints. This endpoint can be enabled inside the [`app.toml`](../run-node/run-node.md#configuring-the-node-using-apptoml) config file, under the `api.swagger` key.
-#### Legacy API REST Endpoints
-
-The [module's Legacy REST interface](../building-modules/module-interfaces.md#legacy-rest) lets users generate transactions and query the state through REST calls to the application's Legacy API Service. REST routes are defined in a file `client/rest/rest.go`, which is composed of:
-
-- A `RegisterRoutes` function, which registers each route defined in the file. This function is called from the [main application's interface](#application-interfaces) for each module used within the application. The router used in the SDK is [Gorilla's mux](https://github.com/gorilla/mux).
-- Custom request type definitions for each query or transaction creation function that needs to be exposed. These custom request types build on the base `request` type of the Cosmos SDK:
- +++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/types/rest/rest.go#L62-L76
-- One handler function for each request that can be routed to the given module. These functions implement the core logic necessary to serve the request.
-
-These Legacy API endpoints are present in the SDK for backward compatibility purposes and will be removed in the next release.
-
## Application Interface
[Interfaces](#command-line-grpc-services-and-rest-interfaces) let end-users interact with full-node clients. This means querying data from the full-node or creating and sending new transactions to be relayed by the full-node and eventually included in a block.
diff --git a/docs/core/grpc_rest.md b/docs/core/grpc_rest.md
index d962e43907f0..74ee91ce241a 100644
--- a/docs/core/grpc_rest.md
+++ b/docs/core/grpc_rest.md
@@ -55,7 +55,7 @@ An overview of all available gRPC endpoints shipped with the Cosmos SDK is [Prot
## REST Server
-In Cosmos SDK v0.40, the node continues to serve a REST server. However, the existing routes present in version v0.39 and earlier are now marked as deprecated, and new routes have been added via gRPC-gateway.
+Cosmos SDK supports REST routes via gRPC-gateway.
All routes are configured under the following fields in `~/.simapp/config/app.toml`:
@@ -73,12 +73,6 @@ If, for various reasons, you cannot use gRPC (for example, you are building a we
For application developers, gRPC-gateway REST routes needs to be wired up to the REST server, this is done by calling the `RegisterGRPCGatewayRoutes` function on the ModuleManager.
-### Legacy REST API Routes
-
-The REST routes present in Cosmos SDK v0.39 and earlier are marked as deprecated via a [HTTP deprecation header](https://tools.ietf.org/id/draft-dalal-deprecation-header-01.html). They are still maintained to keep backwards compatibility, but will be removed in v0.44. For updating from Legacy REST routes to new gRPC-gateway REST routes, please refer to our [migration guide](../migrations/rest.md).
-
-For application developers, Legacy REST API routes needs to be wired up to the REST server, this is done by calling the `RegisterRESTRoutes` function on the ModuleManager.
-
### Swagger
A [Swagger](https://swagger.io/) (or OpenAPIv2) specification file is exposed under the `/swagger` route on the API server. Swagger is an open specification describing the API endpoints a server serves, including description, input arguments, return types and much more about each endpoint.
diff --git a/docs/migrations/rest.md b/docs/migrations/rest.md
index 9978974a2730..e26687af40c7 100644
--- a/docs/migrations/rest.md
+++ b/docs/migrations/rest.md
@@ -4,38 +4,18 @@ order: 2
# REST Endpoints Migration
-Migrate your REST endpoints. Legacy REST endpoints have been deprecated since v0.40 and they will be removed in v0.44. {synopsis}
+Migrate to gRPC-Gateway REST endpoints. Legacy REST endpoints were marked as deprecated in v0.40 and removed in v0.44. {synopsis}
-## Deprecation of Legacy REST Endpoints
+## Legacy REST Endpoints
-The Cosmos SDK versions v0.39 and earlier provided REST endpoints to query the state and broadcast transactions. These endpoints were kept in Cosmos SDK v0.40 and they are still available in v0.43, but they are marked as deprecated and will be removed in v0.44. We therefore call these endpoints legacy REST endpoints.
+Cosmos SDK versions v0.39 and earlier registered REST endpoints using a package called `gorilla/mux`. These REST endpoints were marked as deprecated in v0.40 and have since been referred to as legacy REST endpoints. Legacy REST endpoints were officially removed in v0.44.
-Some important information concerning all legacy REST endpoints:
+## gRPC-Gateway REST Endpoints
-- Most of these endpoints are backwards-comptatible. All breaking changes are described in the next section.
-- In particular, these endpoints still output Amino JSON. Cosmos v0.40 introduced Protobuf as the default encoding library throughout the codebase, but legacy REST endpoints are one of the few places where the encoding is hardcoded to Amino. For more information about Protobuf and Amino, please read our [encoding guide](../core/encoding.md).
-- All legacy REST endpoints include a [HTTP deprecation header](https://tools.ietf.org/id/draft-dalal-deprecation-header-01.html) which links to this document.
-
-## Breaking Changes in Legacy REST Endpoints
-
-| Legacy REST Endpoint | Description | Breaking Change |
-| ------------------------------------------------------------------------ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `POST /txs` | Broadcast tx | Endpoint will error when trying to broadcast transactions that don't support Amino serialization (e.g. IBC txs)1. |
-| `POST /txs/encode`, `POST /txs/decode` | Encode/decode Amino txs from JSON to binary | Endpoint will error when trying to encode/decode transactions that don't support Amino serialization (e.g. IBC txs)1. |
-| `GET /txs/{hash}` | Query tx by hash | Endpoint will error when trying to output transactions that don't support Amino serialization (e.g. IBC txs)1. |
-| `GET /txs` | Query tx by events | Endpoint will error when trying to output transactions that don't support Amino serialization (e.g. IBC txs)1. |
-| `GET /gov/proposals/{id}/votes`, `GET /gov/proposals/{id}/votes/{voter}` | Gov endpoints for querying votes | All gov endpoints which return votes return int32 in the `option` field instead of string: `1=VOTE_OPTION_YES, 2=VOTE_OPTION_ABSTAIN, 3=VOTE_OPTION_NO, 4=VOTE_OPTION_NO_WITH_VETO`. |
-| `GET /staking/*` | Staking query endpoints | All staking endpoints which return validators have two breaking changes. First, the validator's `consensus_pubkey` field returns an Amino-encoded struct representing an `Any` instead of a Bech32-encoded string representing the pubkey. The `value` field of the `Any` is the pubkey's raw key as base64-encoded bytes. Second, the validator's `status` field now returns an int32 instead of string: `1=BOND_STATUS_UNBONDED`, `2=BOND_STATUS_UNBONDING`, `3=BOND_STATUS_BONDED`. |
-| `GET /staking/validators` | Get all validators | BondStatus is now a protobuf enum instead of an int32, and JSON serialized using its protobuf name, so expect query parameters like `?status=BOND_STATUS_{BONDED,UNBONDED,UNBONDING}` as opposed to `?status={bonded,unbonded,unbonding}`. |
-
-1: Transactions that don't support Amino serialization are the ones that contain one or more `Msg`s that are not registered with the Amino codec. Currently in the SDK, only IBC `Msg`s fall into this case.
+Following the Protocol Buffers migration in v0.40, Cosmos SDK has been set to take advantage of a vast number of gRPC tools and solutions. v0.40 introduced new REST endpoints generated from [gRPC `Query` services](../building-modules/query-services.md) using [grpc-gateway](https://grpc-ecosystem.github.io/grpc-gateway/). These new REST endpoints are referred to as gRPC-Gateway REST endpoints.
## Migrating to New REST Endpoints
-Thanks to the Protocol Buffers migration in v0.40, we are able to take advantage of a vast number of gRPC tools and solutions. Most of the legacy REST endpoints have been replaced by REST endpoints generated from [gRPC `Query` services](../building-modules/query-services.md) using [grpc-gateway](https://grpc-ecosystem.github.io/grpc-gateway/). We usually call them _gRPC-gateway REST endpoints_.
-
-Previously, some modules exposed legacy `POST` endpoints to generate unsigned transactions for their `Msg`s. These `POST` endpoints were removed in v0.40. We recommend using [Protobuf `Msg` service](../building-modules/msg-services.md) directly to do client-side transaction generation. A guide can be found [here](../run-node/txs.md).
-
| Legacy REST Endpoint | Description | New gRPC-gateway REST Endpoint |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `GET /txs/{hash}` | Query tx by hash | `GET /cosmos/tx/v1beta1/txs/{hash}` |
@@ -98,4 +78,4 @@ Previously, some modules exposed legacy `POST` endpoints to generate unsigned tr
## Migrating to gRPC
-Instead of hitting REST endpoints as described in the previous paragraph, the SDK also exposes a gRPC server. Any client can use gRPC instead of REST to interact with the node. An overview of different ways to communicate with a node can be found [here](../core/grpc_rest.md), and a concrete tutorial for setting up a gRPC client [here](../run-node/txs.md#programmatically-with-go).
+Instead of hitting REST endpoints as described above, the SDK also exposes a gRPC server. Any client can use gRPC instead of REST to interact with the node. An overview of different ways to communicate with a node can be found [here](../core/grpc_rest.md), and a concrete tutorial for setting up a gRPC client can be found [here](../run-node/txs.md#programmatically-with-go).
\ No newline at end of file
diff --git a/scripts/README.md b/scripts/README.md
index f213124c8dec..4183563e8fec 100644
--- a/scripts/README.md
+++ b/scripts/README.md
@@ -1,3 +1,23 @@
Generally we should avoid shell scripting and write tests purely in Golang.
However, some libraries are not Goroutine-safe (e.g. app simulations cannot be run safely in parallel),
and OS-native threading may be more efficient for many parallel simulations, so we use shell scripts here.
+
+### Validate Gentxs
+A custom utility script is available to [validate gentxs](./validate-gentxs.sh). Though we have
+`ValidateBasic()` for validating gentx data, it cannot validate signatures. This custom script helps
+to validate all the gentxs by collecting them one by one and starting a local network.
+It requires the following env settings.
+```shell
+export DAEMON=gaiad
+export CHAIN_ID=cosmoshub-1
+export DENOM=uatom
+export GH_URL=https://github.com/cosmos/gaia
+export BINARY_VERSION=v1.0.0
+export GO_VERSION=1.15.2
+export PRELAUNCH_GENESIS_URL=https://raw.githubusercontent.com/cosmos/mainnet/main/cosmoshub-1/genesis-prelaunch.json
+export GENTXS_DIR=~/go/src/github.com/cosmos/mainnet/$CHAIN_ID/gentxs
+```
+
+Though this script is handy for verifying the gentxs locally, it is advised to use Github Action to validate gentxs.
+An example can be found here:
+https://github.com/regen-network/mainnet/blob/0bcd387671b9574e893289e39c08a1643cac7d62/.github/workflows/validate-gentx.yml
diff --git a/scripts/validate-gentxs.sh b/scripts/validate-gentxs.sh
new file mode 100755
index 000000000000..da3e0cf0b6c6
--- /dev/null
+++ b/scripts/validate-gentxs.sh
@@ -0,0 +1,147 @@
+#!/usr/bin/env bash
+
+DAEMON_HOME="/tmp/simd$(date +%s)"
+RANDOM_KEY="randomvalidatorkey"
+
+echo "#############################################"
+echo "### Ensure to set the below ENV settings ###"
+echo "#############################################"
+echo "
+DAEMON= # ex: simd
+CHAIN_ID= # ex: testnet-1
+DENOM= # ex: ustake
+GH_URL= # ex: https://github.com/cosmos/cosmos-sdk
+BINARY_VERSION= # ex :v0.43.0-beta1
+GO_VERSION=1.15.2
+PRELAUNCH_GENESIS_URL= # ex: https://raw.githubusercontent.com/cosmos/cosmos-sdk/master/$CHAIN_ID/genesis-prelaunch.json
+GENTXS_DIR= # ex: $GOPATH/github.com/cosmos/mainnet/$CHAIN_ID/gentxs"
+echo
+
+if [[ -z "${GH_URL}" ]]; then
+ echo "GH_URL in not set, required. Ex: https://github.com/cosmos/cosmos-sdk"
+ exit 0
+fi
+if [[ -z "${DAEMON}" ]]; then
+ echo "DAEMON is not set, required. Ex: simd, gaiad etc"
+ exit 0
+fi
+if [[ -z "${DENOM}" ]]; then
+ echo "DENOM in not set, required. Ex: stake, uatom etc"
+ exit 0
+fi
+if [[ -z "${GO_VERSION}" ]]; then
+ echo "GO_VERSION in not set, required. Ex: 1.15.2, 1.16.6 etc."
+ exit 0
+fi
+if [[ -z "${CHAIN_ID}" ]]; then
+ echo "CHAIN_ID in not set, required."
+ exit 0
+fi
+if [[ -z "${PRELAUNCH_GENESIS_URL}" ]]; then
+ echo "PRELAUNCH_GENESIS_URL (genesis file url) in not set, required."
+ exit 0
+fi
+if [[ -z "${GENTXS_DIR}" ]]; then
+ echo "GENTXS_DIR in not set, required."
+ exit 0
+fi
+
+command_exists () {
+ type "$1" &> /dev/null ;
+}
+
+if command_exists go ; then
+ echo "Golang is already installed"
+else
+ read -s -p "Installing go using apt. Do you want to proceed (y/n)?: " useApt
+
+ if [ "$useApt" != "y" ]; then
+ echo
+ echo "Install go manually and execute this script"
+ exit 0;
+ fi
+
+ sudo apt update
+ sudo apt install build-essential -y
+
+ wget https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz
+ tar -xvf go$GO_VERSION.linux-amd64.tar.gz
+ sudo mv go /usr/local
+
+ echo "" >> ~/.profile
+ echo 'export GOPATH=$HOME/go' >> ~/.profile
+ echo 'export GOROOT=/usr/local/go' >> ~/.profile
+ echo 'export GOBIN=$GOPATH/bin' >> ~/.profile
+ echo 'export PATH=$PATH:/usr/local/go/bin:$GOBIN' >> ~/.profile
+
+ . ~/.profile
+
+ go version
+fi
+
+if [ "$(ls -A $GENTXS_DIR)" ]; then
+ echo "Install $DAEMON"
+ git clone $GH_URL $DAEMON
+ cd $DAEMON
+ git fetch && git checkout $BINARY_VERSION
+ make install
+ $DAEMON version
+
+ for GENTX_FILE in $GENTXS_DIR/*.json; do
+ if [ -f "$GENTX_FILE" ]; then
+ set -e
+
+ echo "GentxFile::::"
+ echo $GENTX_FILE
+
+ echo "...........Init a testnet.............."
+ $DAEMON init --chain-id $CHAIN_ID validator --home $DAEMON_HOME
+
+ $DAEMON keys add $RANDOM_KEY --keyring-backend test --home $DAEMON_HOME
+
+ echo "..........Fetching genesis......."
+ curl -s $PRELAUNCH_GENESIS_URL > $DAEMON_HOME/config/genesis.json
+
+ # this genesis time is different from original genesis time, just for validating gentx.
+ sed -i '/genesis_time/c\ \"genesis_time\" : \"2021-01-01T00:00:00Z\",' $DAEMON_HOME/config/genesis.json
+
+ GENACC=$(cat $GENTX_FILE | sed -n 's|.*"delegator_address":"\([^"]*\)".*|\1|p')
+ denomquery=$(jq -r '.body.messages[0].value.denom' $GENTX_FILE)
+ amountquery=$(jq -r '.body.messages[0].value.amount' $GENTX_FILE)
+
+ # only allow $DENOM tokens to be bonded
+ if [ $denomquery != $DENOM ]; then
+ echo "invalid denomination"
+ exit 1
+ fi
+
+ $DAEMON add-genesis-account $RANDOM_KEY 1000000000000000$DENOM --home $DAEMON_HOME \
+ --keyring-backend test
+
+ $DAEMON gentx $RANDOM_KEY 900000000000000$DENOM --home $DAEMON_HOME \
+ --keyring-backend test --chain-id $CHAIN_ID
+
+ cp $GENTX_FILE $DAEMON_HOME/config/gentx/
+
+ echo "..........Collecting gentxs......."
+ $DAEMON collect-gentxs --home $DAEMON_HOME
+ $DAEMON validate-genesis --home $DAEMON_HOME
+
+ echo "..........Starting node......."
+ $DAEMON start --home $DAEMON_HOME &
+
+ sleep 10s
+
+ echo "...checking network status.."
+ echo "if this fails, most probably the gentx with address $GENACC is invalid"
+ $DAEMON status --node http://localhost:26657
+
+ echo "...Cleaning the stuff..."
+ killall $DAEMON >/dev/null 2>&1
+ sleep 2s
+ rm -rf $DAEMON_HOME
+ fi
+ done
+else
+ echo "$GENTXS_DIR is empty, nothing to validate"
+fi
diff --git a/simapp/app_test.go b/simapp/app_test.go
index c84c99003053..76c1a423d1f9 100644
--- a/simapp/app_test.go
+++ b/simapp/app_test.go
@@ -123,8 +123,13 @@ func TestRunMigrations(t *testing.T) {
false, "", true, "no migrations found for module bank: not found", 0,
},
{
- "can register and run migration handler for x/bank",
+ "can register 1->2 migration handler for x/bank, cannot run migration",
"bank", 1,
+ false, "", true, "no migration found for module bank from version 2 to version 3: not found", 0,
+ },
+ {
+ "can register 2->3 migration handler for x/bank, can run migration",
+ "bank", 2,
false, "", false, "", 1,
},
{
diff --git a/types/dec_coin_test.go b/types/dec_coin_test.go
index 938f7dddffb4..c0cf44cd60d4 100644
--- a/types/dec_coin_test.go
+++ b/types/dec_coin_test.go
@@ -112,6 +112,7 @@ func (s *decCoinTestSuite) TestFilteredZeroDecCoins() {
input sdk.DecCoins
original string
expected string
+ panic bool
}{
{
name: "all greater than zero",
@@ -124,6 +125,7 @@ func (s *decCoinTestSuite) TestFilteredZeroDecCoins() {
},
original: "1.000000000000000000testa,2.000000000000000000testb,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste",
expected: "1.000000000000000000testa,2.000000000000000000testb,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste",
+ panic: false,
},
{
name: "zero coin in middle",
@@ -136,6 +138,7 @@ func (s *decCoinTestSuite) TestFilteredZeroDecCoins() {
},
original: "1.000000000000000000testa,2.000000000000000000testb,0.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste",
expected: "1.000000000000000000testa,2.000000000000000000testb,4.000000000000000000testd,5.000000000000000000teste",
+ panic: false,
},
{
name: "zero coin end (unordered)",
@@ -148,13 +151,32 @@ func (s *decCoinTestSuite) TestFilteredZeroDecCoins() {
},
original: "5.000000000000000000teste,3.000000000000000000testc,1.000000000000000000testa,4.000000000000000000testd,0.000000000000000000testb",
expected: "1.000000000000000000testa,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste",
+ panic: false,
+ },
+
+ {
+ name: "panic when same denoms in multiple coins",
+ input: sdk.DecCoins{
+ {"testa", sdk.NewDec(5)},
+ {"testa", sdk.NewDec(3)},
+ {"testa", sdk.NewDec(1)},
+ {"testd", sdk.NewDec(4)},
+ {"testb", sdk.NewDec(2)},
+ },
+ original: "5.000000000000000000teste,3.000000000000000000testc,1.000000000000000000testa,4.000000000000000000testd,0.000000000000000000testb",
+ expected: "1.000000000000000000testa,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste",
+ panic: true,
},
}
for _, tt := range cases {
- undertest := sdk.NewDecCoins(tt.input...)
- s.Require().Equal(tt.expected, undertest.String(), "NewDecCoins must return expected results")
- s.Require().Equal(tt.original, tt.input.String(), "input must be unmodified and match original")
+ if tt.panic {
+ s.Require().Panics(func() { sdk.NewDecCoins(tt.input...) }, "Should panic due to multiple coins with same denom")
+ } else {
+ undertest := sdk.NewDecCoins(tt.input...)
+ s.Require().Equal(tt.expected, undertest.String(), "NewDecCoins must return expected results")
+ s.Require().Equal(tt.original, tt.input.String(), "input must be unmodified and match original")
+ }
}
}
@@ -580,3 +602,543 @@ func (s *decCoinTestSuite) TestDecCoins_AddDecCoinWithIsValid() {
}
}
}
+
+func (s *decCoinTestSuite) TestDecCoins_Empty() {
+ testCases := []struct {
+ input sdk.DecCoins
+ expectedResult bool
+ msg string
+ }{
+ {sdk.DecCoins{}, true, "No coins as expected."},
+ {sdk.DecCoins{sdk.DecCoin{testDenom1, sdk.NewDec(5)}}, false, "DecCoins is not empty"},
+ }
+
+ for _, tc := range testCases {
+ if tc.expectedResult {
+ s.Require().True(tc.input.Empty(), tc.msg)
+ } else {
+ s.Require().False(tc.input.Empty(), tc.msg)
+ }
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoins_GetDenomByIndex() {
+ testCases := []struct {
+ name string
+ input sdk.DecCoins
+ index int
+ expectedResult string
+ expectedErr bool
+ }{
+ {
+ "No DecCoins in Slice",
+ sdk.DecCoins{},
+ 0,
+ "",
+ true,
+ },
+ {"When index out of bounds", sdk.DecCoins{sdk.DecCoin{testDenom1, sdk.NewDec(5)}}, 2, "", true},
+ {"When negative index", sdk.DecCoins{sdk.DecCoin{testDenom1, sdk.NewDec(5)}}, -1, "", true},
+ {
+ "Appropriate index case",
+ sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(5)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(57)},
+ }, 1, testDenom2, false,
+ },
+ }
+
+ for i, tc := range testCases {
+ tc := tc
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.expectedErr {
+ s.Require().Panics(func() { tc.input.GetDenomByIndex(tc.index) }, "Test should have panicked")
+ } else {
+ res := tc.input.GetDenomByIndex(tc.index)
+ s.Require().Equal(tc.expectedResult, res, "Unexpected result for test case #%d, expected output: %s, input: %v", i, tc.expectedResult, tc.input)
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoins_IsAllPositive() {
+ testCases := []struct {
+ name string
+ input sdk.DecCoins
+ expectedResult bool
+ }{
+ {"No Coins", sdk.DecCoins{}, false},
+
+ {"One Coin - Zero value", sdk.DecCoins{sdk.DecCoin{testDenom1, sdk.NewDec(0)}}, false},
+
+ {"One Coin - Postive value", sdk.DecCoins{sdk.DecCoin{testDenom1, sdk.NewDec(5)}}, true},
+
+ {"One Coin - Negative value", sdk.DecCoins{sdk.DecCoin{testDenom1, sdk.NewDec(-15)}}, false},
+
+ {"Multiple Coins - All positive value", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(51)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(123)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(50)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(92233720)},
+ }, true},
+
+ {"Multiple Coins - Some negative value", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(51)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(-123)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(0)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(92233720)},
+ }, false},
+ }
+
+ for i, tc := range testCases {
+ tc := tc
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.expectedResult {
+ s.Require().True(tc.input.IsAllPositive(), "Test case #%d: %s", i, tc.name)
+ } else {
+ s.Require().False(tc.input.IsAllPositive(), "Test case #%d: %s", i, tc.name)
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoin_IsLT() {
+ testCases := []struct {
+ name string
+ coin sdk.DecCoin
+ otherCoin sdk.DecCoin
+ expectedResult bool
+ expectedPanic bool
+ }{
+
+ {"Same Denom - Less than other coin", sdk.DecCoin{testDenom1, sdk.NewDec(3)}, sdk.DecCoin{testDenom1, sdk.NewDec(19)}, true, false},
+
+ {"Same Denom - Greater than other coin", sdk.DecCoin{testDenom1, sdk.NewDec(343340)}, sdk.DecCoin{testDenom1, sdk.NewDec(14)}, false, false},
+
+ {"Same Denom - Same as other coin", sdk.DecCoin{testDenom1, sdk.NewDec(20)}, sdk.DecCoin{testDenom1, sdk.NewDec(20)}, false, false},
+
+ {"Different Denom - Less than other coin", sdk.DecCoin{testDenom1, sdk.NewDec(3)}, sdk.DecCoin{testDenom2, sdk.NewDec(19)}, true, true},
+
+ {"Different Denom - Greater than other coin", sdk.DecCoin{testDenom1, sdk.NewDec(343340)}, sdk.DecCoin{testDenom2, sdk.NewDec(14)}, true, true},
+
+ {"Different Denom - Same as other coin", sdk.DecCoin{testDenom1, sdk.NewDec(20)}, sdk.DecCoin{testDenom2, sdk.NewDec(20)}, true, true},
+ }
+
+ for i, tc := range testCases {
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.expectedPanic {
+ s.Require().Panics(func() { tc.coin.IsLT(tc.otherCoin) }, "Test case #%d: %s", i, tc.name)
+ } else {
+ res := tc.coin.IsLT(tc.otherCoin)
+ if tc.expectedResult {
+ s.Require().True(res, "Test case #%d: %s", i, tc.name)
+ } else {
+ s.Require().False(res, "Test case #%d: %s", i, tc.name)
+ }
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoin_IsGTE() {
+ testCases := []struct {
+ name string
+ coin sdk.DecCoin
+ otherCoin sdk.DecCoin
+ expectedResult bool
+ expectedPanic bool
+ }{
+
+ {"Same Denom - Less than other coin", sdk.DecCoin{testDenom1, sdk.NewDec(3)}, sdk.DecCoin{testDenom1, sdk.NewDec(19)}, false, false},
+
+ {"Same Denom - Greater than other coin", sdk.DecCoin{testDenom1, sdk.NewDec(343340)}, sdk.DecCoin{testDenom1, sdk.NewDec(14)}, true, false},
+
+ {"Same Denom - Same as other coin", sdk.DecCoin{testDenom1, sdk.NewDec(20)}, sdk.DecCoin{testDenom1, sdk.NewDec(20)}, true, false},
+
+ {"Different Denom - Less than other coin", sdk.DecCoin{testDenom1, sdk.NewDec(3)}, sdk.DecCoin{testDenom2, sdk.NewDec(19)}, true, true},
+
+ {"Different Denom - Greater than other coin", sdk.DecCoin{testDenom1, sdk.NewDec(343340)}, sdk.DecCoin{testDenom2, sdk.NewDec(14)}, true, true},
+
+ {"Different Denom - Same as other coin", sdk.DecCoin{testDenom1, sdk.NewDec(20)}, sdk.DecCoin{testDenom2, sdk.NewDec(20)}, true, true},
+ }
+
+ for i, tc := range testCases {
+ tc := tc
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.expectedPanic {
+ s.Require().Panics(func() { tc.coin.IsGTE(tc.otherCoin) }, "Test case #%d: %s", i, tc.name)
+ } else {
+ res := tc.coin.IsGTE(tc.otherCoin)
+ if tc.expectedResult {
+ s.Require().True(res, "Test case #%d: %s", i, tc.name)
+ } else {
+ s.Require().False(res, "Test case #%d: %s", i, tc.name)
+ }
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoins_IsZero() {
+ testCases := []struct {
+ name string
+ coins sdk.DecCoins
+ expectedResult bool
+ }{
+ {"No Coins", sdk.DecCoins{}, true},
+
+ {"One Coin - Zero value", sdk.DecCoins{sdk.DecCoin{testDenom1, sdk.NewDec(0)}}, true},
+
+ {"One Coin - Postive value", sdk.DecCoins{sdk.DecCoin{testDenom1, sdk.NewDec(5)}}, false},
+
+ {"Multiple Coins - All zero value", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(0)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(0)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(0)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(0)},
+ }, true},
+
+ {"Multiple Coins - Some positive value", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(0)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(0)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(0)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(92233720)},
+ }, false},
+ }
+
+ for i, tc := range testCases {
+ tc := tc
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.expectedResult {
+ s.Require().True(tc.coins.IsZero(), "Test case #%d: %s", i, tc.name)
+ } else {
+ s.Require().False(tc.coins.IsZero(), "Test case #%d: %s", i, tc.name)
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoins_MulDec() {
+ testCases := []struct {
+ name string
+ coins sdk.DecCoins
+ multiplier sdk.Dec
+ expectedResult sdk.DecCoins
+ }{
+ {"No Coins", sdk.DecCoins{}, sdk.NewDec(1), sdk.DecCoins(nil)},
+
+ {"Multiple coins - zero multiplier", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(10)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(30)},
+ }, sdk.NewDec(0), sdk.DecCoins(nil)},
+
+ {"Multiple coins - positive multiplier", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(1)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(2)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(4)},
+ }, sdk.NewDec(2), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(20)},
+ }},
+
+ {"Multiple coins - negative multiplier", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(1)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(2)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(4)},
+ }, sdk.NewDec(-2), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(-20)},
+ }},
+
+ {"Multiple coins - Different denom", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(1)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(2)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(4)},
+ }, sdk.NewDec(2), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(8)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(12)},
+ }},
+ }
+
+ for i, tc := range testCases {
+ tc := tc
+ s.T().Run(tc.name, func(t *testing.T) {
+ res := tc.coins.MulDec(tc.multiplier)
+ s.Require().Equal(tc.expectedResult, res, "Test case #%d: %s", i, tc.name)
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoins_MulDecTruncate() {
+ testCases := []struct {
+ name string
+ coins sdk.DecCoins
+ multiplier sdk.Dec
+ expectedResult sdk.DecCoins
+ expectedPanic bool
+ }{
+ {"No Coins", sdk.DecCoins{}, sdk.NewDec(1), sdk.DecCoins(nil), false},
+
+ // TODO - Fix test - Function comment documentation for MulDecTruncate says if multiplier d is zero, it should panic.
+ // However, that is not the observed behaviour. Currently nil is returned.
+ {"Multiple coins - zero multiplier", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(10, 3)},
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(30, 2)},
+ }, sdk.NewDec(0), sdk.DecCoins(nil), false},
+
+ {"Multiple coins - positive multiplier", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(15, 1)},
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(15, 1)},
+ }, sdk.NewDec(1), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(3, 0)},
+ }, false},
+
+ {"Multiple coins - positive multiplier", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(15, 1)},
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(15, 1)},
+ }, sdk.NewDec(-2), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(-6, 0)},
+ }, false},
+
+ {"Multiple coins - Different denom", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(15, 1)},
+ sdk.DecCoin{testDenom2, sdk.NewDecWithPrec(3333, 4)},
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(15, 1)},
+ sdk.DecCoin{testDenom2, sdk.NewDecWithPrec(333, 4)},
+ }, sdk.NewDec(10), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(30, 0)},
+ sdk.DecCoin{testDenom2, sdk.NewDecWithPrec(3666, 3)},
+ }, false},
+ }
+
+ for i, tc := range testCases {
+ tc := tc
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.expectedPanic {
+ s.Require().Panics(func() { tc.coins.MulDecTruncate(tc.multiplier) }, "Test case #%d: %s", i, tc.name)
+ } else {
+ res := tc.coins.MulDecTruncate(tc.multiplier)
+ s.Require().Equal(tc.expectedResult, res, "Test case #%d: %s", i, tc.name)
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoins_QuoDec() {
+
+ testCases := []struct {
+ name string
+ coins sdk.DecCoins
+ input sdk.Dec
+ expectedResult sdk.DecCoins
+ panics bool
+ }{
+ {"No Coins", sdk.DecCoins{}, sdk.NewDec(1), sdk.DecCoins(nil), false},
+
+ {"Multiple coins - zero input", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(10)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(30)},
+ }, sdk.NewDec(0), sdk.DecCoins(nil), true},
+
+ {"Multiple coins - positive input", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(4)},
+ }, sdk.NewDec(2), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(35, 1)},
+ }, false},
+
+ {"Multiple coins - negative input", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(4)},
+ }, sdk.NewDec(-2), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDecWithPrec(-35, 1)},
+ }, false},
+
+ {"Multiple coins - Different input", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(1)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(2)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(4)},
+ }, sdk.NewDec(2), sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(2)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(3)},
+ }, false},
+ }
+
+ for i, tc := range testCases {
+ tc := tc
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.panics {
+ s.Require().Panics(func() { tc.coins.QuoDec(tc.input) }, "Test case #%d: %s", i, tc.name)
+ } else {
+ res := tc.coins.QuoDec(tc.input)
+ s.Require().Equal(tc.expectedResult, res, "Test case #%d: %s", i, tc.name)
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoin_IsEqual() {
+ testCases := []struct {
+ name string
+ coin sdk.DecCoin
+ otherCoin sdk.DecCoin
+ expectedResult bool
+ expectedPanic bool
+ }{
+
+ {"Different Denom Same Amount", sdk.DecCoin{testDenom1, sdk.NewDec(20)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(20)},
+ false, true},
+
+ {"Different Denom Different Amount", sdk.DecCoin{testDenom1, sdk.NewDec(20)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(10)},
+ false, true},
+
+ {"Same Denom Different Amount", sdk.DecCoin{testDenom1, sdk.NewDec(20)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(10)},
+ false, false},
+
+ {"Same Denom Same Amount", sdk.DecCoin{testDenom1, sdk.NewDec(20)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(20)},
+ true, false},
+ }
+
+ for i, tc := range testCases {
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.expectedPanic {
+ s.Require().Panics(func() { tc.coin.IsEqual(tc.otherCoin) }, "Test case #%d: %s", i, tc.name)
+ } else {
+ res := tc.coin.IsEqual(tc.otherCoin)
+ if tc.expectedResult {
+ s.Require().True(res, "Test case #%d: %s", i, tc.name)
+ } else {
+ s.Require().False(res, "Test case #%d: %s", i, tc.name)
+ }
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoins_IsEqual() {
+ testCases := []struct {
+ name string
+ coinsA sdk.DecCoins
+ coinsB sdk.DecCoins
+ expectedResult bool
+ expectedPanic bool
+ }{
+ {"Different length sets", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(4)},
+ }, sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(35)},
+ }, false, false},
+
+ {"Same length - different denoms", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(4)},
+ }, sdk.DecCoins{
+ sdk.DecCoin{testDenom2, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom2, sdk.NewDec(4)},
+ }, false, true},
+
+ {"Same length - different amounts", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(3)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(4)},
+ }, sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(41)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(343)},
+ }, false, false},
+
+ {"Same length - same amounts", sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(33)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(344)},
+ }, sdk.DecCoins{
+ sdk.DecCoin{testDenom1, sdk.NewDec(33)},
+ sdk.DecCoin{testDenom1, sdk.NewDec(344)},
+ }, true, false},
+ }
+
+ for i, tc := range testCases {
+ s.T().Run(tc.name, func(t *testing.T) {
+ if tc.expectedPanic {
+ s.Require().Panics(func() { tc.coinsA.IsEqual(tc.coinsB) }, "Test case #%d: %s", i, tc.name)
+ } else {
+ res := tc.coinsA.IsEqual(tc.coinsB)
+ if tc.expectedResult {
+ s.Require().True(res, "Test case #%d: %s", i, tc.name)
+ } else {
+ s.Require().False(res, "Test case #%d: %s", i, tc.name)
+ }
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoin_Validate() {
+ var empty sdk.DecCoin
+ testCases := []struct {
+ name string
+ input sdk.DecCoin
+ expectedPass bool
+ }{
+ {"Uninitalized deccoin", empty, false},
+
+ {"Invalid denom string", sdk.DecCoin{"(){9**&})", sdk.NewDec(33)}, false},
+
+ {"Negative coin amount", sdk.DecCoin{testDenom1, sdk.NewDec(-33)}, false},
+
+ {"Valid coin", sdk.DecCoin{testDenom1, sdk.NewDec(33)}, true},
+ }
+
+ for i, tc := range testCases {
+ s.T().Run(tc.name, func(t *testing.T) {
+ err := tc.input.Validate()
+ if tc.expectedPass {
+ s.Require().NoError(err, "unexpected result for test case #%d %s, input: %v", i, tc.name, tc.input)
+ } else {
+ s.Require().Error(err, "unexpected result for test case #%d %s, input: %v", i, tc.name, tc.input)
+ }
+ })
+ }
+}
+
+func (s *decCoinTestSuite) TestDecCoin_ParseDecCoin() {
+ var empty sdk.DecCoin
+ testCases := []struct {
+ name string
+ input string
+ expectedResult sdk.DecCoin
+ expectedErr bool
+ }{
+ {"Empty input", "", empty, true},
+
+ {"Bad input", "✨🌟⭐", empty, true},
+
+ {"Invalid decimal coin", "9.3.0stake", empty, true},
+
+ {"Precision over limit", "9.11111111111111111111stake", empty, true},
+
+ // TODO - Clarify - According to error message for ValidateDenom call, supposed to
+ // throw error when upper case characters are used. Currently uppercase denoms are allowed.
+ {"Invalid denom", "9.3STAKE", sdk.DecCoin{"STAKE", sdk.NewDecWithPrec(93, 1)}, false},
+
+ {"Valid input - amount and denom seperated by space", "9.3 stake", sdk.DecCoin{"stake", sdk.NewDecWithPrec(93, 1)}, false},
+
+ {"Valid input - amount and denom concatenated", "9.3stake", sdk.DecCoin{"stake", sdk.NewDecWithPrec(93, 1)}, false},
+ }
+
+ for i, tc := range testCases {
+ s.T().Run(tc.name, func(t *testing.T) {
+ res, err := sdk.ParseDecCoin(tc.input)
+ if tc.expectedErr {
+ s.Require().Error(err, "expected error for test case #%d %s, input: %v", i, tc.name, tc.input)
+ } else {
+ s.Require().NoError(err, "unexpected error for test case #%d %s, input: %v", i, tc.name, tc.input)
+ s.Require().Equal(tc.expectedResult, res, "unexpected result for test case #%d %s, input: %v", i, tc.name, tc.input)
+ }
+ })
+ }
+}
diff --git a/x/auth/client/testutil/suite.go b/x/auth/client/testutil/suite.go
index e1f903952af6..666542e30e1a 100644
--- a/x/auth/client/testutil/suite.go
+++ b/x/auth/client/testutil/suite.go
@@ -1163,12 +1163,11 @@ func (s *IntegrationTestSuite) TestTxWithoutPublicKey() {
s.Require().NotEqual(0, res.Code)
}
+// TestSignWithMultiSignersAminoJSON tests the case where a transaction with 2
+// messages which has to be signed with 2 different keys. Sign and append the
+// signatures using the CLI with Amino signing mode. Finally, send the
+// transaction to the blockchain.
func (s *IntegrationTestSuite) TestSignWithMultiSignersAminoJSON() {
- // test case:
- // Create a transaction with 2 messages which has to be signed with 2 different keys
- // Sign and append the signatures using the CLI with Amino signing mode.
- // Finally send the transaction to the blockchain. It should work.
-
require := s.Require()
val0, val1 := s.network.Validators[0], s.network.Validators[1]
val0Coin := sdk.NewCoin(fmt.Sprintf("%stoken", val0.Moniker), sdk.NewInt(10))
@@ -1185,7 +1184,7 @@ func (s *IntegrationTestSuite) TestSignWithMultiSignersAminoJSON() {
banktypes.NewMsgSend(val1.Address, addr1, sdk.NewCoins(val1Coin)),
)
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))))
- txBuilder.SetGasLimit(testdata.NewTestGasLimit()) // min required is 101892
+ txBuilder.SetGasLimit(testdata.NewTestGasLimit() * 2)
require.Equal([]sdk.AccAddress{val0.Address, val1.Address}, txBuilder.GetTx().GetSigners())
// Write the unsigned tx into a file.
@@ -1201,14 +1200,19 @@ func (s *IntegrationTestSuite) TestSignWithMultiSignersAminoJSON() {
// Then let val1 sign the file with signedByVal0.
val1AccNum, val1Seq, err := val0.ClientCtx.AccountRetriever.GetAccountNumberSequence(val0.ClientCtx, val1.Address)
require.NoError(err)
+
signedTx, err := TxSignExec(
- val1.ClientCtx, val1.Address, signedByVal0File.Name(),
- "--offline", fmt.Sprintf("--account-number=%d", val1AccNum), fmt.Sprintf("--sequence=%d", val1Seq), "--sign-mode=amino-json",
+ val1.ClientCtx,
+ val1.Address,
+ signedByVal0File.Name(),
+ "--offline",
+ fmt.Sprintf("--account-number=%d", val1AccNum),
+ fmt.Sprintf("--sequence=%d", val1Seq),
+ "--sign-mode=amino-json",
)
require.NoError(err)
signedTxFile := testutil.WriteToNewTempFile(s.T(), signedTx.String())
- // Now let's try to send this tx.
res, err := TxBroadcastExec(
val0.ClientCtx,
signedTxFile.Name(),
@@ -1218,7 +1222,7 @@ func (s *IntegrationTestSuite) TestSignWithMultiSignersAminoJSON() {
require.NoError(err)
var txRes sdk.TxResponse
require.NoError(val0.ClientCtx.Codec.UnmarshalJSON(res.Bytes(), &txRes))
- require.Equal(uint32(0), txRes.Code)
+ require.Equal(uint32(0), txRes.Code, txRes.RawLog)
// Make sure the addr1's balance got funded.
queryResJSON, err := bankcli.QueryBalancesExec(val0.ClientCtx, addr1)
diff --git a/x/auth/tx/decoder.go b/x/auth/tx/decoder.go
index d93c7446db60..2fef6312b994 100644
--- a/x/auth/tx/decoder.go
+++ b/x/auth/tx/decoder.go
@@ -16,7 +16,7 @@ import (
func DefaultTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, error) {
// Make sure txBytes follow ADR-027.
- err := rejectNonADR027(txBytes)
+ err := rejectNonADR027TxRaw(txBytes)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrTxDecode, err.Error())
}
@@ -90,13 +90,14 @@ func DefaultJSONTxDecoder(cdc codec.ProtoCodecMarshaler) sdk.TxDecoder {
}
}
-// rejectNonADR027 rejects txBytes that do not follow ADR-027. This function
+// rejectNonADR027TxRaw rejects txBytes that do not follow ADR-027. This is NOT
+// a generic ADR-027 checker, it only applies decoding TxRaw. Specifically, it
// only checks that:
// - field numbers are in ascending order (1, 2, and potentially multiple 3s),
-// - and varints as as short as possible.
-// All other ADR-027 edge cases (e.g. TxRaw fields having default values) will
-// not happen with TxRaw.
-func rejectNonADR027(txBytes []byte) error {
+// - and varints are as short as possible.
+// All other ADR-027 edge cases (e.g. default values) are not applicable with
+// TxRaw.
+func rejectNonADR027TxRaw(txBytes []byte) error {
// Make sure all fields are ordered in ascending order with this variable.
prevTagNum := protowire.Number(0)
@@ -105,21 +106,25 @@ func rejectNonADR027(txBytes []byte) error {
if m < 0 {
return fmt.Errorf("invalid length; %w", protowire.ParseError(m))
}
+ // TxRaw only has bytes fields.
if wireType != protowire.BytesType {
- return fmt.Errorf("expected %d wire type, got %d", protowire.VarintType, wireType)
+ return fmt.Errorf("expected %d wire type, got %d", protowire.BytesType, wireType)
}
+ // Make sure fields are ordered in ascending order.
if tagNum < prevTagNum {
return fmt.Errorf("txRaw must follow ADR-027, got tagNum %d after tagNum %d", tagNum, prevTagNum)
}
prevTagNum = tagNum
// All 3 fields of TxRaw have wireType == 2, so their next component
- // is a varint.
- // We make sure that the varint is as short as possible.
+ // is a varint, so we can safely call ConsumeVarint here.
+ // Byte structure:
+ // Inner fields are verified in `DefaultTxDecoder`
lengthPrefix, m := protowire.ConsumeVarint(txBytes[m:])
if m < 0 {
return fmt.Errorf("invalid length; %w", protowire.ParseError(m))
}
+ // We make sure that this varint is as short as possible.
n := varintMinLength(lengthPrefix)
if n != m {
return fmt.Errorf("length prefix varint for tagNum %d is not as short as possible, read %d, only need %d", tagNum, m, n)
@@ -141,23 +146,23 @@ func rejectNonADR027(txBytes []byte) error {
func varintMinLength(n uint64) int {
switch {
// Note: 1< byte(amount)`
-- Denom Metadata: `0x1 | byte(denom) -> ProtocolBuffer(Metadata)`
-- Balances: `0x2 | byte(address length) | []byte(address) | []byte(balance.Denom) -> ProtocolBuffer(balance)`
+1. Account balances
+2. Denomination metadata
+3. The total supply of all balances
+
+In addition, the `x/bank` module keeps the following indexes to manage the
+aforementioned state:
+
+- Supply Index: `0x0 | byte(denom) -> byte(amount)`
+- Denom Metadata Index: `0x1 | byte(denom) -> ProtocolBuffer(Metadata)`
+- Balances Index: `0x2 | byte(address length) | []byte(address) | []byte(balance.Denom) -> ProtocolBuffer(balance)`
+- Reverse Denomination to Address Index: `0x03 | byte(denom) | 0x00 | []byte(address) -> 0`
diff --git a/x/bank/types/key.go b/x/bank/types/key.go
index e91a38970e8b..be8f43f9aebe 100644
--- a/x/bank/types/key.go
+++ b/x/bank/types/key.go
@@ -22,11 +22,13 @@ const (
// KVStore keys
var (
- // BalancesPrefix is the prefix for the account balances store. We use a byte
- // (instead of `[]byte("balances")` to save some disk space).
- BalancesPrefix = []byte{0x02}
SupplyKey = []byte{0x00}
DenomMetadataPrefix = []byte{0x1}
+ DenomAddressPrefix = []byte{0x03}
+
+ // BalancesPrefix is the prefix for the account balances store. We use a byte
+ // (instead of `[]byte("balances")` to save some disk space).
+ BalancesPrefix = []byte{0x02}
)
// DenomMetadataKey returns the denomination metadata key.
@@ -44,12 +46,16 @@ func AddressFromBalancesStore(key []byte) (sdk.AccAddress, error) {
if len(key) == 0 {
return nil, ErrInvalidKey
}
+
kv.AssertKeyAtLeastLength(key, 1)
+
addrLen := key[0]
bound := int(addrLen)
+
if len(key)-1 < bound {
return nil, ErrInvalidKey
}
+
return key[1 : bound+1], nil
}
@@ -57,3 +63,10 @@ func AddressFromBalancesStore(key []byte) (sdk.AccAddress, error) {
func CreateAccountBalancesPrefix(addr []byte) []byte {
return append(BalancesPrefix, address.MustLengthPrefix(addr)...)
}
+
+// CreateDenomAddressPrefix creates a prefix for a reverse index of denomination
+// to account balance for that denomination.
+func CreateDenomAddressPrefix(denom string) []byte {
+ key := append(DenomAddressPrefix, []byte(denom)...)
+ return append(key, 0)
+}
diff --git a/x/genutil/client/cli/init.go b/x/genutil/client/cli/init.go
index a51d4b90757b..8a7d85059cb3 100644
--- a/x/genutil/client/cli/init.go
+++ b/x/genutil/client/cli/init.go
@@ -80,7 +80,11 @@ func InitCmd(mbm module.BasicManager, defaultNodeHome string) *cobra.Command {
config.SetRoot(clientCtx.HomeDir)
chainID, _ := cmd.Flags().GetString(flags.FlagChainID)
- if chainID == "" {
+ switch {
+ case chainID != "":
+ case clientCtx.ChainID != "":
+ chainID = clientCtx.ChainID
+ default:
chainID = fmt.Sprintf("test-chain-%v", tmrand.Str(6))
}
diff --git a/x/genutil/client/cli/init_test.go b/x/genutil/client/cli/init_test.go
index 64ec01c4adf1..b64d792c29fc 100644
--- a/x/genutil/client/cli/init_test.go
+++ b/x/genutil/client/cli/init_test.go
@@ -199,13 +199,60 @@ func TestStartStandAlone(t *testing.T) {
func TestInitNodeValidatorFiles(t *testing.T) {
home := t.TempDir()
cfg, err := genutiltest.CreateDefaultTendermintConfig(home)
+ require.NoError(t, err)
+
nodeID, valPubKey, err := genutil.InitializeNodeValidatorFiles(cfg)
+ require.NoError(t, err)
- require.Nil(t, err)
require.NotEqual(t, "", nodeID)
require.NotEqual(t, 0, len(valPubKey.Bytes()))
}
+func TestInitConfig(t *testing.T) {
+ home := t.TempDir()
+ logger := log.NewNopLogger()
+ cfg, err := genutiltest.CreateDefaultTendermintConfig(home)
+ require.NoError(t, err)
+
+ serverCtx := server.NewContext(viper.New(), cfg, logger)
+ interfaceRegistry := types.NewInterfaceRegistry()
+ marshaler := codec.NewProtoCodec(interfaceRegistry)
+ clientCtx := client.Context{}.
+ WithCodec(marshaler).
+ WithLegacyAmino(makeCodec()).
+ WithChainID("foo"). // add chain-id to clientCtx
+ WithHomeDir(home)
+
+ ctx := context.Background()
+ ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
+ ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx)
+
+ cmd := genutilcli.InitCmd(testMbm, home)
+ cmd.SetArgs([]string{"testnode"})
+
+ require.NoError(t, cmd.ExecuteContext(ctx))
+
+ old := os.Stdout
+ r, w, _ := os.Pipe()
+ os.Stdout = w
+
+ cmd = server.ExportCmd(nil, home)
+ require.NoError(t, cmd.ExecuteContext(ctx))
+
+ outC := make(chan string)
+ go func() {
+ var buf bytes.Buffer
+ io.Copy(&buf, r)
+ outC <- buf.String()
+ }()
+
+ w.Close()
+ os.Stdout = old
+ out := <-outC
+
+ require.Contains(t, out, "\"chain_id\": \"foo\"")
+}
+
// custom tx codec
func makeCodec() *codec.LegacyAmino {
var cdc = codec.NewLegacyAmino()
diff --git a/x/staking/client/testutil/suite.go b/x/staking/client/testutil/suite.go
index 965c35823c57..4cbae20eba43 100644
--- a/x/staking/client/testutil/suite.go
+++ b/x/staking/client/testutil/suite.go
@@ -57,15 +57,18 @@ func (s *IntegrationTestSuite) SetupSuite() {
val2 := s.network.Validators[1]
// redelegate
- _, err = MsgRedelegateExec(
+ out, err := MsgRedelegateExec(
val.ClientCtx,
val.Address,
val.ValAddress,
val2.ValAddress,
unbond,
- fmt.Sprintf("--%s=%d", flags.FlagGas, 202954), // 202954 is the required
+ fmt.Sprintf("--%s=%d", flags.FlagGas, 300000),
)
s.Require().NoError(err)
+ var txRes sdk.TxResponse
+ s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes))
+ s.Require().Equal(uint32(0), txRes.Code)
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
// unbonding
@@ -1181,7 +1184,7 @@ func (s *IntegrationTestSuite) TestNewRedelegateCmd() {
val2.ValAddress.String(), // dst-validator-addr
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(150)).String(), // amount
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
- fmt.Sprintf("--%s=%s", flags.FlagGas, "auto"),
+ fmt.Sprintf("--%s=%d", flags.FlagGas, 300000),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),